Skip to content

Commit 9aef510

Browse files
Clear errinfo during handling exception
1 parent 8f2aee4 commit 9aef510

File tree

3 files changed

+66
-31
lines changed

3 files changed

+66
-31
lines changed

ext/witapi/bindgen/rb-abi-guest.wit

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ rb-eval-string-protect: function(str: string) -> tuple<rb-abi-value, s32>
1313
rb-funcallv-protect: function(recv: rb-abi-value, mid: rb-id, args: list<rb-abi-value>) -> tuple<rb-abi-value, s32>
1414
rb-intern: function(name: string) -> rb-id
1515
rb-errinfo: function() -> rb-abi-value
16+
rb-clear-errinfo: function()
1617

1718
rstring-ptr: function(value: rb-abi-value) -> string

ext/witapi/witapi-core.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,10 @@ rb_abi_guest_rb_abi_value_t rb_abi_guest_rb_errinfo(void) {
218218
return rb_abi_guest_rb_abi_value_new((void *)retval);
219219
}
220220

221+
void rb_abi_guest_rb_clear_errinfo(void) {
222+
rb_set_errinfo(Qnil);
223+
}
224+
221225
void rb_abi_guest_rstring_ptr(rb_abi_guest_rb_abi_value_t value,
222226
rb_abi_guest_string_t *ret0) {
223227
VALUE r_str = (VALUE)rb_abi_guest_rb_abi_value_get(&value);

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

Lines changed: 61 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,12 @@ export class RubyVM {
2323
guest: RbAbi.RbAbiGuest;
2424
private instance: WebAssembly.Instance | null = null;
2525
private exporter: JsValueExporter;
26+
private exceptionFormatter: RbExceptionFormatter;
2627

2728
constructor() {
2829
this.guest = new RbAbi.RbAbiGuest();
2930
this.exporter = new JsValueExporter();
31+
this.exceptionFormatter = new RbExceptionFormatter();
3032
}
3133

3234
/**
@@ -168,7 +170,7 @@ export class RubyVM {
168170
*
169171
*/
170172
eval(code: string): RbValue {
171-
return evalRbCode(this, this.exporter, code);
173+
return evalRbCode(this, { exporter: this.exporter, exceptionFormatter: this.exceptionFormatter }, code);
172174
}
173175
}
174176

@@ -189,7 +191,7 @@ export class RbValue {
189191
constructor(
190192
private inner: RbAbi.RbAbiValue,
191193
private vm: RubyVM,
192-
private exporter: JsValueExporter
194+
private privateObject: RubyVMPrivate
193195
) {}
194196

195197
/**
@@ -207,9 +209,9 @@ export class RbValue {
207209
call(callee: string, ...args: RbValue[]): RbValue {
208210
const innerArgs = args.map((arg) => arg.inner);
209211
return new RbValue(
210-
callRbMethod(this.vm, this.exporter, this.inner, callee, innerArgs),
212+
callRbMethod(this.vm, this.privateObject, this.inner, callee, innerArgs),
211213
this.vm,
212-
this.exporter
214+
this.privateObject
213215
);
214216
}
215217

@@ -231,7 +233,7 @@ export class RbValue {
231233
toString(): string {
232234
const rbString = callRbMethod(
233235
this.vm,
234-
this.exporter,
236+
this.privateObject,
235237
this.inner,
236238
"to_s",
237239
[]
@@ -252,7 +254,7 @@ export class RbValue {
252254
return null;
253255
}
254256
jsValue.call("__export_to_js");
255-
return this.exporter.exportJsValue();
257+
return this.privateObject.exporter.exportJsValue();
256258
}
257259
}
258260

@@ -269,18 +271,52 @@ enum ruby_tag_type {
269271
Mask = 0xf,
270272
}
271273

272-
const formatException = (
273-
klass: string,
274-
message: string,
275-
backtrace: [string, string]
276-
) => {
277-
return `${backtrace[0]}: ${message} (${klass})\n${backtrace[1]}`;
274+
type RubyVMPrivate = {
275+
exporter: JsValueExporter,
276+
exceptionFormatter: RbExceptionFormatter,
278277
};
279278

279+
280+
class RbExceptionFormatter {
281+
private literalsCache: [RbValue, RbValue, RbValue] | null = null;
282+
283+
format(error: RbValue, vm: RubyVM, privateObject: RubyVMPrivate): string {
284+
const [zeroLiteral, oneLiteral, newLineLiteral] = (() => {
285+
if (this.literalsCache == null) {
286+
const zeroOneNewLine: [RbValue, RbValue, RbValue] = [
287+
evalRbCode(vm, privateObject, "0"),
288+
evalRbCode(vm, privateObject, "1"),
289+
evalRbCode(vm, privateObject, `"\n"`)
290+
];
291+
this.literalsCache = zeroOneNewLine;
292+
return zeroOneNewLine;
293+
} else {
294+
return this.literalsCache;
295+
}
296+
})();
297+
298+
const backtrace = error.call("backtrace");
299+
const firstLine = backtrace.call("at", zeroLiteral);
300+
const restLines = backtrace.call("drop", oneLiteral).call("join", newLineLiteral);
301+
return this.formatString(error.call("class").toString(), error.toString(), [
302+
firstLine.toString(),
303+
restLines.toString(),
304+
])
305+
}
306+
307+
formatString(
308+
klass: string,
309+
message: string,
310+
backtrace: [string, string]
311+
): string {
312+
return `${backtrace[0]}: ${message} (${klass})\n${backtrace[1]}`;
313+
};
314+
}
315+
280316
const checkStatusTag = (
281317
rawTag: number,
282318
vm: RubyVM,
283-
exporter: JsValueExporter
319+
privateObject: RubyVMPrivate
284320
) => {
285321
switch (rawTag & ruby_tag_type.Mask) {
286322
case ruby_tag_type.None:
@@ -299,40 +335,34 @@ const checkStatusTag = (
299335
throw new RbError("unexpected throw");
300336
case ruby_tag_type.Raise:
301337
case ruby_tag_type.Fatal:
302-
const error = new RbValue(vm.guest.rbErrinfo(), vm, exporter);
303-
const newLine = evalRbCode(vm, exporter, `"\n"`);
304-
const backtrace = error.call("backtrace");
305-
const firstLine = backtrace.call("at", evalRbCode(vm, exporter, "0"));
306-
const restLines = backtrace
307-
.call("drop", evalRbCode(vm, exporter, "1"))
308-
.call("join", newLine);
309-
throw new RbError(
310-
formatException(error.call("class").toString(), error.toString(), [
311-
firstLine.toString(),
312-
restLines.toString(),
313-
])
314-
);
338+
const error = new RbValue(vm.guest.rbErrinfo(), vm, privateObject);
339+
if (error.call("nil?").toString() === "true") {
340+
throw new RbError("no exception object");
341+
}
342+
// clear errinfo if got exception due to no rb_jump_tag
343+
vm.guest.rbClearErrinfo();
344+
throw new RbError(privateObject.exceptionFormatter.format(error, vm, privateObject));
315345
default:
316346
throw new RbError(`unknown error tag: ${rawTag}`);
317347
}
318348
};
319349

320350
const callRbMethod = (
321351
vm: RubyVM,
322-
exporter: JsValueExporter,
352+
privateObject: RubyVMPrivate,
323353
recv: RbAbi.RbAbiValue,
324354
callee: string,
325355
args: RbAbi.RbAbiValue[]
326356
) => {
327357
const mid = vm.guest.rbIntern(callee + "\0");
328358
const [value, status] = vm.guest.rbFuncallvProtect(recv, mid, args);
329-
checkStatusTag(status, vm, exporter);
359+
checkStatusTag(status, vm, privateObject);
330360
return value;
331361
};
332-
const evalRbCode = (vm: RubyVM, exporter: JsValueExporter, code: string) => {
362+
const evalRbCode = (vm: RubyVM, privateObject: RubyVMPrivate, code: string) => {
333363
const [value, status] = vm.guest.rbEvalStringProtect(code + "\0");
334-
checkStatusTag(status, vm, exporter);
335-
return new RbValue(value, vm, exporter);
364+
checkStatusTag(status, vm, privateObject);
365+
return new RbValue(value, vm, privateObject);
336366
};
337367

338368
/**

0 commit comments

Comments
 (0)