Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8148924

Inconsistent "this" context in JSAdapter adaptee function calls

XMLWordPrintable

    • b143
    • x86_64
    • windows_7

      FULL PRODUCT VERSION :
      java version "1.8.0_71"
      Java(TM) SE Runtime Environment (build 1.8.0_71-b15)
      Java HotSpot(TM) 64-Bit Server VM (build 25.71-b15, mixed mode)

      ADDITIONAL OS VERSION INFORMATION :
      Microsoft Windows [Version 6.1.7601]

      A DESCRIPTION OF THE PROBLEM :
      The meaning of the "this" reference during calls to JSAdapter adaptee hook functions is inconsistent. Some hook functions have access to "this" as defined by the overrides object while others do not.
      As far as I can see it isn't defined / documented anywhere what the expected scope of any call to the hook functions is supposed to be. Developers typically expect the context to be the instance the functions are called upon, which would be the JSAdapter including any properties of the overrides object are supposed to be copied unto the JSAdapter instances as per JavaDoc:

      "JSAdapter constructor can optionally receive an "overrides" object. Properties of overrides object is copied to JSAdapter instance."

      STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
      - Create a JSAdapter instance by providing an overrides and adaptee object. The overrides object should define at least one property that is to be accessed in adaptee hook functions.
      - Invoke operations on the JSAdapter instance to trigger the various hook functions

      1. Start jjs
      2. Load script file included in "source code for test case"
      3. Get a new JSAdapter instance by running "var proxy = createTestProxy();"
      4. Execute the following statements to test the hook functions
      4.a. print(proxy.prop);
      4.b. proxy.prop = 'differentValue';
      4.c. print(proxy);
      4.d. print('prop' in proxy);
      4.e. var props = []; for(var prop in proxy) { props.push(prop);} print(Array.prototype.join.call(props, ','));
      4.f. for each(var value in proxy) { print(value); }
      4.g. Object.isExtensible(proxy);
      4.h Object.isSealed(proxy);
      4.i. Object.isFrozen(proxy);
      4.j. Object.freeze(proxy);
      4.k. Object.seal(proxy);
      4.l. Object.preventExtensions(proxy);
      4.m. delete proxy.prop;

      EXPECTED VERSUS ACTUAL BEHAVIOR :
      EXPECTED -
      Statements listed in steps 4.a. through 4.m. successfully run and - in addition to implicit return values and explicit prints - log out paired messages in the form of:

      __<hookName>__ has access to this.__proxiedObject: true
      __<hookName>__ has access to this.__proxiedObject.prop: true
      ACTUAL -
      Only the statements in steps 4.a, b and c succeed and print the expected log messages.

      All other statements result in pairs of messages in the form of:

      __<hookName>__ has access to this.__proxiedObject: false
      <script>:<line> TypeError: Cannot read property "prop" from undefined

      REPRODUCIBILITY :
      This bug can be reproduced always.

      ---------- BEGIN SOURCE ----------
      // JSAdapter-test.js
      function createTestProxy()
      {
          var proto, overrides, adaptee, proxy;

          proto = {
              prop : 'propValue',
              toString : function()
              {
                  return 'proxiedObject - prop=' + this.prop;
              }
          };

          // overrides allow clients to access actual object of proxy
          overrides = {
              __proxiedObject : proto,
              __proxiedObjectCtor : function dummyCtor()
              {
              }
          };

          overrides.__proxiedObjectCtor.prototype = proto;

          adaptee = {
              __get__ : function(name)
              {
                  print('__get__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__get__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));
                  return this.__proxiedObject[name];
              },

              __put__ : function(name, value)
              {
                  print('__put__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__put__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  this.__proxiedObject[name] = value;
              },

              __has__ : function(name)
              {
                  print('__has__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__has__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  return name in this.__proxiedObject[name];
              },

              __delete__ : function(name)
              {
                  print('__delete__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__delete__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  delete this.__proxiedObject[name];
              },

              __call__ : function(name)
              {
                  print('__call__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__call__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  return this.__proxiedObject[name].call(this.__proxiedObject, Array.prototype.slice(arguments));
              },

              __new__ : function()
              {
                  var BoundCtor, proto, overrides;

                  print('__new__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__new__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  BoundCtor = Function.prototype.bind.apply(this.__proxiedObjectCtor, [ undefined ].concat(Array.prototype.slice(arguments, 0)));
                  proto = new BoundCtor();

                  overrides = {
                      __proxiedObject : proto,
                      __proxiedObjectCtor : function dummyCtor()
                      {
                      }
                  };

                  overrides.__proxiedObjectCtor.prototype = proto;

                  return new JSAdapter(proto, overrides, adaptee);
              },

              __getIds__ : function()
              {
                  var ids = [], id;

                  print('__getIds__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__getIds__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  for (id in this.__proxiedObject)
                  {
                      ids.push(id);
                  }
                  return ids;
              },
              
              __getValues__ : function()
              {
                  var values = [], id;

                  print('__getValues__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__getValues__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  for (id in this.__proxiedObject)
                  {
                      values.push(this.__proxiedObject[id]);
                  }
                  return values;
              },

              __preventExtensions__ : function()
              {
                  print('__preventExtensions__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__preventExtensions__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));
              },

              __freeze__ : function()
              {
                  print('__freeze__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__freeze__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));
              },

              __isFrozen__ : function()
              {
                  print('__isFrozen__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__isFrozen__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  return false;
              },

              __seal__ : function()
              {
                  print('__seal__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__seal__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));
              },

              __isSealed__ : function()
              {
                  print('__isSealed__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__isSealed__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  return false;
              },

              __isExtensible__ : function()
              {
                  print('__isExtensible__ has access to this.__proxiedObject: ' + (this.__proxiedObject !== undefined));
                  print('__isExtensible__ has access to this.__proxiedObject.prop: ' + (this.__proxiedObject.prop !== undefined));

                  return true;
              }
          };

          proxy = new JSAdapter(proto, overrides, adaptee);
          return proxy;
      };
      ---------- END SOURCE ----------

      CUSTOMER SUBMITTED WORKAROUND :
      JSAdapter instances that use closures as adaptee hook functions can access any proxied object from the parent scope of the hook functions without relying on a defined "this" context.

      This workaround restricts the ability of developers to reuse adaptee objects and hook functions, leading to code bloat / redundancy and potential performance drawbacks (less optimization of hook function code due to less frequent execution of individual non-shared code; more script classes putting more pressure on script class cache).

            hannesw Hannes Wallnoefer
            webbuggrp Webbug Group
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

              Created:
              Updated:
              Resolved: