[Vm-dev] VM handle management
Andreas Raab
andreas.raab at gmx.de
Wed Mar 24 03:10:18 UTC 2010
Folks -
[Warning: Long post. Please try to stay on topic and don't get into any
SmartSyntaxInterpreterPlugin / FFI syntax digressions that always come
up with these issues. This is a practical proposal that I'd like to
implement].
For the third time this month I ran into the situation that I was
writing a plugin that would create an internal data structure
(containing pointers etc) and where I wanted to return a handle to this
structure and pass that back to the VM.
Since I'm a lazy bastard, what I usually do is something like this in
the support code:
int sqNewFooBar(void) {
/* creates a new foobar and returns a handle */
FooBar *newFoo = malloc(1, sizeof(FooBar));
/* return a handle, sort of */
return (int) newFoo;
}
void sqDeleteFooBar(int handle) {
FooBar *theFoo = (FooBar*) handle;
free(handle);
}
I don't have to tell you how horrible this is and what all the problems
with the approach are - I'm sure you're all well aware of it. The
question is, how do we fix this?
Here's my proposal. I say let's extend the VM with 4 new proxy
functions, namely:
/* Registers a pointer and returns a handle.
The type argument ensures that the handle cannot be forged. */
OOP registerHandleType(void *ptr, char *type);
/* Unregister a handle */
void unregisterHandle(void *ptr);
/* Look up the pointer for a given handle/pair type */
void* lookupHandleType(OOP handle, char *type);
/* Ditto, just based on a stack index */
void* stackHandleValueType(int stackIndex, char *type);
With these functions the support code can *always* deal with pointers,
never with OOPs. The glue code does the translation between OOP and
pointer. As a consequence the above support code becomes:
FooBar *sqNewFooBar(void) {
/* creates a new foobar and returns it */
FooBar *newFoo = malloc(1, sizeof(FooBar));
return newFoo;
}
void sqDeleteFooBar(FooBar *theFoo) {
/* deletes the FooBar */
free(theFoo);
}
and the glue code in the plugin becomes:
primitiveNewFooBar
"Creates a new foobar"
| foobar |
<var: #foobar type: 'FooBar*'>
foobar := self sqNewFooBar.
handle := interpreterProxy registerHandle: foobar type: 'FooBar';
interpreterProxy pop: interpreterProxy methodArgumentCount+1
thenPush: handle.
primitiveDestroyFooBar
"Destroys a foobar"
| foobar |
<var: #foobar type: 'FooBar*'>
foobar := interpreterProxy lookupHandle: (interpreterProxy
stackValue: 0) type: 'FooBar'.
"or simply:
foobar = interpreterProxy stackHandleValue: 0 type: 'FooBar'
"
foobar ifNotNil:[
interpreterProxy unregisterHandle: foobar.
self sqDestroy: foobar.
].
interpreterProxy pop: interpreterProxy methodArgumentCount.
What is interesting is that handles are completely opaque structures.
They could be integers, byte arrays, whatever representation we choose,
the only requirement is that the VM has a handleTable that can look up
these pointers.
The obvious advantages of such a scheme are:
* It's safe. We don't expose a pointer but rather a handle that the VM
knows about and that does not expose any dangerous internals.
* It's secure. By passing the additional "type" argument you can ensure
that only handles registered as that type are used. I.e., no passing a
SocketPtr into the FilePlugin.
* It's simple. I've written at least three handle management
implementations and I'm getting sick and tired of it.
* It allows sharing. For example, OSProcessPlugin likes to do manipulate
and register / deregister file handles, and can now safely look up the
internal structure from the handle. (there are some issues here but they
are no worse than what OSProcess already does)
If you agree, I'd like to implement this scheme and rewrite SocketPlugin
and FilePlugin as examples for how to use such handles. They are already
places that expose a ton of pretty risky information and it'd be a good
example for how to utilize handles.
Cheers,
- Andreas
More information about the Vm-dev
mailing list