Implement printing backtraces on VM execution errors

This commit is contained in:
Konstantin Nazarov 2024-09-15 03:32:52 +01:00
parent 6126c7b8eb
commit bb71669c83
Signed by: knazarov
GPG key ID: 4CFE0A42FA409C22
4 changed files with 87 additions and 22 deletions

View file

@ -436,6 +436,46 @@ Result<StackFrame> StackFrame::ret(uint64_t regnum) const {
return std::move(parent_stack); return std::move(parent_stack);
} }
Result<String> StackFrame::backtrace(uint64_t indent) const {
String res = TRY(String::create(""));
StackFrame cur = TRY(copy());
while (true) {
Value par = TRY(cur.parent());
// If parent frame is empty - we are at the top-level. Top-level is always
// <unnamed>, so let's just skip it
if (par.is<Nil>()) break;
auto fun = TRY(cur.fun());
for (uint64_t i = 0; i < indent; i++) {
res = TRY(res.concat(" "));
}
res = TRY(res.concat("Function "));
if (fun.is<Function>()) {
Function& f = *fun.to<Function>();
auto name = TRY(f.name());
if (!name.is<Symbol>()) {
name = Value(TRY(String::create("<unnamed>")));
} else {
name = Value(TRY(String::create(*name.to<Symbol>())));
}
res = TRY(res.concat(*name.to<String>()));
} else if (fun.is<StdlibFunction>()) {
StdlibFunction& f = *fun.to<StdlibFunction>();
auto name = TRY(String::create(TRY(f.name())));
res = TRY(res.concat(name));
}
res = TRY(res.concat("\n"));
cur = TRY(par.to<StackFrame>()->copy());
}
return res;
}
Result<Value> Error::copy_value() const { Result<Value> Error::copy_value() const {
return Value(Error(TRY(_value.copy()))); return Value(Error(TRY(_value.copy())));
} }

View file

@ -1144,6 +1144,8 @@ class StackFrame : public Object {
virtual Result<Value> copy_value() const final; virtual Result<Value> copy_value() const final;
Result<StackFrame> copy() const; Result<StackFrame> copy() const;
Result<String> backtrace(uint64_t indent = 0) const;
private: private:
GcRoot<PodStackFrame> _value; GcRoot<PodStackFrame> _value;
}; };

View file

@ -13,23 +13,7 @@
StaticArena<64 * 1024 * 1024> arena; StaticArena<64 * 1024 * 1024> arena;
Result<Value> run_string(const String& fname, const String& src) { Result<void> print_error(const Result<Value>& res, const String& backtrace) {
auto parsed = TRY(read_multiple(src));
TRY(arena_gc());
auto compiled = TRY(compile(fname, parsed));
Module& mod = *compiled.to<Module>();
Dict globals = TRY(Dict::create());
auto vm = TRY(VM::create(mod, globals));
auto res = TRY(vm.run());
return res;
}
Result<void> print_error(const Result<Value>& res) {
if (res.has_error()) { if (res.has_error()) {
auto errobj = TRY(geterrobj().copy()); auto errobj = TRY(geterrobj().copy());
if (errobj.is<Nil>()) { if (errobj.is<Nil>()) {
@ -39,11 +23,46 @@ Result<void> print_error(const Result<Value>& res) {
print_string(*message.to<String>()); print_string(*message.to<String>());
std::cout << "\n"; std::cout << "\n";
} }
if (TRY(backtrace.size()) > 0) {
print_string(backtrace);
}
} }
return Result<void>(); return Result<void>();
} }
Result<Value> run_string(const String& fname, const String& src) {
auto maybe_parsed = read_multiple(src);
auto empty_backtrace = TRY(String::create(""));
if (maybe_parsed.has_error()) {
print_error(maybe_parsed, empty_backtrace);
return maybe_parsed;
}
auto parsed = maybe_parsed.release_value();
auto maybe_compiled = compile(fname, parsed);
if (maybe_compiled.has_error()) {
print_error(maybe_compiled, empty_backtrace);
return maybe_compiled;
}
auto compiled = maybe_compiled.release_value();
Module& mod = *compiled.to<Module>();
Dict globals = TRY(Dict::create());
auto vm = TRY(VM::create(mod, globals));
auto maybe_res = vm.run();
if (maybe_res.has_error()) {
auto backtrace = TRY(vm.backtrace(2));
print_error(maybe_res, backtrace);
}
return maybe_res.release_value();
}
Result<void> run_repl() { Result<void> run_repl() {
Dict globals = TRY(Dict::create()); Dict globals = TRY(Dict::create());
@ -51,15 +70,16 @@ Result<void> run_repl() {
while (true) { while (true) {
auto src = TRY(read_line("valeri> ")); auto src = TRY(read_line("valeri> "));
auto maybe_parsed = read_multiple(src); auto maybe_parsed = read_multiple(src);
auto empty_backtrace = TRY(String::create(""));
if (maybe_parsed.has_error()) { if (maybe_parsed.has_error()) {
print_error(maybe_parsed); print_error(maybe_parsed, empty_backtrace);
continue; continue;
} }
auto parsed = maybe_parsed.release_value(); auto parsed = maybe_parsed.release_value();
auto maybe_compiled = compile(fname, parsed); auto maybe_compiled = compile(fname, parsed);
if (maybe_compiled.has_error()) { if (maybe_compiled.has_error()) {
print_error(maybe_compiled); print_error(maybe_compiled, empty_backtrace);
continue; continue;
} }
auto compiled = maybe_compiled.release_value(); auto compiled = maybe_compiled.release_value();
@ -77,7 +97,8 @@ Result<void> run_repl() {
} }
} else { } else {
// Need to print the error // Need to print the error
print_error(maybe_res); auto backtrace = TRY(vm.backtrace(2));
print_error(maybe_res, backtrace);
} }
} }
@ -96,7 +117,6 @@ Result<void> run(int argc, const char* argv[]) {
auto fname = TRY(String::create("<stdin>")); auto fname = TRY(String::create("<stdin>"));
auto res = run_string(fname, src); auto res = run_string(fname, src);
if (res.has_error()) { if (res.has_error()) {
print_error(res);
exit(1); exit(1);
} }
@ -105,7 +125,6 @@ Result<void> run(int argc, const char* argv[]) {
auto fname = TRY(String::create(argv[1])); auto fname = TRY(String::create(argv[1]));
auto res = run_string(fname, src); auto res = run_string(fname, src);
if (res.has_error()) { if (res.has_error()) {
print_error(res);
exit(1); exit(1);
} }
} }

View file

@ -85,6 +85,10 @@ class VM {
return fun.to<Function>()->closure(); return fun.to<Function>()->closure();
} }
Result<String> backtrace(uint64_t indent = 0) {
return _stack.backtrace(indent);
}
private: private:
StackFrame _stack; StackFrame _stack;
Dict _globals; Dict _globals;