Skip to content

Commit 3e07094

Browse files
Merge pull request #331 from ruby/pr-e063f53aaa8d0fab071fc56cad811a29ba29e1b4
Make `RbExceptionFormatter#format` error tolerant
2 parents 9a15b99 + a9b9dd0 commit 3e07094

File tree

2 files changed

+71
-13
lines changed

2 files changed

+71
-13
lines changed

packages/npm-packages/ruby-wasm-wasi/src/index.ts

Lines changed: 52 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,26 @@ type RubyVMPrivate = {
534534

535535
class RbExceptionFormatter {
536536
private literalsCache: [RbValue, RbValue, RbValue] | null = null;
537+
private isFormmatting: boolean = false;
537538

538539
format(error: RbValue, vm: RubyVM, privateObject: RubyVMPrivate): string {
540+
// All Ruby exceptions raised during formatting exception message should
541+
// be caught and return a fallback message.
542+
// Therefore, we don't need to worry about infinite recursion here ideally
543+
// but checking re-entrancy just in case.
544+
class RbExceptionFormatterError extends Error {}
545+
if (this.isFormmatting) {
546+
throw new RbExceptionFormatterError("Unexpected exception occurred during formatting exception message");
547+
}
548+
this.isFormmatting = true;
549+
try {
550+
return this._format(error, vm, privateObject);
551+
} finally {
552+
this.isFormmatting = false;
553+
}
554+
}
555+
556+
private _format(error: RbValue, vm: RubyVM, privateObject: RubyVMPrivate): string {
539557
const [zeroLiteral, oneLiteral, newLineLiteral] = (() => {
540558
if (this.literalsCache == null) {
541559
const zeroOneNewLine: [RbValue, RbValue, RbValue] = [
@@ -550,21 +568,42 @@ class RbExceptionFormatter {
550568
}
551569
})();
552570

553-
const backtrace = error.call("backtrace");
571+
let className: string;
572+
let backtrace: RbValue;
573+
let message: string;
574+
try {
575+
className = error.call("class").toString();
576+
} catch (e) {
577+
className = "unknown";
578+
}
579+
580+
try {
581+
message = error.toString();
582+
} catch (e) {
583+
message = "unknown";
584+
}
585+
586+
try {
587+
backtrace = error.call("backtrace");
588+
} catch (e) {
589+
return this.formatString(className, message);
590+
}
591+
554592
if (backtrace.call("nil?").toString() === "true") {
555-
return this.formatString(
556-
error.call("class").toString(),
557-
error.toString(),
558-
);
593+
return this.formatString(className, message);
594+
}
595+
try {
596+
const firstLine = backtrace.call("at", zeroLiteral);
597+
const restLines = backtrace
598+
.call("drop", oneLiteral)
599+
.call("join", newLineLiteral);
600+
return this.formatString(className, message, [
601+
firstLine.toString(),
602+
restLines.toString(),
603+
]);
604+
} catch (e) {
605+
return this.formatString(className, message);
559606
}
560-
const firstLine = backtrace.call("at", zeroLiteral);
561-
const restLines = backtrace
562-
.call("drop", oneLiteral)
563-
.call("join", newLineLiteral);
564-
return this.formatString(error.call("class").toString(), error.toString(), [
565-
firstLine.toString(),
566-
restLines.toString(),
567-
]);
568607
}
569608

570609
formatString(

packages/npm-packages/ruby-wasm-wasi/test/vm.test.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,25 @@ eval:3:in \`foo'
113113
eval:11:in \`<main>'`);
114114
});
115115

116+
test("exception while formatting exception backtrace", async () => {
117+
const vm = await initRubyVM();
118+
const throwError = () => {
119+
vm.eval(`
120+
class BrokenException < Exception
121+
def to_s
122+
raise "something went wrong in BrokenException#to_s"
123+
end
124+
def backtrace
125+
raise "something went wrong in BrokenException#backtrace"
126+
end
127+
end
128+
raise BrokenException.new
129+
`);
130+
}
131+
expect(throwError)
132+
.toThrowError(`BrokenException: unknown`);
133+
});
134+
116135
test("eval encoding", async () => {
117136
const vm = await initRubyVM();
118137
expect(vm.eval(`Encoding.default_external.name`).toString()).toBe("UTF-8");

0 commit comments

Comments
 (0)