Thursday, January 7, 2010

The 'become' operator, advice, and JavaScript

I've been involved in a couple of security projects recently about adding advice to JavaScript (and the next one is ramping up!). I'm not the only one, and I've heard about uses for other contexts. What's interesting is, for most of these recent systems, the main pointcut is a reference to a function. In contrast, in almost all other aspect systems, pointcuts are based on types or some ambiguous notion of symbol name. There are compelling reasons to use references from a practical perspective (what else would a pointcut be?) and theoretical ones (e.g., type-based pointcuts, unless there's a special module system I haven't heard about, can be viewed as a source of ambient authority). But... is this new at a general level?

Gilad Bracha had a fun post about the become primitive in Smalltalk. "foo become bar" means anything that pointed to what foo points to now points to what bar pointed to and vice versa. The comments on the post are pretty good too.

Is `become' as powerful as reference-based around advice on functions? Yes:


function adviseAround (aFunc, advice) {
var swapMeIn = function () {
advice(swapMeIn, arguments);
};
aFunc become swapMeIn;
}
adviseAround(XMLHttpRequest, alert);


How about the other direction? Can we implement become (on functions) using adviseAround? Ignoring a lot of noise cases (exceptions, returns, etc.), and hoping I got the interleaving right:


function become(a, b) {
var goRaw = false;
function swap (base, advice) {
adviseAround(base, function (raw) {
if (goRaw) { raw(arguments); }
else { goRaw = true; advice(arguments); goRaw = false; }
});
}
swap(a, b);
swap(b, a);
}
become(XMLHttpRequest, alert);



At least for function objects, leaving the rest as an exercise, 'become' and 'around' are the same.

Ironically, Gilad comments "AOP. I have never made it a secret that I don't believe in that stuff." Given the above, that's an odd stance. However, he also writes "Security is another concern: containing this magic power to suspends the natural laws of the universe in a capability is crucial." Perhaps our restriction to references is what he means, distinguishing it from typical advice that can touch pretty much anything in principle. It's not an obvious approach -- for example, one of my co-workers decided to break function encapsulation due to his use cases -- but as we were interested in security, I thought it was key that we took the capability perspective.

Anyways, neato.

4 comments:

Gilad Bracha said...

Hi Leo,

This is most intriguing. What about ordinary (non-function) objects? Unless you can extend our result to them, than it seems that become: is more general - it can be used for any object.

Implementing advice in terms of become: is straightforward; the inverse is rather tricky (does that code actually work?). This again leads me to believe that become: is the better primitive to around.

Gilad Bracha said...

Shouldn't this be:

function become(a, b) {
var goRaw = false;
function swap (base, advice) {
adviseAround(base, function (raw) {
if (goRaw) {
goRaw = !goRaw;
raw(arguments);
goRaw = !goRaw;
}
else { goRaw = !goRaw;
advice(arguments);
goRaw = !goRaw;
}
});
}
swap(a, b);
swap(b, a);
}

Otherwise, it won't behave like become: for recursive or mutually recursive functions. But it's too late for me to think about this ...

lmeyerov said...

Right, I suspected that as well.

For extending it to objects, there is advice for interacting with fields of an object. Typically, languages support dynamically defined setters/getters for an object, etc., but missing (in JS, at least) is the ability to catch undefined field/method lookups, deletions, property lookups, etc.

To get 'become' for objects, we'd need all of those (swapping where they go, just as we did for function calls). You're right: it gets even hairier to extend 'become' to arbitrary values. However, getting the above individual pieces of functionality using just 'become' also seems tricky. Trickiness doesn't say too much about expressiveness, though.

In terms of usability... I'd want to rethink some use cases. Common security patterns, like checking arguments/context and proceeding, are easy in both. 'become' might be closer to a notion of whitelisting than blacklisting: you put in something you understand, rather than modify something you don't fully.

lmeyerov said...

As for testing... my old modified interpreter is locked away in Microsoft land. After some layout engine hacking this week, I want to modify Narcissus for browser-based demo of JS + secure views to make it easy for people to play with these ideas.

(a view is a context-sensitive aspect, and by secure view, I mean specializing context to be script origin and using a default-deny aspect for all views, separating the notion of passing a reference from enabling a capability)