Source code
Revision control
Copy as Markdown
Other Tools
/*
* Any copyright is dedicated to the Public Domain.
*/
var count = 0;
function testCaller(obj) {
switch (++count) {
case 1:
case 2:
/*
* The first two times, obj is objA. The first time, we reference
* arguments.callee.caller before obj.go, so the caller getter must
* force the joined function object in the stack frame to cross the
* method read barrier. The second time, obj.go has been cloned and
* it should match the new frame's callee from the get-go.
*/
assertEq(obj, objA);
break;
case 3: {
assertEq(obj, objB);
/*
* Store another clone of the joined function object before obj.go has
* been read, but after it has been invoked via objB.go(objB).
*
* In this case, arguments.callee.caller must not lie and return what
* is currently stored in objB.go, since that function object (objA.go)
* was cloned earlier, when count was 1, and it is not the function
* object that was truly invoked.
*
* But since the invocation of objB.go(objB) did not clone go, and the
* following assignment overwrote the invoked value, leaving the only
* reference to the joined function object for go in the stack frame's
* callee (argv[-2]) member, the arguments.callee.caller reference must
* clone a function object for the callee, store it as the callee, and
* return it here.
*
* It won't equal obj.go, but (implementation detail) it should have
* the same proto as obj.go
*/
obj.go = objA.go;
let caller = arguments.callee.caller;
let obj_go = obj.go;
return caller != obj_go && caller.__proto__ == obj_go.__proto__;
}
case 4: {
assertEq(obj, objC);
let save = obj.go;
delete obj.go;
return arguments.callee.caller == save;
}
case 5: {
assertEq(obj, objD);
let read = obj.go;
break;
}
}
return arguments.callee.caller == obj.go;
}
function make() {
return {
go: function(obj) {
return testCaller(obj);
}
};
}
var objA = make(),
objB = make(),
objC = make(),
objD = make();
reportCompare(true, objA.go(objA), "1");
reportCompare(true, objA.go(objA), "2");
reportCompare(true, objB.go(objB), "3");
reportCompare(true, objC.go(objC), "4");
reportCompare(true, objD.go(objD), "5");