Mercurial > scratch
comparison pydershell/pydershell.py @ 19:057260102960
Made wrapping of python objects much nicer.
author | Atul Varma <varmaa@toolness.com> |
---|---|
date | Mon, 07 Sep 2009 12:57:20 -0700 |
parents | 69e5523ebdc6 |
children | 8dd18f864351 |
comparison
equal
deleted
inserted
replaced
18:69e5523ebdc6 | 19:057260102960 |
---|---|
69 class SafeJsObjectWrapper(object): | 69 class SafeJsObjectWrapper(object): |
70 """ | 70 """ |
71 Securely wraps a JS object to behave like any normal Python object. | 71 Securely wraps a JS object to behave like any normal Python object. |
72 """ | 72 """ |
73 | 73 |
74 __slots__ = ['_jsobject', '_cx', '_this'] | 74 __slots__ = ['_jsobject', '_sandbox', '_this'] |
75 | 75 |
76 def __init__(self, cx, jsobject, this): | 76 def __init__(self, sandbox, jsobject, this): |
77 if not isinstance(jsobject, pydermonkey.Object): | 77 if not isinstance(jsobject, pydermonkey.Object): |
78 raise TypeError("Cannot wrap '%s' object" % | 78 raise TypeError("Cannot wrap '%s' object" % |
79 type(jsobject).__name__) | 79 type(jsobject).__name__) |
80 object.__setattr__(self, '_sandbox', sandbox) | |
80 object.__setattr__(self, '_jsobject', jsobject) | 81 object.__setattr__(self, '_jsobject', jsobject) |
81 object.__setattr__(self, '_cx', cx) | |
82 object.__setattr__(self, '_this', this) | 82 object.__setattr__(self, '_this', this) |
83 | 83 |
84 @staticmethod | 84 @property |
85 def wrap(cx, jsvalue, this): | 85 def wrapped_jsobject(self): |
86 if isinstance(jsvalue, pydermonkey.Function): | 86 return self._jsobject |
87 return SafeJsFunctionWrapper(cx, jsvalue, this) | |
88 elif isinstance(jsvalue, pydermonkey.Object): | |
89 return SafeJsObjectWrapper(cx, jsvalue, this) | |
90 else: | |
91 # It's a primitive value. | |
92 return jsvalue | |
93 | 87 |
94 def _wrap_to_python(self, jsvalue): | 88 def _wrap_to_python(self, jsvalue): |
95 return self.wrap(self._cx, jsvalue, self._jsobject) | 89 return self._sandbox.wrap_jsobject(jsvalue, self._jsobject) |
96 | 90 |
97 def _wrap_to_js(self, value): | 91 def _wrap_to_js(self, value): |
98 # TODO: Add support for wrapping non-primitive python objects. | 92 return self._sandbox.wrap_pyobject(value) |
99 return value | |
100 | 93 |
101 def __eq__(self, other): | 94 def __eq__(self, other): |
102 if isinstance(other, SafeJsObjectWrapper): | 95 if isinstance(other, SafeJsObjectWrapper): |
103 return self._jsobject == other._jsobject | 96 return self._jsobject == other._jsobject |
104 else: | 97 else: |
112 | 105 |
113 def __setitem__(self, item, value): | 106 def __setitem__(self, item, value): |
114 self.__setattr__(item, value) | 107 self.__setattr__(item, value) |
115 | 108 |
116 def __setattr__(self, name, value): | 109 def __setattr__(self, name, value): |
117 cx = self._cx | 110 cx = self._sandbox.cx |
118 jsobject = self._jsobject | 111 jsobject = self._jsobject |
119 | 112 |
120 cx.define_property(jsobject, name, | 113 cx.define_property(jsobject, name, |
121 self._wrap_to_js(value)) | 114 self._wrap_to_js(value)) |
122 | 115 |
123 def __getitem__(self, item): | 116 def __getitem__(self, item): |
124 return self.__getattr__(item) | 117 return self.__getattr__(item) |
125 | 118 |
126 def __getattr__(self, name): | 119 def __getattr__(self, name): |
127 cx = self._cx | 120 cx = self._sandbox.cx |
128 jsobject = self._jsobject | 121 jsobject = self._jsobject |
129 | 122 |
130 return self._wrap_to_python(cx.get_property(jsobject, name)) | 123 return self._wrap_to_python(cx.get_property(jsobject, name)) |
131 | 124 |
132 def __contains__(self, item): | 125 def __contains__(self, item): |
133 cx = self._cx | 126 cx = self._sandbox.cx |
134 jsobject = self._jsobject | 127 jsobject = self._jsobject |
135 | 128 |
136 return cx.has_property(jsobject, item) | 129 return cx.has_property(jsobject, item) |
137 | 130 |
138 def __iter__(self): | 131 def __iter__(self): |
139 cx = self._cx | 132 cx = self._sandbox.cx |
140 jsobject = self._jsobject | 133 jsobject = self._jsobject |
141 | 134 |
142 properties = cx.enumerate(jsobject) | 135 properties = cx.enumerate(jsobject) |
143 for property in properties: | 136 for property in properties: |
144 yield property | 137 yield property |
146 class SafeJsFunctionWrapper(SafeJsObjectWrapper): | 139 class SafeJsFunctionWrapper(SafeJsObjectWrapper): |
147 """ | 140 """ |
148 Securely wraps a JS function to behave like any normal Python object. | 141 Securely wraps a JS function to behave like any normal Python object. |
149 """ | 142 """ |
150 | 143 |
151 def __init__(self, cx, jsfunction, this): | 144 def __init__(self, sandbox, jsfunction, this): |
152 if not isinstance(jsfunction, pydermonkey.Function): | 145 if not isinstance(jsfunction, pydermonkey.Function): |
153 raise TypeError("Cannot wrap '%s' object" % | 146 raise TypeError("Cannot wrap '%s' object" % |
154 type(jsobject).__name__) | 147 type(jsobject).__name__) |
155 SafeJsObjectWrapper.__init__(self, cx, jsfunction, this) | 148 SafeJsObjectWrapper.__init__(self, sandbox, jsfunction, this) |
156 | 149 |
157 def __call__(self, *args): | 150 def __call__(self, *args): |
158 cx = self._cx | 151 cx = self._sandbox.cx |
159 jsobject = self._jsobject | 152 jsobject = self._jsobject |
160 this = self._this | 153 this = self._this |
161 | 154 |
162 obj = cx.call_function(this, jsobject, | 155 arglist = [] |
163 self._wrap_to_js(args)) | 156 for arg in args: |
157 arglist.append(self._wrap_to_js(arg)) | |
158 | |
159 obj = cx.call_function(this, jsobject, tuple(arglist)) | |
164 return self._wrap_to_python(obj) | 160 return self._wrap_to_python(obj) |
165 | |
166 def safejsfunc(cx, on_obj, name=None): | |
167 """ | |
168 Exposes the decorated Python function on the given JS object. | |
169 | |
170 Any unexpected exceptions raised by the function will be | |
171 re-raised as InternalError exceptions. | |
172 """ | |
173 | |
174 def make_wrapper(func): | |
175 if name is None: | |
176 func_name = func.__name__ | |
177 else: | |
178 func_name = name | |
179 def wrapper(func_cx, this, args): | |
180 try: | |
181 return func(func_cx, this, args) | |
182 except pydermonkey.error: | |
183 raise | |
184 except Exception: | |
185 raise InternalError() | |
186 cx.define_property( | |
187 on_obj, | |
188 func_name, | |
189 cx.new_function(wrapper, func_name) | |
190 ) | |
191 return func | |
192 return make_wrapper | |
193 | 161 |
194 def format_stack(js_stack): | 162 def format_stack(js_stack): |
195 """ | 163 """ |
196 Returns a formatted Python-esque stack traceback of the given | 164 Returns a formatted Python-esque stack traceback of the given |
197 JS stack. | 165 JS stack. |
223 pass | 191 pass |
224 js_stack = js_stack['caller'] | 192 js_stack = js_stack['caller'] |
225 lines.insert(0, "Traceback (most recent call last):") | 193 lines.insert(0, "Traceback (most recent call last):") |
226 return '\n'.join(lines) | 194 return '\n'.join(lines) |
227 | 195 |
228 class JSRuntime(object): | 196 class JSSandbox(object): |
229 """ | 197 """ |
230 A JS runtime capable of loading and executing scripts. | 198 A JS runtime and associated functionality capable of securely |
199 loading and executing scripts. | |
231 """ | 200 """ |
232 | 201 |
233 def __init__(self, watchdog=watchdog): | 202 def __init__(self, watchdog=watchdog): |
234 rt = pydermonkey.Runtime() | 203 rt = pydermonkey.Runtime() |
235 cx = rt.new_context() | 204 cx = rt.new_context() |
236 globalobj = cx.new_object() | 205 root = cx.new_object() |
237 cx.init_standard_classes(globalobj) | 206 cx.init_standard_classes(root) |
238 | |
239 @safejsfunc(cx, globalobj) | |
240 def bar(cx, this, args): | |
241 obj = SafeJsObjectWrapper.wrap(cx, args[0], this) | |
242 print obj.bar() | |
243 | |
244 @safejsfunc(cx, globalobj) | |
245 def foo(cx, this, args): | |
246 return cx.call_function(this, args[0], ()) | |
247 | |
248 @safejsfunc(cx, globalobj, 'print') | |
249 def jsprint(cx, this, args): | |
250 if len(args) > 0: | |
251 print args[0] | |
252 | 207 |
253 cx.set_operation_callback(self._opcb) | 208 cx.set_operation_callback(self._opcb) |
254 cx.set_throw_hook(self._throwhook) | 209 cx.set_throw_hook(self._throwhook) |
255 watchdog.add_context(cx) | 210 watchdog.add_context(cx) |
256 | 211 |
257 self.rt = rt | 212 self.rt = rt |
258 self.cx = cx | 213 self.cx = cx |
259 self.globalobj = globalobj | |
260 self.curr_exc = None | 214 self.curr_exc = None |
261 self.py_stack = None | 215 self.py_stack = None |
262 self.js_stack = None | 216 self.js_stack = None |
217 self.__py_to_js = {} | |
218 self.root = self.wrap_jsobject(root, root) | |
219 | |
220 def finish(self): | |
221 for jsobj in self.__py_to_js.values(): | |
222 self.cx.clear_object_private(jsobj) | |
223 del self.__py_to_js | |
224 del self.curr_exc | |
225 del self.py_stack | |
226 del self.js_stack | |
227 del self.cx | |
228 del self.rt | |
263 | 229 |
264 def _opcb(self, cx): | 230 def _opcb(self, cx): |
265 # Don't do anything; if a keyboard interrupt was triggered, | 231 # Don't do anything; if a keyboard interrupt was triggered, |
266 # it'll get raised here automatically. | 232 # it'll get raised here automatically. |
267 pass | 233 pass |
271 if self.curr_exc != curr_exc: | 237 if self.curr_exc != curr_exc: |
272 self.curr_exc = curr_exc | 238 self.curr_exc = curr_exc |
273 self.py_stack = traceback.extract_stack() | 239 self.py_stack = traceback.extract_stack() |
274 self.js_stack = cx.get_stack() | 240 self.js_stack = cx.get_stack() |
275 | 241 |
242 def __wrap_pycallable(self, func): | |
243 if func in self.__py_to_js: | |
244 return self.__py_to_js[func] | |
245 | |
246 if hasattr(func, '__name__'): | |
247 name = func.__name__ | |
248 else: | |
249 name = "" | |
250 | |
251 def wrapper(func_cx, this, args): | |
252 try: | |
253 arglist = [] | |
254 for arg in args: | |
255 arglist.append(self.wrap_jsobject(arg)) | |
256 # TODO: Fill in extra required params with pymonkey.undefined? | |
257 # or automatically throw an exception to calling js code? | |
258 return func(*arglist) | |
259 except pydermonkey.error: | |
260 raise | |
261 except Exception: | |
262 raise InternalError() | |
263 wrapper.wrapped_pyobject = func | |
264 wrapper.__name__ = name | |
265 | |
266 jsfunc = self.cx.new_function(wrapper, name) | |
267 self.__py_to_js[func] = jsfunc | |
268 | |
269 return jsfunc | |
270 | |
271 def wrap_pyobject(self, value): | |
272 if isinstance(value, SafeJsObjectWrapper): | |
273 # It's already wrapped, just unwrap it. | |
274 return value.wrapped_jsobject | |
275 elif callable(value): | |
276 return self.__wrap_pycallable(value) | |
277 elif isinstance(value, object): | |
278 raise NotImplementedError("TODO") | |
279 else: | |
280 # Here's hoping it's a primitive value. | |
281 return value | |
282 | |
283 def wrap_jsobject(self, jsvalue, this=None): | |
284 if this is None: | |
285 this = self.root.wrapped_jsobject | |
286 if isinstance(jsvalue, pydermonkey.Function): | |
287 if jsvalue.is_python: | |
288 # It's a Python function, just unwrap it. | |
289 return self.cx.get_object_private(jsvalue).wrapped_pyobject | |
290 return SafeJsFunctionWrapper(self, jsvalue, this) | |
291 elif isinstance(jsvalue, pydermonkey.Object): | |
292 return SafeJsObjectWrapper(self, jsvalue, this) | |
293 else: | |
294 # It's a primitive value. | |
295 return jsvalue | |
296 | |
276 def run_script(self, filename): | 297 def run_script(self, filename): |
277 contents = open(filename).read() | 298 contents = open(filename).read() |
278 cx = self.cx | 299 cx = self.cx |
279 try: | 300 try: |
280 cx.evaluate_script(self.globalobj, contents, | 301 cx.evaluate_script(self.root.wrapped_jsobject, contents, |
281 filename, 1) | 302 filename, 1) |
282 except pydermonkey.error, e: | 303 except pydermonkey.error, e: |
283 print format_stack(self.js_stack) | 304 print format_stack(self.js_stack) |
284 print e.args[1] | 305 print e.args[1] |
285 except InternalError, e: | 306 except InternalError, e: |
286 print "An internal error occurred." | 307 print "An internal error occurred." |
287 traceback.print_tb(e.exc_info[2]) | 308 traceback.print_tb(e.exc_info[2]) |
288 print e.exc_info[1] | 309 print e.exc_info[1] |
289 | 310 |
290 if __name__ == '__main__': | 311 if __name__ == '__main__': |
291 runtime = JSRuntime() | 312 sandbox = JSSandbox() |
292 runtime.run_script('test.js') | 313 |
293 del runtime | 314 def bar(obj): |
315 print obj.bar() | |
316 sandbox.root.bar = bar | |
317 | |
318 def foo(callback): | |
319 return callback() | |
320 sandbox.root.foo = foo | |
321 | |
322 def jsprint(string): | |
323 print string | |
324 sandbox.root['print'] = jsprint | |
325 | |
326 sandbox.run_script('test.js') | |
327 | |
328 sandbox.finish() | |
329 del sandbox | |
294 | 330 |
295 import gc | 331 import gc |
296 gc.collect() | 332 gc.collect() |
297 if pydermonkey.get_debug_info()['runtime_count']: | 333 if pydermonkey.get_debug_info()['runtime_count']: |
298 print "WARNING: JS runtime was not destroyed." | 334 print "WARNING: JS runtime was not destroyed." |