Mercurial > pydertron
annotate pydertron.py @ 10:97f3b2242505
Removed unused js files.
| author | Atul Varma <varmaa@toolness.com> |
|---|---|
| date | Thu, 10 Sep 2009 11:09:14 -0700 |
| parents | a88d1cc4db92 |
| children | 665f69f61716 |
| rev | line source |
|---|---|
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
1 import os |
| 0 | 2 import sys |
| 3 import threading | |
| 4 import traceback | |
| 5 import weakref | |
| 6 import types | |
|
7
2117265e4dfe
Fixed some threading issues.
Atul Varma <varmaa@toolness.com>
parents:
6
diff
changeset
|
7 import atexit |
| 0 | 8 |
| 9 import pydermonkey | |
| 10 | |
| 11 class ContextWatchdogThread(threading.Thread): | |
| 12 """ | |
| 13 Watches active JS contexts and triggers their operation callbacks | |
| 14 at a regular interval. | |
| 15 """ | |
| 16 | |
| 17 # Default interval, in seconds, that the operation callbacks are | |
| 18 # triggered at. | |
|
8
fb0b161542b1
Improved performance of watchdog thread.
Atul Varma <varmaa@toolness.com>
parents:
7
diff
changeset
|
19 DEFAULT_INTERVAL = 0.25 |
| 0 | 20 |
| 21 def __init__(self, interval=DEFAULT_INTERVAL): | |
| 22 threading.Thread.__init__(self) | |
| 23 self._lock = threading.Lock() | |
| 24 self._stop = threading.Event() | |
| 25 self._contexts = [] | |
| 26 self.interval = interval | |
| 27 | |
| 28 def add_context(self, cx): | |
| 29 self._lock.acquire() | |
| 30 try: | |
| 31 self._contexts.append(weakref.ref(cx)) | |
| 32 finally: | |
| 33 self._lock.release() | |
| 34 | |
| 35 def join(self): | |
| 36 self._stop.set() | |
| 37 threading.Thread.join(self) | |
| 38 | |
| 39 def run(self): | |
| 40 while not self._stop.isSet(): | |
| 41 new_list = [] | |
| 42 self._lock.acquire() | |
| 43 try: | |
| 44 for weakcx in self._contexts: | |
| 45 cx = weakcx() | |
| 46 if cx: | |
| 47 new_list.append(weakcx) | |
| 48 cx.trigger_operation_callback() | |
|
7
2117265e4dfe
Fixed some threading issues.
Atul Varma <varmaa@toolness.com>
parents:
6
diff
changeset
|
49 del cx |
| 0 | 50 self._contexts = new_list |
| 51 finally: | |
| 52 self._lock.release() | |
|
8
fb0b161542b1
Improved performance of watchdog thread.
Atul Varma <varmaa@toolness.com>
parents:
7
diff
changeset
|
53 self._stop.wait(self.interval) |
| 0 | 54 |
| 55 # Create a global watchdog. | |
| 56 watchdog = ContextWatchdogThread() | |
| 57 watchdog.start() | |
|
7
2117265e4dfe
Fixed some threading issues.
Atul Varma <varmaa@toolness.com>
parents:
6
diff
changeset
|
58 atexit.register(watchdog.join) |
| 0 | 59 |
| 60 class InternalError(BaseException): | |
| 61 """ | |
| 62 Represents an error in a JS-wrapped Python function that wasn't | |
| 63 expected to happen; because it's derived from BaseException, it | |
| 64 unrolls the whole JS/Python stack so that the error can be | |
| 65 reported to the outermost calling code. | |
| 66 """ | |
| 67 | |
| 68 def __init__(self): | |
| 69 BaseException.__init__(self) | |
| 70 self.exc_info = sys.exc_info() | |
| 71 | |
| 72 class SafeJsObjectWrapper(object): | |
| 73 """ | |
| 74 Securely wraps a JS object to behave like any normal Python object. | |
| 75 """ | |
| 76 | |
| 77 __slots__ = ['_jsobject', '_sandbox', '_this'] | |
| 78 | |
| 79 def __init__(self, sandbox, jsobject, this): | |
| 80 if not isinstance(jsobject, pydermonkey.Object): | |
| 81 raise TypeError("Cannot wrap '%s' object" % | |
| 82 type(jsobject).__name__) | |
| 83 object.__setattr__(self, '_sandbox', sandbox) | |
| 84 object.__setattr__(self, '_jsobject', jsobject) | |
| 85 object.__setattr__(self, '_this', this) | |
| 86 | |
| 87 @property | |
| 88 def wrapped_jsobject(self): | |
| 89 return self._jsobject | |
| 90 | |
| 91 def _wrap_to_python(self, jsvalue): | |
| 92 return self._sandbox.wrap_jsobject(jsvalue, self._jsobject) | |
| 93 | |
| 94 def _wrap_to_js(self, value): | |
| 95 return self._sandbox.wrap_pyobject(value) | |
| 96 | |
| 97 def __eq__(self, other): | |
| 98 if isinstance(other, SafeJsObjectWrapper): | |
| 99 return self._jsobject == other._jsobject | |
| 100 else: | |
| 101 return False | |
| 102 | |
| 103 def __str__(self): | |
| 104 return self.toString() | |
| 105 | |
| 106 def __unicode__(self): | |
| 107 return self.toString() | |
| 108 | |
| 109 def __setitem__(self, item, value): | |
| 110 self.__setattr__(item, value) | |
| 111 | |
| 112 def __setattr__(self, name, value): | |
| 113 cx = self._sandbox.cx | |
| 114 jsobject = self._jsobject | |
| 115 | |
| 116 cx.define_property(jsobject, name, | |
| 117 self._wrap_to_js(value)) | |
| 118 | |
| 119 def __getitem__(self, item): | |
| 120 return self.__getattr__(item) | |
| 121 | |
| 122 def __getattr__(self, name): | |
| 123 cx = self._sandbox.cx | |
| 124 jsobject = self._jsobject | |
| 125 | |
| 126 return self._wrap_to_python(cx.get_property(jsobject, name)) | |
| 127 | |
| 128 def __contains__(self, item): | |
| 129 cx = self._sandbox.cx | |
| 130 jsobject = self._jsobject | |
| 131 | |
| 132 return cx.has_property(jsobject, item) | |
| 133 | |
| 134 def __iter__(self): | |
| 135 cx = self._sandbox.cx | |
| 136 jsobject = self._jsobject | |
| 137 | |
| 138 properties = cx.enumerate(jsobject) | |
| 139 for property in properties: | |
| 140 yield property | |
| 141 | |
| 142 class SafeJsFunctionWrapper(SafeJsObjectWrapper): | |
| 143 """ | |
| 144 Securely wraps a JS function to behave like any normal Python object. | |
| 145 """ | |
| 146 | |
| 147 def __init__(self, sandbox, jsfunction, this): | |
| 148 if not isinstance(jsfunction, pydermonkey.Function): | |
| 149 raise TypeError("Cannot wrap '%s' object" % | |
| 150 type(jsobject).__name__) | |
| 151 SafeJsObjectWrapper.__init__(self, sandbox, jsfunction, this) | |
| 152 | |
| 153 def __call__(self, *args): | |
| 154 cx = self._sandbox.cx | |
| 155 jsobject = self._jsobject | |
| 156 this = self._this | |
| 157 | |
| 158 arglist = [] | |
| 159 for arg in args: | |
| 160 arglist.append(self._wrap_to_js(arg)) | |
| 161 | |
| 162 obj = cx.call_function(this, jsobject, tuple(arglist)) | |
| 163 return self._wrap_to_python(obj) | |
| 164 | |
| 165 def format_stack(js_stack): | |
| 166 """ | |
| 167 Returns a formatted Python-esque stack traceback of the given | |
| 168 JS stack. | |
| 169 """ | |
| 170 | |
| 171 STACK_LINE =" File \"%(filename)s\", line %(lineno)d, in %(name)s" | |
| 172 | |
| 173 lines = [] | |
| 174 while js_stack: | |
| 175 script = js_stack['script'] | |
| 176 function = js_stack['function'] | |
| 177 if script: | |
| 178 frameinfo = dict(filename = script.filename, | |
| 179 lineno = js_stack['lineno'], | |
| 180 name = '<module>') | |
| 181 elif function and not function.is_python: | |
| 182 frameinfo = dict(filename = function.filename, | |
| 183 lineno = js_stack['lineno'], | |
| 184 name = function.name) | |
| 185 else: | |
| 186 frameinfo = None | |
| 187 if frameinfo: | |
| 188 lines.insert(0, STACK_LINE % frameinfo) | |
| 189 try: | |
| 190 filelines = open(frameinfo['filename']).readlines() | |
| 191 line = filelines[frameinfo['lineno'] - 1].strip() | |
| 192 lines.insert(1, " %s" % line) | |
| 193 except Exception: | |
| 194 pass | |
| 195 js_stack = js_stack['caller'] | |
| 196 lines.insert(0, "Traceback (most recent call last):") | |
| 197 return '\n'.join(lines) | |
| 198 | |
| 199 def jsexposed(name=None, on=None): | |
| 200 """ | |
| 201 Decorator used to expose the decorated function or method to | |
| 202 untrusted JS. | |
| 203 | |
| 204 'name' is an optional alternative name for the function. | |
| 205 | |
| 206 'on' is an optional SafeJsObjectWrapper that the function can be | |
| 207 automatically attached as a property to. | |
| 208 """ | |
| 209 | |
| 210 if callable(name): | |
| 211 func = name | |
| 212 func.__jsexposed__ = True | |
| 213 return func | |
| 214 | |
| 215 def make_exposed(func): | |
| 216 if name: | |
| 217 func.__name__ = name | |
| 218 func.__jsexposed__ = True | |
| 219 if on: | |
| 220 on[func.__name__] = func | |
| 221 return func | |
| 222 return make_exposed | |
| 223 | |
| 224 class JsExposedObject(object): | |
| 225 """ | |
| 226 Trivial base/mixin class for any Python classes that choose to | |
| 227 expose themselves to JS code. | |
| 228 """ | |
| 229 | |
| 230 pass | |
| 231 | |
| 232 class JsSandbox(object): | |
| 233 """ | |
| 234 A JS runtime and associated functionality capable of securely | |
| 235 loading and executing scripts. | |
| 236 """ | |
| 237 | |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
238 def __init__(self, watchdog=watchdog): |
| 0 | 239 rt = pydermonkey.Runtime() |
| 240 cx = rt.new_context() | |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
241 root_proto = cx.new_object() |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
242 cx.init_standard_classes(root_proto) |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
243 root = cx.new_object(None, root_proto) |
| 0 | 244 |
| 245 cx.set_operation_callback(self._opcb) | |
| 246 cx.set_throw_hook(self._throwhook) | |
| 247 watchdog.add_context(cx) | |
| 248 | |
| 249 self.rt = rt | |
| 250 self.cx = cx | |
| 251 self.curr_exc = None | |
| 252 self.py_stack = None | |
| 253 self.js_stack = None | |
| 254 self.__py_to_js = {} | |
| 255 self.__type_protos = {} | |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
256 self.root_proto = root_proto |
| 0 | 257 self.root = self.wrap_jsobject(root, root) |
| 258 | |
| 259 def finish(self): | |
| 260 """ | |
| 261 Cleans up all resources used by the sandbox, breaking any reference | |
| 262 cycles created due to issue #2 in pydermonkey: | |
| 263 | |
| 264 http://code.google.com/p/pydermonkey/issues/detail?id=2 | |
| 265 """ | |
| 266 | |
| 267 for jsobj in self.__py_to_js.values(): | |
| 268 self.cx.clear_object_private(jsobj) | |
| 269 del self.__py_to_js | |
| 270 del self.__type_protos | |
| 271 del self.curr_exc | |
| 272 del self.py_stack | |
| 273 del self.js_stack | |
| 274 del self.cx | |
| 275 del self.rt | |
| 276 | |
| 277 def _opcb(self, cx): | |
| 278 # Don't do anything; if a keyboard interrupt was triggered, | |
| 279 # it'll get raised here automatically. | |
| 280 pass | |
| 281 | |
| 282 def _throwhook(self, cx): | |
| 283 curr_exc = cx.get_pending_exception() | |
| 284 if self.curr_exc != curr_exc: | |
| 285 self.curr_exc = curr_exc | |
| 286 self.py_stack = traceback.extract_stack() | |
| 287 self.js_stack = cx.get_stack() | |
| 288 | |
| 289 def __wrap_pycallable(self, func, pyproto=None): | |
| 290 if func in self.__py_to_js: | |
| 291 return self.__py_to_js[func] | |
| 292 | |
| 293 if hasattr(func, '__name__'): | |
| 294 name = func.__name__ | |
| 295 else: | |
| 296 name = "" | |
| 297 | |
| 298 if pyproto: | |
| 299 def wrapper(func_cx, this, args): | |
| 300 try: | |
| 301 arglist = [] | |
| 302 for arg in args: | |
| 303 arglist.append(self.wrap_jsobject(arg)) | |
| 304 instance = func_cx.get_object_private(this) | |
| 305 if instance is None or not isinstance(instance, pyproto): | |
| 306 raise pydermonkey.error("Method type mismatch") | |
| 307 | |
| 308 # TODO: Fill in extra required params with | |
| 309 # pymonkey.undefined? or automatically throw an | |
| 310 # exception to calling js code? | |
| 311 return self.wrap_pyobject(func(instance, *arglist)) | |
| 312 except pydermonkey.error: | |
| 313 raise | |
| 314 except Exception: | |
| 315 raise InternalError() | |
| 316 else: | |
| 317 def wrapper(func_cx, this, args): | |
| 318 try: | |
| 319 arglist = [] | |
| 320 for arg in args: | |
| 321 arglist.append(self.wrap_jsobject(arg)) | |
| 322 | |
| 323 # TODO: Fill in extra required params with | |
| 324 # pymonkey.undefined? or automatically throw an | |
| 325 # exception to calling js code? | |
| 326 return self.wrap_pyobject(func(*arglist)) | |
| 327 except pydermonkey.error: | |
| 328 raise | |
| 329 except Exception: | |
| 330 raise InternalError() | |
| 331 wrapper.wrapped_pyobject = func | |
| 332 wrapper.__name__ = name | |
| 333 | |
| 334 jsfunc = self.cx.new_function(wrapper, name) | |
| 335 self.__py_to_js[func] = jsfunc | |
| 336 | |
| 337 return jsfunc | |
| 338 | |
| 339 def __wrap_pyinstance(self, value): | |
| 340 pyproto = type(value) | |
| 341 if pyproto not in self.__type_protos: | |
| 342 jsproto = self.cx.new_object() | |
| 343 if hasattr(pyproto, '__jsprops__'): | |
| 344 define_getter = self.cx.get_property(jsproto, | |
| 345 '__defineGetter__') | |
| 346 define_setter = self.cx.get_property(jsproto, | |
| 347 '__defineSetter__') | |
| 348 for name in pyproto.__jsprops__: | |
| 349 prop = getattr(pyproto, name) | |
| 350 if not type(prop) == property: | |
| 351 raise TypeError("Expected attribute '%s' to " | |
| 352 "be a property" % name) | |
| 353 getter = None | |
| 354 setter = None | |
| 355 if prop.fget: | |
| 356 getter = self.__wrap_pycallable(prop.fget, | |
| 357 pyproto) | |
| 358 if prop.fset: | |
| 359 setter = self.__wrap_pycallable(prop.fset, | |
| 360 pyproto) | |
| 361 if getter: | |
| 362 self.cx.call_function(jsproto, | |
| 363 define_getter, | |
| 364 (name, getter)) | |
| 365 if setter: | |
| 366 self.cx.call_function(jsproto, | |
| 367 define_setter, | |
| 368 (name, setter,)) | |
| 369 for name in dir(pyproto): | |
| 370 attr = getattr(pyproto, name) | |
| 371 if (isinstance(attr, types.UnboundMethodType) and | |
| 372 hasattr(attr, '__jsexposed__') and | |
| 373 attr.__jsexposed__): | |
| 374 jsmethod = self.__wrap_pycallable(attr, pyproto) | |
| 375 self.cx.define_property(jsproto, name, jsmethod) | |
| 376 self.__type_protos[pyproto] = jsproto | |
| 377 return self.cx.new_object(value, self.__type_protos[pyproto]) | |
| 378 | |
| 379 def wrap_pyobject(self, value): | |
| 380 """ | |
| 381 Wraps the given Python object for export to untrusted JS. | |
| 382 | |
| 383 If the Python object isn't of a type that can be exposed to JS, | |
| 384 a TypeError is raised. | |
| 385 """ | |
| 386 | |
| 387 if (isinstance(value, (int, basestring, float, bool)) or | |
| 388 value is pydermonkey.undefined or | |
| 389 value is None): | |
| 390 return value | |
| 391 if isinstance(value, SafeJsObjectWrapper): | |
| 392 # It's already wrapped, just unwrap it. | |
| 393 return value.wrapped_jsobject | |
| 394 elif callable(value): | |
| 395 if not (hasattr(value, '__jsexposed__') and | |
| 396 value.__jsexposed__): | |
| 397 raise ValueError("Callable isn't configured for exposure " | |
| 398 "to untrusted JS code") | |
| 399 return self.__wrap_pycallable(value) | |
| 400 elif isinstance(value, JsExposedObject): | |
| 401 return self.__wrap_pyinstance(value) | |
| 402 else: | |
| 403 raise TypeError("Can't expose objects of type '%s' to JS." % | |
| 404 type(value).__name__) | |
| 405 | |
| 406 def wrap_jsobject(self, jsvalue, this=None): | |
| 407 """ | |
| 408 Wraps the given pydermonkey.Object for import to trusted | |
| 409 Python code. If the type is just a primitive, it's simply | |
| 410 returned, since no wrapping is needed. | |
| 411 """ | |
| 412 | |
| 413 if this is None: | |
| 414 this = self.root.wrapped_jsobject | |
| 415 if isinstance(jsvalue, pydermonkey.Function): | |
| 416 if jsvalue.is_python: | |
| 417 # It's a Python function, just unwrap it. | |
| 418 return self.cx.get_object_private(jsvalue).wrapped_pyobject | |
| 419 return SafeJsFunctionWrapper(self, jsvalue, this) | |
| 420 elif isinstance(jsvalue, pydermonkey.Object): | |
| 421 # It's a wrapped Python object instance, just unwrap it. | |
| 422 instance = self.cx.get_object_private(jsvalue) | |
| 423 if instance: | |
| 424 if not isinstance(instance, JsExposedObject): | |
| 425 raise AssertionError("Object private is not of type " | |
| 426 "JsExposedObject") | |
| 427 return instance | |
| 428 else: | |
| 429 return SafeJsObjectWrapper(self, jsvalue, this) | |
| 430 else: | |
| 431 # It's a primitive value. | |
| 432 return jsvalue | |
| 433 | |
| 434 def new_array(self, *contents): | |
| 435 array = self.wrap_jsobject(self.cx.new_array_object()) | |
| 436 for item in contents: | |
| 437 array.push(item) | |
| 438 return array | |
| 439 | |
| 440 def new_object(self, **contents): | |
| 441 obj = self.wrap_jsobject(self.cx.new_object()) | |
| 442 for name in contents: | |
| 443 obj[name] = contents[name] | |
| 444 return obj | |
| 445 | |
| 446 def evaluate(self, code, filename='<string>', lineno=1): | |
| 447 retval = self.cx.evaluate_script(self.root.wrapped_jsobject, | |
| 448 code, filename, lineno) | |
| 449 return self.wrap_jsobject(retval) | |
| 450 | |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
451 def run_script(self, contents, filename='<string>', lineno=1, |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
452 callback=None): |
| 0 | 453 """ |
| 454 Runs the given JS script, returning 0 on success, -1 on failure. | |
| 455 """ | |
| 456 | |
| 457 retval = -1 | |
| 458 cx = self.cx | |
| 459 try: | |
| 460 result = cx.evaluate_script(self.root.wrapped_jsobject, | |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
461 contents, filename, lineno) |
| 0 | 462 if callback: |
| 463 callback(self.wrap_jsobject(result)) | |
| 464 retval = 0 | |
| 465 except pydermonkey.error, e: | |
| 466 print format_stack(self.js_stack) | |
| 467 print e.args[1] | |
| 468 except InternalError, e: | |
| 469 print "An internal error occurred." | |
| 470 traceback.print_tb(e.exc_info[2]) | |
| 471 print e.exc_info[1] | |
| 472 return retval | |
|
1
ab09b8a10876
Added trivial half-baked implementation of securable modules.
Atul Varma <varmaa@toolness.com>
parents:
0
diff
changeset
|
473 |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
474 class SecurableModuleLoader(object): |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
475 """ |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
476 Loader for CommonJS SecurableModules. |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
477 """ |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
478 |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
479 def __init__(self, sandbox, root_dir, globals): |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
480 self.root_dir = root_dir |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
481 self.sandbox = sandbox |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
482 self.globals = globals |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
483 self.modules = {} |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
484 self._install_globals(sandbox.root) |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
485 |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
486 def _install_globals(self, object): |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
487 for name in self.globals: |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
488 object[name] = self.globals[name] |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
489 object['require'] = self.require |
|
1
ab09b8a10876
Added trivial half-baked implementation of securable modules.
Atul Varma <varmaa@toolness.com>
parents:
0
diff
changeset
|
490 |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
491 def _get_calling_script(self): |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
492 frame = self.sandbox.cx.get_stack()['caller'] |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
493 curr_script = None |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
494 while frame and curr_script is None: |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
495 if frame['function'] and frame['function'].filename: |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
496 curr_script = frame['function'].filename |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
497 elif frame['script']: |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
498 curr_script = frame['script'].filename |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
499 frame = frame['caller'] |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
500 |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
501 if curr_script is None: |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
502 raise RuntimeError("Can't find calling script") |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
503 return curr_script |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
504 |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
505 def _load_module(self, filename): |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
506 sandbox = self.sandbox |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
507 cx = sandbox.cx |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
508 |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
509 module = cx.new_object(None, sandbox.root_proto) |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
510 cx.init_standard_classes(module) |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
511 exports = cx.new_object() |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
512 cx.define_property(module, 'exports', exports) |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
513 self._install_globals(sandbox.wrap_jsobject(module)) |
|
4
ae5869491e61
Added more to securablemodule impl.
Atul Varma <varmaa@toolness.com>
parents:
3
diff
changeset
|
514 self.modules[filename] = sandbox.wrap_jsobject(exports) |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
515 |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
516 contents = open(filename).read() |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
517 cx.evaluate_script(module, contents, filename, 1) |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
518 |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
519 @jsexposed |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
520 def require(self, path): |
|
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
521 curr_script = self._get_calling_script() |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
522 curr_dir = os.path.split(curr_script)[0] |
|
1
ab09b8a10876
Added trivial half-baked implementation of securable modules.
Atul Varma <varmaa@toolness.com>
parents:
0
diff
changeset
|
523 |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
524 if (not path.startswith(".") or |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
525 not curr_dir.startswith(self.root_dir)): |
|
4
ae5869491e61
Added more to securablemodule impl.
Atul Varma <varmaa@toolness.com>
parents:
3
diff
changeset
|
526 curr_dir = self.root_dir |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
527 ospath = path.replace('/', os.path.sep) |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
528 filename = os.path.join(curr_dir, "%s.js" % ospath) |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
529 filename = os.path.normpath(filename) |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
530 if (not filename.startswith(self.root_dir) or |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
531 not (os.path.exists(filename) and |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
532 not os.path.isdir(filename))): |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
533 raise pydermonkey.error('Module not found: %s' % path) |
|
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
534 |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
535 if filename not in self.modules: |
|
4
ae5869491e61
Added more to securablemodule impl.
Atul Varma <varmaa@toolness.com>
parents:
3
diff
changeset
|
536 self._load_module(filename) |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
537 return self.modules[filename] |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
538 |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
539 def run_test(name, libpath): |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
540 print name |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
541 sandbox = JsSandbox() |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
542 |
|
9
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
543 stats = [0, 0] |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
544 |
|
3
14d8d73774d7
Refactored securable module loader.
Atul Varma <varmaa@toolness.com>
parents:
2
diff
changeset
|
545 @jsexposed(name='print') |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
546 def jsprint(message, label): |
|
9
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
547 if label == "pass": |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
548 stats[0] += 1 |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
549 elif label == "fail": |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
550 stats[1] += 1 |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
551 print "%s %s" % (message, label) |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
552 |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
553 system = sandbox.new_object() |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
554 system['print'] = jsprint |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
555 globals = {'sys': system, |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
556 'environment': sandbox.new_object()} |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
557 loader = SecurableModuleLoader(sandbox, libpath, globals) |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
558 retval = sandbox.run_script("require('program')") |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
559 sandbox.finish() |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
560 print |
|
9
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
561 |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
562 if retval != 0: |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
563 stats[1] += 1 |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
564 return stats |
|
2
b6f9d743a2b5
Refined require() implementation.
Atul Varma <varmaa@toolness.com>
parents:
1
diff
changeset
|
565 |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
566 if __name__ == '__main__': |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
567 base_libpath = os.path.join("interoperablejs-read-only", |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
568 "compliance") |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
569 if not os.path.exists(base_libpath): |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
570 print "Please run the following command and then re-run " |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
571 print "this script:" |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
572 print |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
573 print ("svn checkout " |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
574 "http://interoperablejs.googlecode.com/svn/trunk/ " |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
575 "interoperablejs-read-only") |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
576 sys.exit(1) |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
577 |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
578 dirs = [(os.path.join(base_libpath, name), name) |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
579 for name in os.listdir(base_libpath) |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
580 if name not in ['.svn', 'ORACLE']] |
|
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
581 |
|
9
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
582 totals = [0, 0] |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
583 |
|
6
97adec8c8127
Now passing all CommonJS SecurableModule compliance tests.
Atul Varma <varmaa@toolness.com>
parents:
5
diff
changeset
|
584 for libpath, name in dirs: |
|
9
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
585 passed, failed = run_test(name, libpath) |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
586 totals[0] += passed |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
587 totals[1] += failed |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
588 |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
589 print "passed: %d failed: %d" % tuple(totals) |
|
5
c11c84274192
Added memory leak detection.
Atul Varma <varmaa@toolness.com>
parents:
4
diff
changeset
|
590 |
|
c11c84274192
Added memory leak detection.
Atul Varma <varmaa@toolness.com>
parents:
4
diff
changeset
|
591 import gc |
|
c11c84274192
Added memory leak detection.
Atul Varma <varmaa@toolness.com>
parents:
4
diff
changeset
|
592 gc.collect() |
|
c11c84274192
Added memory leak detection.
Atul Varma <varmaa@toolness.com>
parents:
4
diff
changeset
|
593 if pydermonkey.get_debug_info()['runtime_count']: |
|
c11c84274192
Added memory leak detection.
Atul Varma <varmaa@toolness.com>
parents:
4
diff
changeset
|
594 print "WARNING: JS runtime was not destroyed." |
|
9
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
595 |
|
a88d1cc4db92
Test runner now displays # passed and failed.
Atul Varma <varmaa@toolness.com>
parents:
8
diff
changeset
|
596 sys.exit(totals[1]) |
