mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
142 lines
3.4 KiB
142 lines
3.4 KiB
/*
|
|
* Tailcall bug when tailcalling Duktape.Thread.resume(). Reported by
|
|
* Andreas Öman.
|
|
*
|
|
* What probably happens is:
|
|
*
|
|
* - The compiler compiles 'return Duktape.Thread.resume(t)', and detects
|
|
* a tail call opportunity when compiling the return statement.
|
|
*
|
|
* - The compiler adds a 'tailcall' flag to a previously emitted DUK_OP_JUMP
|
|
* instruction. Because a tailcall was emitted, the compiler does not
|
|
* emit a return statement.
|
|
*
|
|
* - When executing, the executor notices that the target function is native,
|
|
* so the requested tailcall is not possible, so a normal recursive call
|
|
* is made.
|
|
*
|
|
* - Normally, once the recursive call returns, the executor simulates a
|
|
* return statement (as if a DUK_OP_RETURN was present). This makes the
|
|
* behavior correct for normal functions.
|
|
*
|
|
* - However, Duktape.Thread.yield() or Duktape.Thread.resume() are
|
|
* different: they manipulate the execution state (changing the current
|
|
* thread) and longjmp to resume execution.
|
|
*
|
|
* - Because of the longjmp, the simulated DUK_OP_RETURN is not executed
|
|
* when the resumed thread returns. Instead, execution continues after
|
|
* the opcode following the tailcall. In the test below, this causes
|
|
* Duktape 0.10.0 to print "never here".
|
|
*/
|
|
|
|
/*===
|
|
retval
|
|
retval
|
|
123
|
|
234
|
|
TypeError
|
|
123
|
|
234
|
|
TypeError
|
|
===*/
|
|
|
|
function test1() {
|
|
var t = new Duktape.Thread(function () {
|
|
return "retval";
|
|
});
|
|
|
|
// base case: no tail call
|
|
var res = Duktape.Thread.resume(t);
|
|
return res;
|
|
|
|
print('never here');
|
|
}
|
|
|
|
function test2() {
|
|
var t = new Duktape.Thread(function () {
|
|
return "retval";
|
|
});
|
|
|
|
// tail call, should have same result but fails
|
|
return Duktape.Thread.resume(t);
|
|
|
|
// the bug causes the 'return' to be skipped, so this statement
|
|
// incorrectly executes too
|
|
print('never here');
|
|
}
|
|
|
|
function test3() {
|
|
var t = new Duktape.Thread(function () {
|
|
// Base case: no tailcall
|
|
var tmp = Duktape.Thread.yield(123);
|
|
return tmp;
|
|
print('never here');
|
|
return 999;
|
|
});
|
|
|
|
// Thread should yield 123.
|
|
var res = Duktape.Thread.resume(t);
|
|
print(res);
|
|
|
|
// Thread should be resumed with 234 which becomes the return value of
|
|
// the Duktape.Thread.yield() and should be returned back here.
|
|
res = Duktape.Thread.resume(t, 234);
|
|
print(res);
|
|
|
|
// Thread is finished, so attempt to resume is an error
|
|
try {
|
|
res = Duktape.Thread.resume(t, 345);
|
|
print(res);
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
}
|
|
|
|
function test4() {
|
|
var t = new Duktape.Thread(function () {
|
|
// Tailcall breaks here again.
|
|
return Duktape.Thread.yield(123);
|
|
print('never here');
|
|
return 999;
|
|
});
|
|
|
|
var res = Duktape.Thread.resume(t);
|
|
print(res);
|
|
|
|
// Because of the tailcall bug, this breaks: the 'return' of the yield
|
|
// is skipped. So print('never here') gets executed and 999 is returned.
|
|
res = Duktape.Thread.resume(t, 234);
|
|
print(res);
|
|
|
|
// TypeError because thread is finished.
|
|
try {
|
|
res = Duktape.Thread.resume(t, 345);
|
|
print(res);
|
|
} catch (e) {
|
|
print(e.name);
|
|
}
|
|
}
|
|
|
|
try {
|
|
print(test1());
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
try {
|
|
print(test2());
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
try {
|
|
test3();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|
|
try {
|
|
test4();
|
|
} catch (e) {
|
|
print(e);
|
|
}
|
|
|