On Wed, Dec 4, 2013 at 5:59 PM, Mark Miller <erig...@gmail.com> wrote:
> Good, I think we're converging on what the important sub-problem is:
> building an abstraction mechanism that enables the open-ended expression of
> projections, but not the expression of non-projections. My previous
> statements about join were premised on such a projection-only abstraction.
> But I mistakenly thought that we could build it by adapting the E and Joe-E
> auditor logic. Sam is right that we can't. Some of Cormac's work on
> trace-monitoring temporal contracts might be the answer, or at least a place
> to start.
>
> Once we have such an open-ended projection-only abstraction, then all these
> questions about equality become interesting and pressing.

I'm unconvinced that this is genuinely a problem that needs solving.
In particular, I don't think that given such a projection-only
abstraction, any of the questions about equality are easier.

For example, if we had one, that wouldn't reduce our desire to have a
pointer-equality predicate for performance, given which none of the
invariants about programs that you think you have in the presence of
projection-only abstractions are actually invariants.

Additionally, I'm still unconvinced that "is a projection" is a useful
heuristic for "I should be willing to trust this unknown code".  In
particular, termination and resources are both real concerns.  When
the question is "should I synchronously invoke this unknown untrusted
code in my heap and address space", I'd want a lot stronger guarantee
than "is a projection".

Third, the ability to handle failure is a genuine semantic problem.

Finally, if the expression of non-projections in proxies that produce
true when compared to the underlying object is a problem, then there
ought to be some some program that demonstrates this problem; some
attack that can be demonstrated. But I don't think we've seen one yet
in this discussion.

Sam

>
>
> On Wed, Dec 4, 2013 at 1:55 PM, Peter Thiemann
> <thiem...@informatik.uni-freiburg.de> wrote:
>>
>> On 12/4/13 5:54 PM, Mark Miller wrote:
>>>
>>>
>>>     Consider
>>>     var p = new Proxy ( target, handler);
>>>     The trick is *not* to trap equality, but to let the handler
>>>     control whether EQ dereferences p to target before comparing
>>>     pointers. Notice that one handler only controls one step of
>>>     dereferencing. If target is a proxy, again, then EQ recursively
>>>     asks that proxy's handler until either a non-proxy object is
>>>     reached *or* the handler does not request further forwarding.
>>>     Here is an outline of the proposal:
>>>     http://proglang.informatik.uni-freiburg.de/projects/JSProxy/proxy.pdf
>>>
>>>     With this design each individual proxy can choose to be
>>>     transparent or opaque, while guaranteeing that EQ implements a
>>>     non-trivial equivalence relation - the latter is straightforward
>>>     to check.
>>>
>>>
>>> Ok good, I see how this provides a meaningful equivalence relation. But I
>>> don't see how it helps your goal.
>>>
>>> a) In order to do a membrane of any sort, including a contract-membrane,
>>> you need to use the shadow target technique, where the system-recognized
>>> target for master and slave in your example are distinct shadow targets. And
>>> where the shared real target is not system recognized, but is only our way
>>> of describing what the handlers are doing; and thus not available to any
>>> system enforcement mechanism.
>>
>>
>> I don't think that all contracts require a membrane, for example, if you
>> just restrict an object to reading or writing a subset of its properties,
>> then no recursive wrapping is required.
>> I don't understand "shadow target technique", but if the master/slave
>> roles are contracts like Role1 and Role2, then each of them can be
>> represented by a proxy and the two proxies can share a single target.
>>
>>
>>>
>>> You write "If x===y, then uses of x and y *in their common interface*
>>> (i.e., in the intersection of their roles) are interchangeable."
>>>
>>> b) But this is untrue in your proposal, even if the same
>>> system-recognized target were somehow used, because the different handlers
>>> can perform different side effects, including differing side effects on the
>>> shared target.
>>
>>
>> True. I was stuck in the contract world, where the handler implements a
>> projection.
>> So I should tune down my proposal to contract wrappers where contracts are
>> implemented in a suitable DSL that only expresses projections.
>>
>>
>>>
>>>
>>> Let's distinguish two categories of membrane:
>>>
>>> x) A general membrane maintains the basic membrane invariant, that there
>>> is no leakage across the membrane boundary. But invocations that cross the
>>> membrane can have their meaning changed by the handlers in any way allowed
>>> by the JS Proxy invariant enforcement mechanism.
>>>
>>> y) A guarding membrane is one which either disallows an operation, or
>>> (identity aside) allows it with the same semantics it would have had in the
>>> absence of the membrane's interposition.
>>>
>>> Given checkably-pure functions (which E and Joe-E have but JS currently
>>> does not) one can use the general membrane mechanism to build a membrane
>>> maker to be parameterized by checkably-pure guard predicates, so that it
>>> only makes guarding membranes. What you (and IIUC Phil and Jeremy) are
>>> generally seeking are guarding membranes, which would answer issue #b. But
>>> in JS (as of ES6) these would be implemented by unprivileged user code, and
>>> so not something that the === implementation could directly recognize. A new
>>> equivalence operation, whether an EQ-like equivalence predicate or a join,
>>> could be provided as part of the same package and be in bed with this
>>> guarding membrane maker.
>>>
>>> Note that the guarding membranes made by this maker would still need to
>>> use the shadow target technique, so objection #a still stands -- testing
>>> "the same system-recognized proxy target" will not help you build these new
>>> equivalence operations as part of the guarding membrane maker package.
>>
>>
>> My understanding of the above is that given an appropriate way of
>> restricting a handler to essentially implement a projection, then the proxy
>> of that handler could safely be transparent while granting an equivalence
>> restricted to uses in the common interface.
>> Well, such a handler DSL could be designed and then we could all have our
>> cake and eat it.
>>
>>
>> -Peter
>>
>>
>>>
>>>
>>>
>>>
>>>         In any case, weakening EQ so that the only guarantee that
>>>         remains is that it is an equivalence relation would break
>>>         existing uses of EQ in JS (i.e., "===" aside from NaN). For
>>>         example, an EQ that always said true would still be an
>>>         equivalence relation, so clearly something more is needed. The
>>>         position Tom and I take is that, aside from -0.0, JS ===
>>>         guarantees full observational equivalence, i.e., for any
>>>         program containing the fragment
>>>
>>>         if (x === y) {
>>>         .... x ....
>>>         }
>>>
>>>         if all or any or all of the mentions of x in the body were
>>>         changed to y, the meaning of the program would not change.
>>>         (Also, given the usual simplifying assumptions about renaming,
>>>         non-shadowing, and alpha equivalence.)
>>>
>>>         If the guarantees your === would provide is weaker than that,
>>>         what are its guarantees, and why do you believe those to be
>>>         sufficient?
>>>
>>>
>>>     Observational equivalence is hard to argue against, but I’ll give
>>>     it a try, anyway. I believe such a strong spec is too limiting and
>>>     excludes many useful applications. Examples that I am concerned
>>>     about are wrappers for transparent monitoring and enforcing
>>>     contracts. Apparently and unsurprisingly others are concerned
>>>     about these examples, too.
>>>
>>>
>>>     Here is an example scenario. Then, I provide some code fragments
>>>     to support a proposal for a weaker spec for ===. This leads to a
>>>     question about join in the end.
>>>
>>>     ~~~~~~
>>>
>>>     Consider a function sync(master,slave) that takes two objects and
>>>     that wants these two objects to exchange information: the master
>>>     syncing with the slave. For efficiency, the function sync first
>>>     checks if these two objects are “the same”, in which case no
>>>     synchronization is needed; perhaps a flag or timestamp needs to be
>>>     set to indicate that synchronization has been achieved. If the
>>>     objects are different, then some potentially expensive sequence of
>>>     method calls may be needed to exchange the information. (Perhaps
>>>     involving authentication, passing of capabilities, etc.)
>>>
>>>
>>>     If we put a contract on sync that imposes a Master interface on
>>>     master and a Slave interface on slave and this contract is
>>>     implemented using opaque proxies, then the sync function is losing
>>>     the efficient shortcut.
>>>
>>>
>>>     (Why would an object implement the Master and the Slave interface?
>>>     Here are two possible reasons: the master may be elected at run
>>>     time or there may be a hierarchical system where level-n masters
>>>     are slaves on level-(n+1))
>>>
>>>     ~~~~~~
>>>
>>>     A proposal for a weaker spec for ===
>>>
>>>
>>>     In the following code fragments, I am using Java interface
>>>     notation to indicate a dynamically checked contract for brevity.
>>>
>>>     Let’s say we have a function that can treat “same” objects more
>>>     efficiently, as motivated in the sync example:
>>>
>>>
>>>     function f (x, y) {
>>>
>>>     ...
>>>
>>>     if (x === y) {
>>>
>>>     … x.foo(); x.inc(); y.inc(); z = y.bar() …
>>>
>>>     }
>>>
>>>     …
>>>
>>>     }
>>>
>>>
>>>     Using contracts, x and y may be restricted to certain roles.
>>>
>>>     Let’s say,
>>>
>>>     interface Role1 {
>>>
>>>     foo();
>>>
>>>     inc();
>>>
>>>     }
>>>
>>>     interface Role2 {
>>>
>>>     bar();
>>>
>>>     inc();
>>>
>>>     }
>>>
>>>
>>>     var g = … apply contract( (Role1, Role2) -> Any ) to f;
>>>
>>>
>>>     var obj = {
>>>
>>>     state: 42;
>>>
>>>     foo: function () { this.state = 0; };
>>>
>>>     bar: function () { return this.state;};
>>>
>>>     inc: function () { this.state++; };
>>>
>>>     }
>>>
>>>
>>>     g (obj, obj);
>>>
>>>
>>>     I want to argue that this call of g should execute the code
>>>     guarded by (x === y) in f, because the interposition of the
>>>     contract may otherwise change the semantics. In other words, I
>>>     want to make sure that g(obj, obj) either raises an error due to a
>>>     contract violation or it has exactly the same effect and result as
>>>     f(obj, obj).
>>>
>>>
>>>     What guarantees can we assume below (x===y)?
>>>
>>>     If x===y, then uses of x and y *in their common interface* (i.e.,
>>>     in the intersection of their roles) are interchangeable.
>>>
>>>
>>>     In the example, the common interface is
>>>
>>>     interface Role1&Role2 {
>>>
>>>     inc();
>>>
>>>     }
>>>
>>>     therefore
>>>
>>>     if (x === y) { … x.foo(); x.inc(); y.inc(); z = y.bar() … }
>>>
>>>     must be observationally equivalent to each of the following
>>>
>>>     if (x === y) { … x.foo(); x.inc(); x.inc(); z = y.bar() … }
>>>
>>>     if (x === y) { … x.foo(); y.inc(); y.inc(); z = y.bar() … }
>>>
>>>     if (x === y) { … x.foo(); y.inc(); x.inc(); z = y.bar() … }
>>>
>>>
>>>     That is, sameness of x and y is reduced to the ability to observe
>>>     the same state changes through both x and y. Of course, limited to
>>>     the common interface.
>>>
>>>
>>>     The extreme case for the above f-example would arise if the
>>>     interfaces Role1 and Role2 were disjoint. In this case, no
>>>     transformation would be sanctioned from the knowledge that x === y.
>>>
>>>     ~~~~~~
>>>
>>>     This observation leads me to a question about join:
>>>
>>>     If only join is provided, but not EQ, then I don’t see how to
>>>     implement the function f above. If I successfully obtain z =
>>>     join(x, y), then it fulfills interface Role1&Role2, but I cannot
>>>     use it in lieu of x or y in the body of the conditional because it
>>>     does not implement foo() and bar().
>>>
>>>     If Role1&Role2 were empty, then the successful join would produce
>>>     a useless object. For example, in the Master/Slave example above,
>>>     it is highly likely that the interfaces are disjoint.
>>>
>>>
>>>
>>> Given a synchronous join:
>>>
>>>   if (join(master, slave)) {
>>>     ... use master for mastery stuff, use slave for slavy stuff ...
>>>   } else {
>>>     ... do the more expensive thing ...
>>>   }
>>>
>>> Given the actual async join:
>>>
>>>   Q(join(master, slave)).then(() => {
>>>     ... use master for mastery stuff, use slave for slavy stuff ...
>>>   }, () => {
>>>     ... do the more expensive thing ...
>>>   });
>>>
>>> We're here using join only to test whether it succeeds, not to actually
>>> use its result. Admittedly this is kludgy, but IMO no more kludgy than the
>>> problem statement demands. This would certainly not be the typical case for
>>> using join.
>>>
>>>
>>>
>>>
>>>         --         Cheers,
>>>         --MarkM
>>>
>>>
>>>     -Peter
>>>
>>>
>>>
>>>
>>>
>>> --
>>> Text by me above is hereby placed in the public domain
>>>
>>>   Cheers,
>>>   --MarkM
>>
>>
>
>
>
> --
> Text by me above is hereby placed in the public domain
>
>   Cheers,
>   --MarkM
_______________________________________________
dev-tech-js-engine-internals mailing list
dev-tech-js-engine-internals@lists.mozilla.org
https://lists.mozilla.org/listinfo/dev-tech-js-engine-internals

Reply via email to