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