/*
 * Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/licenses/publicdomain/
 */

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");
