Skip to content

Commit

Permalink
Merge pull request #331 from ruby/pr-e063f53aaa8d0fab071fc56cad811a29…
Browse files Browse the repository at this point in the history
…ba29e1b4

Make `RbExceptionFormatter#format` error tolerant
  • Loading branch information
kateinoigakukun authored Dec 5, 2023
2 parents 9a15b99 + a9b9dd0 commit 3e07094
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 13 deletions.
65 changes: 52 additions & 13 deletions packages/npm-packages/ruby-wasm-wasi/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,8 +534,26 @@ type RubyVMPrivate = {

class RbExceptionFormatter {
private literalsCache: [RbValue, RbValue, RbValue] | null = null;
private isFormmatting: boolean = false;

format(error: RbValue, vm: RubyVM, privateObject: RubyVMPrivate): string {
// All Ruby exceptions raised during formatting exception message should
// be caught and return a fallback message.
// Therefore, we don't need to worry about infinite recursion here ideally
// but checking re-entrancy just in case.
class RbExceptionFormatterError extends Error {}
if (this.isFormmatting) {
throw new RbExceptionFormatterError("Unexpected exception occurred during formatting exception message");
}
this.isFormmatting = true;
try {
return this._format(error, vm, privateObject);
} finally {
this.isFormmatting = false;
}
}

private _format(error: RbValue, vm: RubyVM, privateObject: RubyVMPrivate): string {
const [zeroLiteral, oneLiteral, newLineLiteral] = (() => {
if (this.literalsCache == null) {
const zeroOneNewLine: [RbValue, RbValue, RbValue] = [
Expand All @@ -550,21 +568,42 @@ class RbExceptionFormatter {
}
})();

const backtrace = error.call("backtrace");
let className: string;
let backtrace: RbValue;
let message: string;
try {
className = error.call("class").toString();
} catch (e) {
className = "unknown";
}

try {
message = error.toString();
} catch (e) {
message = "unknown";
}

try {
backtrace = error.call("backtrace");
} catch (e) {
return this.formatString(className, message);
}

if (backtrace.call("nil?").toString() === "true") {
return this.formatString(
error.call("class").toString(),
error.toString(),
);
return this.formatString(className, message);
}
try {
const firstLine = backtrace.call("at", zeroLiteral);
const restLines = backtrace
.call("drop", oneLiteral)
.call("join", newLineLiteral);
return this.formatString(className, message, [
firstLine.toString(),
restLines.toString(),
]);
} catch (e) {
return this.formatString(className, message);
}
const firstLine = backtrace.call("at", zeroLiteral);
const restLines = backtrace
.call("drop", oneLiteral)
.call("join", newLineLiteral);
return this.formatString(error.call("class").toString(), error.toString(), [
firstLine.toString(),
restLines.toString(),
]);
}

formatString(
Expand Down
19 changes: 19 additions & 0 deletions packages/npm-packages/ruby-wasm-wasi/test/vm.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,25 @@ eval:3:in \`foo'
eval:11:in \`<main>'`);
});

test("exception while formatting exception backtrace", async () => {
const vm = await initRubyVM();
const throwError = () => {
vm.eval(`
class BrokenException < Exception
def to_s
raise "something went wrong in BrokenException#to_s"
end
def backtrace
raise "something went wrong in BrokenException#backtrace"
end
end
raise BrokenException.new
`);
}
expect(throwError)
.toThrowError(`BrokenException: unknown`);
});

test("eval encoding", async () => {
const vm = await initRubyVM();
expect(vm.eval(`Encoding.default_external.name`).toString()).toBe("UTF-8");
Expand Down

0 comments on commit 3e07094

Please sign in to comment.