diff --git a/Tag.cpp b/Tag.cpp index 46167ec..9424198 100644 --- a/Tag.cpp +++ b/Tag.cpp @@ -499,6 +499,17 @@ namespace jet { coreutils::MString name; coreutils::MString modifier; renderVariableName(variable, name, modifier); + // local is NULL at the true top level (jet-2.0.cpp's own + // new jet::__jet(data, out, global, NULL, NULL)) and stays NULL + // through any tag that doesn't establish its own local scope (most + // don't - ///'s splitTagName constructor + // form is the exception, passing `this` as local) - dereferencing + // it unconditionally here crashes (SIGABRT/segfault, not a clean + // catchable exception) rather than reporting a normal error. + // Confirmed via a real crash while testing #[name] inside a custom + // tag invocation with no enclosing local-scope-owning tag. + if(local == NULL) + throw coreutils::Exception("local variable requested but no local scope is active."); if(local->variables.find(name) == local->variables.end()) throw coreutils::Exception("local variable is not initialized."); return local->variables[name]; @@ -550,10 +561,15 @@ namespace jet { } if((scope == "global") || (scope == "")) global.variables[variable] = value; - else if(scope == "local") - local->variables[variable] = value; - else if(scope == "parent") - local->parent->variables[variable] = value; + else if(scope == "local") { + if(local == NULL) + throw coreutils::Exception("scope=\"local\" requested but no local scope is active."); + local->variables[variable] = value; + } else if(scope == "parent") { + if((local == NULL) || (local->parent == NULL)) + throw coreutils::Exception("scope=\"parent\" requested but no local scope (or its parent) is active."); + local->parent->variables[variable] = value; + } } void Tag::storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) { diff --git a/jet-2.0.cpp b/jet-2.0.cpp index 6ad22fa..fed52e5 100644 --- a/jet-2.0.cpp +++ b/jet-2.0.cpp @@ -23,16 +23,37 @@ int main(int argc, char **argv, char **envp) { std::cout << out; } catch(coreutils::Exception e) { - data.setCursor(global.errorCursor); - data.moveBackToLineStart(); std::cout << "-----------------------------" << std::endl; - std::cout << "Error in jet script '" << argv[1] << "' at line " << data.getLineNumberAtCursor() << std::endl; std::cout << "Error text: " << e.text << std::endl; - std::cout << "-----------------------------" << std::endl; - std::cout << data.parsed() << std::endl; - std::cout << "******** Error caught: " << e.text << std::endl; - std::cout << data.unparsed() << std::endl; + // global.errorCursor is updated by every Tag's own construction, so + // it can point into a completely different buffer than `data` (the + // top-level script) - most commonly, a custom tag's own expanded + // template body (container3 in __dotag.cpp) when the error actually + // happened while that was being processed, not the top-level script + // directly. ZString::setCursor() correctly rejects a cursor that + // isn't within its own buffer, but that rejection is itself a throw + // - uncaught here, a SECOND exception escaping while this catch + // block is still handling the first one calls std::terminate() + // immediately, crashing the whole process instead of reporting a + // normal error. Confirmed via a real crash + gdb backtrace while + // testing an error thrown from inside a custom tag's own body. + // Degrade gracefully instead: the error text above is already the + // important part; the precise source-line context is a bonus only + // available when the cursor genuinely belongs to this buffer. + try { + data.setCursor(global.errorCursor); + data.moveBackToLineStart(); + std::cout << "Error in jet script '" << argv[1] << "' at line " << data.getLineNumberAtCursor() << std::endl; + std::cout << "-----------------------------" << std::endl; + std::cout << data.parsed() << std::endl; + std::cout << "******** Error caught: " << e.text << std::endl; + std::cout << data.unparsed() << std::endl; + } catch(coreutils::Exception) { + std::cout << "(error originated inside a custom tag's own expansion - " + << "no precise source-line context available for it)" << std::endl; + std::cout << "-----------------------------" << std::endl; + } global.dump(); - } + } }