Sfink/Context and Compartments: Difference between revisions

From MozillaWiki
Jump to navigation Jump to search
(Created page with "See also http://www.christianwimmer.at/Publications/Wagner11a/Wagner11a.pdf == Contexts are Control, Compartments are Data == JSContexts are control, JSCompartments are data. ...")
 
(Replaced content with "=REDIRECT sfink/Contexts and Compartments")
 
Line 1: Line 1:
See also http://www.christianwimmer.at/Publications/Wagner11a/Wagner11a.pdf
=REDIRECT [[sfink/Contexts and Compartments]]
 
== Contexts are Control, Compartments are Data ==
 
JSContexts are control, JSCompartments are data.
 
A JSContext (from here on, 'context') represents the execution of JS code, so
it contains a JS stack and is associated with a thread. A JSCompartment
('compartment') is a memory space that objects and other garbage-collected
things (GCthings) are stored within.
 
A context is associated with ("is running inside") a single compartment
whenever it is doing anything. Any object created with that context will be
physically stored within its current compartment, and just about any GCthing it
reads or touches should also be within that compartment. To access data in
other compartments, the context must first "enter" that other compartment. This
is termed a "cross-compartment call" (remember, contexts are control). Usually,
the context will enter another compartment, do a bunch of stuff, and then exit
back to the original compartment -- though that's just because of how JS
control flow works. (There's a stack. When you make a function call, it'll
eventually finish and you'll return to what you were doing before the call.)
 
When a context is not running code (its JS stack is empty; it's not in a
request) then it isn't really associated with any compartment at all. In the
future, starting a request and entering an initial compartment will become the
same action. Also, a context is only ever running on one thread at a time.
 
In implementation terms, a context has a compartment field (cx->compartment)
that gives the current compartment, and a scope object (cx->globalObject) that
is one of the objects within the current compartment. In fact, cx->compartment
could considered a cached version of cx->globalObject->getCompartment().
Contexts also contain a "pending exception" object, which must also be in the
same compartment when set. New objects are created within the current
compartment, and their scope chains are initialized to cx->globalObject.
 
To make a cross-compartment call, cx->compartment is updated to the new
compartment and the scope object is set to a wrapper (really, a proxy) inside
the new compartment. The wrapper mediates access to the original scope object.
Also, a dummy frame that represents the compartment transition is pushed onto
the JS stack, because the security privileges of executing code are detemined
by the current stack. For example, if your chrome code calls a content script,
that script will execute with content privileges until it returns, then it will
revert to chrome privileges.
 
When debugging, it is helpful to know that a compartment is associated with a
"JSPrincipals" object that represents the "security information" for the
contents of that compartment. This is used to decide who can access what, and
is mostly opaque to the JS engine. But for Gecko, it'll typically contain a
human-understandable URL, which makes it much easier to figure out what's going
on.
 
Anything within a single compartment can freely access anything else in that
same compartment. No locking or wrappers are necessary (or possible). The
overall model is thus a partitioning of all (garbage collectible) data into
separate compartments, with controlled access from one compartment to another
but lockless, direct access between objects within a compartment.
Cross-compartment access is handled via "wrappers", which is the subject of the
next section.
 
== Wrappers ==
 
GCthings may be wrapped in cross-compartment wrappers for a number of reasons.
When a context is transitioning from one compartment to another (ie, it's
making a cross-compartment call), its scope object and pending exception (if
any) are changed to wrappers pointing back to the objects in the old
compartment. But any object can be wrapped in a cross-compartment wrapper if
needed. You can clone an object from another compartment, and all of its
properties will be wrappers pointing at the "real" properties in the origin
compartment.
 
Cross-compartment wrappers do not compose. When you wrap an object, any
existing wrappers will be ripped off first. (Slight oversimplification; there
is one exception.) In fact, the type of wrapper used for an object is uniquely
determined by the source and destination compartments.
 
The precise terminology is a little confusing. A cross-compartment wrapper is a
JSObject whose class is one of the proxy classes. When you access such an
object, it fetches its proxy handler (a subclass of JSProxyHandler) out of a
slot to decide how to handle that access. Confusingly, in the code a
JSCrossCompartmentWrapper is the subclass of JSProxyHandler that manages
cross-compartment access, but usually when we refer to a "cross-compartment
wrapper", we're really talking about the JSObject. (The JSObject of type
js::SomethingProxyClass that has a private JSSLOT_PROXY_HANDLER field
containing a JSProxyHandler subclass that knows how to mediate access to the
proxied object stored in JSSLOT_PROXY_PRIVATE. Phew.)
 
A proxy handler mediates access to the proxied objects based on a set of rules
embodied by some subclass of JSProxyHandler. A proxy handler might allow all
accesses through, conceal certain properties, or check on each access whether
the source compartment is allowed to see a particular property. Examples of
proxy handler classes are the things listed on
https://developer.mozilla.org/en/XPConnect_wrappers : cross-origin wrappers
(XOWs), chrome object wrappers (COWs), etc.
 
Also, the same wrapper will always be used for a given object. This is
necessary for equality testing between independently generated wrappings of the
same object, and useful for performance and memory usage as well. Internally,
every compartment has a wrapperCache that is keyed off of wrapped objects'
identity. You could think of the flavor of wrapper (i.e., the type of proxy
handler) being determined by the tuple <destination compartment, source
compartment, object>, but the object is stored within the source compartment so
those last two are redundant.
 
From the JS engine's point of view, there are a bunch of objects, every object
lives in a different compartment, and whenever you call something or point to
something in another compartment, the engine will interpose a cross-compartment
wrapper for you. It's up to the embedding -- the user of the JS engine -- to
decide how to divide up data into different compartments, and what the behavior
is triggered when you cross between compartments. You could have a "home"
compartment and a "bigger" compartment, and the cross-compartment wrapper could
convert any string to Pig Latin when it is retrieved from "bigger" by "home".
More practically, you could conceal certain properties from view when accessing
them from an "unprivileged" compartment (whatever that might mean in your
embedding), or you could do locking or queuing when accessing one compartment
from another compartment in a different thread. Or add a remoting layer.
 
XPConnect (Gecko's current SpiderMonkey embedding code) uses cross-compartment
wrappers to implement security policies and access rules. The 'Introduction'
section at https://developer.mozilla.org/en/XPConnect_security_membranes gives
a very good description of what XPConnect is using the wrappers for. Gecko uses
one compartment for chrome, and one compartment for each content domain. The
wrapper is chosen based on whether the two compartments are the same origin, or
whether one is privileged to see anything or a subset of the information in the
other, etc. See js/src/xpconnect/wrappers/WrapperFactory.cpp for the gruesome
details.
 
== Threads ==
 
A compartment can only be accessed by a single thread at a time, though it
doesn't have to be the same thread every time. Additionally, only one thread
may have stack frames running a given compartment's code at a time.
(Equivalently, "access compartment X" is defined as "currently executing or
potentially returning to a frame from compartment X").
 
A context is associated with a thread. (JSContext has a thread_ field with
thread-specific data.) Multiple contexts may be associated with the same
thread. A context is associated with a compartment whenever it is running code
(equivalently, when it is in a request). A context may move between
compartments, and have multiple compartments' code on its stack(s).
 
...to be finished...

Latest revision as of 23:22, 21 June 2011