Fix a real crash: an uncaught error from inside a custom tag's own
expansion terminated the whole process instead of reporting cleanly Found while verifying documentation for #[name] vs $[%name] (writing the new tag-hierarchy section surfaced that my own published example was factually wrong about which sigil reads a keyword attribute - #[name] actually reads local->variables, a separate store; $[%name] is the real keyword sigil, confirmed against Tag::getVariable directly). Referencing an uninitialized #[name] from inside a custom tag's own template crashed the process outright (SIGABRT) rather than reporting a normal error - confirmed via a real gdb backtrace, not guessed: 1. Tag::getVariable's "#[" branch and Tag::assignVariable's "local"/ "parent" scope branches all dereferenced `local` unconditionally. `local` is genuinely 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 - <if>/<while>/<until> are the exception, passing `this` as local via their splitTagName constructor path). Added NULL checks with clear messages instead of an unconditional dereference. 2. The real root cause of the actual crash observed, though: jet-2.0.cpp's own top-level catch(coreutils::Exception) handler unconditionally calls data.setCursor(global.errorCursor) to show precise source-line context - but global.errorCursor is updated by every Tag's own construction, so it can (and, for an error originating inside a custom tag's own expanded template - container3 in __dotag.cpp - always does) point into a completely different ZString buffer than `data` (the top-level script). ZString::setCursor() correctly rejects a cursor outside its own buffer by throwing - but that throw happens while the ORIGINAL exception is still being handled inside this same catch block, and a second exception escaping an active catch block calls std::terminate() immediately, regardless of any enclosing try/catch. Confirmed via gdb: the abort's own stack showed __cxa_throw called from ZString::setCursor, called from the catch block itself. Fixed by wrapping the precise-location diagnostics in their own try/catch, falling back to just the error text (already printed first) plus a note that precise context isn't available, rather than crashing the whole process. The error text is the useful part either way - the line-context is a bonus only available when the cursor genuinely belongs to the top-level buffer. Verified via a full regression pass (9 real jet-2.0 runs covering custom tag scoping at 1/2/3 nesting levels, the dump tag tree, <try>/<catch>, <if>/<else>, and the corrected $[%name] keyword sigil) - all still work, plus the specific crash reproduction now reports a clean error instead of aborting.
This commit is contained in:
1 parent
8483618012
commit
9f3349b671
2 files changed
+48
-11
No files matched your search
@@ -499,6 +499,17 @@ namespace jet {
|
|||||||
coreutils::MString name;
|
coreutils::MString name;
|
||||||
coreutils::MString modifier;
|
coreutils::MString modifier;
|
||||||
renderVariableName(variable, name, 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 - <if>/<while>/<until>/<until>'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())
|
if(local->variables.find(name) == local->variables.end())
|
||||||
throw coreutils::Exception("local variable is not initialized.");
|
throw coreutils::Exception("local variable is not initialized.");
|
||||||
return local->variables[name];
|
return local->variables[name];
|
||||||
@@ -550,10 +561,15 @@ namespace jet {
|
|||||||
}
|
}
|
||||||
if((scope == "global") || (scope == ""))
|
if((scope == "global") || (scope == ""))
|
||||||
global.variables[variable] = value;
|
global.variables[variable] = value;
|
||||||
else if(scope == "local")
|
else if(scope == "local") {
|
||||||
local->variables[variable] = value;
|
if(local == NULL)
|
||||||
else if(scope == "parent")
|
throw coreutils::Exception("scope=\"local\" requested but no local scope is active.");
|
||||||
local->parent->variables[variable] = value;
|
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) {
|
void Tag::storeVariable(coreutils::ZString variable, coreutils::MString value, coreutils::ZString scope) {
|
||||||
|
|||||||
+28
-7
@@ -23,15 +23,36 @@ int main(int argc, char **argv, char **envp) {
|
|||||||
std::cout << out;
|
std::cout << out;
|
||||||
}
|
}
|
||||||
catch(coreutils::Exception e) {
|
catch(coreutils::Exception e) {
|
||||||
data.setCursor(global.errorCursor);
|
|
||||||
data.moveBackToLineStart();
|
|
||||||
std::cout << "-----------------------------" << std::endl;
|
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 << "Error text: " << e.text << std::endl;
|
||||||
std::cout << "-----------------------------" << std::endl;
|
// global.errorCursor is updated by every Tag's own construction, so
|
||||||
std::cout << data.parsed() << std::endl;
|
// it can point into a completely different buffer than `data` (the
|
||||||
std::cout << "******** Error caught: " << e.text << std::endl;
|
// top-level script) - most commonly, a custom tag's own expanded
|
||||||
std::cout << data.unparsed() << std::endl;
|
// 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();
|
global.dump();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user