[Harmony proxies] Non-configurable properties: fixed properties VS trap all with invariants enforcement

David Bruant david.bruant at labri.fr
Sun Jul 3 11:07:20 PDT 2011


Le 03/07/2011 14:59, Tom Van Cutsem a écrit :
> As a follow-up to the fixed properties proposal:
>
> I created a prototype implementation of fixed properties on top of the
> existing Proxy API. The idea is to wrap an existing proxy handler in a
> FixedHandler. This FixedHandler does the bookkeeping and checking to
> ensure that the wrapped handler doesn't misreport non-configurable
> properties.
>
> Full source code available here:
> <http://code.google.com/p/es-lab/source/browse/trunk/src/proxies/FixedHandler.js>
Just to make sure I do not misunderstand your work:
- Your code relies on current Proxy implementation behavior (FF4&5)
which doesn't do any check on the "configurable" attribute for
defineProperty and get{Own}PropertyDecsriptor traps
- The idea is that eventually, by default, any handler would become your
wrapped FixedHandler() handler (functionnally, of course; exact
implementation would be left at the discretion of implementors)

In your current FixedHandler implementation, all traps of the wrapped
handler are called for configurable properties. For non-configurable
properties, if already present in this.fixedProps, the "delete",
"hasOwn", "has", "get" and "set" traps aren't called while all others
are (first instruction of all other traps).
This might appear to be an inconsistent behavior to the user. An easy
fix could be to change a little bit these traps:
-----
  'delete': function(name) {
    var ret = this.targetHandler['delete'](name);
    // allows to throw with personalized message error rather than
default decided by the engine.

    if (name in this.fixedProps) {
      throw new TypeError(
        "property "+name+" is non-configurable and can't be deleted");
    }

    return ret;
  },

  hasOwn: function(name) {
    var ret = this.targetHandler.hasOwn ?
             this.targetHandler.hasOwn(name) : // call the trap in all
cases if present
             TrapDefaults.hasOwn.call(this.targetHandler, name);
    return name in this.fixedProps || ret; // always true if
non-configurable
  },

  has: function(name) {
    var ret = this.targetHandler.has ?
             this.targetHandler.has(name) : // call the trap in all
cases if present
             TrapDefaults.has.call(this.targetHandler, name);
    return name in this.fixedProps || ret; // always true if
non-configurable
  },

  get: function(rcvr, name) {
    var ret = this.targetHandler.get ?
             this.targetHandler.get(rcvr, name) :
             TrapDefaults.get.call(this.targetHandler, rcvr, name);
   
    var desc = Object.getOwnPropertyDescriptor(this.fixedProps, name);
    if (desc !== undefined) {
      if ('value' in desc) {
        return desc.value;
      } else {
        if (desc.get === undefined) { return undefined; }
        return desc.get.call(rcvr);
      }
    }
   
    return ret;
  },

  set: function(rcvr, name, val) {
    var ret = this.targetHandler.set ?
             this.targetHandler.set(rcvr, name, val) :
             TrapDefaults.set.call(this.targetHandler, rcvr, name, val);

    var desc = Object.getOwnPropertyDescriptor(this.fixedProps, name);
    if (desc !== undefined) {
      if ('writable' in desc) { // fixed data property
        if (desc.writable) {
          desc.value = val;
          this.defineProperty(name, desc);
          return true;
        } else {
          return false;
        }
      } else { // fixed accessor property
        if (desc.set) {
          desc.set.call(rcvr, val);
          return true;
        } else {
          return false;
        }
      }
    }
    // simulate missing derived trap fall-back behavior
    return ret;
  },
-----
This way, all traps are called, regardless of configurability and the
invariant-complying result is returned in all cases.
When it comes to return value, my implementation may surprise the
programmer as what he return from his (target)handler doesn't match what
is returned in the end. An alternative could be to test if "ret" matches
expectations (with harmony:egal, not === of course) and throw if it's
not the case.

Unless testing expectation matching, there is barely any overhead in
calling the traps (besides the trap themselves, but that's a cost the
programmer is ready to pay for configurable properties already) when
comparing with your implementation.
And it has the advantages of not leaking things the way I've showed it
at the beginning of this thread for the membrane use case.

Thanks for the follow-up and the implementation.

David


More information about the es-discuss mailing list