view tcb.cpp @ 69:be61430630ab

Separated out some of the TCB-specific code into tcb.cpp/h.
author Atul Varma <varmaa@toolness.com>
date Thu, 25 Jun 2009 17:53:28 -0700
parents
children f6e3733eac01
line wrap: on
line source

#include "tcb.h"

// The class of the global object.
JSClass TCB_global_class = {
  "TCBGlobal", JSCLASS_GLOBAL_FLAGS,
  JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub,
  JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub,
  JSCLASS_NO_OPTIONAL_MEMBERS
};

// This native JS function prints the given string to the console.

JSBool TCB_print(JSContext *cx, JSObject *obj, uintN argc,
                 jsval *argv, jsval *rval)
{
  char *str;

  if (!JS_ConvertArguments(cx, argc, argv, "s", &str))
    return JS_FALSE;

  printf("%s\n", str);

  return JS_TRUE;
}

// This native JS function forces garbage collection.

JSBool TCB_forceGC(JSContext *cx, JSObject *obj, uintN argc,
                   jsval *argv, jsval *rval)
{
  JS_GC(cx);
  return JS_TRUE;
}

// This native JS function is a wrapper for JS_Enumerate().

JSBool TCB_enumerate(JSContext *cx, JSObject *obj, uintN argc,
                     jsval *argv, jsval *rval)
{
  JSObject *target;

  if (!JS_ConvertArguments(cx, argc, argv, "o", &target))
    return JS_FALSE;

  JSIdArray *ids = JS_Enumerate(cx, target);

  if (ids == NULL)
    return JS_FALSE;

  JSObject *array = JS_NewArrayObject(cx, ids->length, ids->vector);
  *rval = OBJECT_TO_JSVAL(array);
  return JS_TRUE;
}

// This native JS function looks up the property of an object, bypassing
// security checks and getters/setters.

JSBool TCB_lookupProperty(JSContext *cx, JSObject *obj, uintN argc,
                          jsval *argv, jsval *rval)
{
  JSObject *target;

  if (argc < 2) {
    JS_ReportError(cx, "Must provide id to lookup.");
    return JS_FALSE;
  }

  if (!JS_ConvertArguments(cx, argc, argv, "o", &target))
    return JS_FALSE;

  return JS_LookupPropertyById(cx, target, argv[1], rval);
}

// This native JS function returns a JS object containing metadata about
// the given function.

JSBool TCB_functionInfo(JSContext *cx, JSObject *obj, uintN argc,
                        jsval *argv, jsval *rval)
{
  JSFunction *func;

  if (!JS_ConvertArguments(cx, argc, argv, "f", &func))
    return JS_FALSE;

  JSScript *script = JS_GetFunctionScript(cx, func);

  if (script == NULL) {
    *rval = JSVAL_NULL;
    return JS_TRUE;
  }
  
  jsval filenameVal = JSVAL_NULL;

  const char *filename = JS_GetScriptFilename(cx, script);
  if (filename) {
    JSString *filenameStr = JS_NewStringCopyZ(cx, filename);
    filenameVal = STRING_TO_JSVAL(filenameStr);
  }

  // TODO: Check for return values here.
  JSObject *funcInfo = JS_NewObject(cx, NULL, NULL, NULL);
  JS_DefineProperty(cx, funcInfo, "filename", filenameVal,
                    NULL, NULL, 0);

  *rval = OBJECT_TO_JSVAL(funcInfo);
  return JS_TRUE;
}

// This native JS function returns a JS representation of the current
// state of the stack, starting with the callee's stack frame and going
// up from there.

JSBool TCB_stack(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                 jsval *rval)
{
  JSAutoLocalRootScope autoScope(cx);

  JSStackFrame *iterator = NULL;
  JSStackFrame *frame;
  JSObject *prevFrameInfo = NULL;
  JSObject *firstFrameInfo = NULL;
  bool skippedMyFrame = false;

  if (obj == NULL)
    // We're being called from native code, don't skip the topmost frame.
    skippedMyFrame = true;

  while ((frame = JS_FrameIterator(cx, &iterator)) != NULL) {
    if (!skippedMyFrame) {
      skippedMyFrame = true;
      continue;
    }

    jsval functionNameVal = JSVAL_NULL;
    jsval filenameVal = JSVAL_NULL;
    jsval lineNoVal = JSVAL_ZERO;
    jsval functionObjectVal = JSVAL_NULL;
    jsval scopeChainVal = JSVAL_NULL;

    JSFunction *func = JS_GetFrameFunction(cx, frame);
    if (func) {
      JSString *functionName = JS_GetFunctionId(func);
      if (functionName)
        functionNameVal = STRING_TO_JSVAL(functionName);
    }

    if (!JS_IsNativeFrame(cx, frame)) {
      JSScript *script = JS_GetFrameScript(cx, frame);
      jsbytecode *bytecode = JS_GetFramePC(cx, frame);

      const char *filename = JS_GetScriptFilename(cx, script);
      if (filename) {
        JSString *filenameStr = JS_NewStringCopyZ(cx, filename);
        filenameVal = STRING_TO_JSVAL(filenameStr);
      }

      uintN lineNo = JS_PCToLineNumber(cx, script, bytecode);
      lineNoVal = INT_TO_JSVAL(lineNo);

      JSObject *functionObject = JS_GetFrameFunctionObject(cx, frame);
      functionObjectVal = OBJECT_TO_JSVAL(functionObject);

      JSObject *scopeChain = JS_GetFrameScopeChain(cx, frame);
      scopeChainVal = OBJECT_TO_JSVAL(scopeChain);
    }

    // TODO: Check for return values here.
    JSObject *frameInfo = JS_NewObject(cx, NULL, NULL, NULL);
    JS_DefineProperty(cx, frameInfo, "filename", filenameVal,
                      NULL, NULL, 0);
    JS_DefineProperty(cx, frameInfo, "lineNo", lineNoVal,
                      NULL, NULL, 0);
    JS_DefineProperty(cx, frameInfo, "functionName", functionNameVal,
                      NULL, NULL, 0);
    JS_DefineProperty(cx, frameInfo, "functionObject", functionObjectVal,
                      NULL, NULL, 0);
    JS_DefineProperty(cx, frameInfo, "scopeChain", scopeChainVal,
                      NULL, NULL, 0);

    if (prevFrameInfo)
      JS_DefineProperty(cx, prevFrameInfo, "caller",
                        OBJECT_TO_JSVAL(frameInfo), NULL, NULL, 0);
    else
      firstFrameInfo = frameInfo;

    prevFrameInfo = frameInfo;
  }

  if (firstFrameInfo)
    *rval = OBJECT_TO_JSVAL(firstFrameInfo);
  else
    *rval = JSVAL_NULL;

  return JS_TRUE;
}

// Our global hook called whenever an exception is thrown saves the current
// exception and stack information to the 'lastException' and
// 'lastExceptionTraceback' globals of the TCB, respectively, much like
// Python's sys.exc_info().

JSTrapStatus TCB_throwHook(JSContext *cx, JSScript *script, jsbytecode *pc,
                           jsval *rval, void *closure)
{
  JSObject *tcb_global = (JSObject *) closure;
  jsval lastExceptionTraceback;
  jsval lastException;

  jsval exception = *rval;
  if (JS_IsExceptionPending(cx))
    if (!JS_GetPendingException(cx, &exception))
      printf("Getting exception failed.\n");

  if (!JS_GetProperty(cx, tcb_global, "lastException", &lastException))
    printf("Unable to retrieve last exception.");

  if (lastException == exception)
    // The same exception is just propagating through the stack; keep
    // our existing last-exception info.
    return JSTRAP_CONTINUE;

  if (!TCB_stack(cx, NULL, 0, NULL, &lastExceptionTraceback)) {
    printf("Generation of exception info failed.");
    lastExceptionTraceback = JSVAL_NULL;
  }

  if (!JS_SetProperty(cx, tcb_global, "lastExceptionTraceback",
                      &lastExceptionTraceback) ||
      !JS_SetProperty(cx, tcb_global, "lastException", &exception))
    printf("Setting of exception info failed.");

  return JSTRAP_CONTINUE;
}