view spidermonkey-playground.cpp @ 18:33b2d70d8ada

Added license blocks.
author Atul Varma <varmaa@toolness.com>
date Mon, 22 Jun 2009 07:03:31 -0700
parents 83b891ccc471
children bbbad7e4b4aa
line wrap: on
line source

/* ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Ubiquity.
 *
 * The Initial Developer of the Original Code is Mozilla.
 * Portions created by the Initial Developer are Copyright (C) 2007
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *   Atul Varma <atul@mozilla.com>
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK ***** */

#include "string.h"
#include "jsapi.h"
#include "jsdbgapi.h"

#include "wrapper.h"

#define MAX_SCRIPT_SIZE 100000
#define TCB_FILENAME "tcb.js"

static JSContext *tcb_cx;
static JSObject  *tcb_global;

// TODO: Make sure we're rooting objects appropriately here so that
// the interpreter doesn't randomly crash or something.

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

static JSBool 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;
}

static JSBool wrap(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                   jsval *rval)
{
  JSObject *wrappee;
  JSObject *resolver;

  if (!JS_ConvertArguments(cx, argc, argv, "oo", &wrappee, &resolver))
    return JS_FALSE;

  JSObject *result;

  result = wrapObject(cx, argv[0], argv[1]);

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

static JSBool require(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                      jsval *rval)
{
  char *filename;
  JSObject *exports;

  if (!JS_ConvertArguments(cx, argc, argv, "so", &filename, &exports))
    return JS_FALSE;

  FILE *f = fopen(filename, "r");
  if (!f) {
    JS_ReportError(cx, "File not found");
    return JS_FALSE;
  }

  char source[MAX_SCRIPT_SIZE];
  fread(source, MAX_SCRIPT_SIZE, 1, f);
  fclose(f);

  // TODO: Check for return values here.
  JSContext *module_cx = JS_NewContext(JS_GetRuntime(cx), 8192);
  JSObject *module_global = JS_NewObject(module_cx, &global_class, NULL,
                                         NULL);
  JS_InitStandardClasses(module_cx, module_global);
  JS_DefineProperty(module_cx, module_global, "imports",
                    OBJECT_TO_JSVAL(exports), NULL, NULL, 0);
  JSObject *module_exports = JS_NewObject(module_cx, NULL, NULL,
                                          module_global);
  JS_DefineProperty(module_cx, module_global, "exports",
                    OBJECT_TO_JSVAL(module_exports), NULL, NULL, 0);

  jsval module_rval;
  if (!JS_EvaluateScript(module_cx, module_global, source,
                         strlen(source), filename, 1, &module_rval)) {
    JS_DestroyContext(module_cx);
    return JS_FALSE;
  }

  *rval = OBJECT_TO_JSVAL(module_exports);

  JS_DestroyContext(module_cx);

  return JS_TRUE;
}

static JSBool 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);
}

static JSBool 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;
}

static JSBool stack(JSContext *cx, JSObject *obj, uintN argc, jsval *argv,
                    jsval *rval)
{
  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;
}

static JSTrapStatus 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 (!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;
}

static JSBool checkAccess(JSContext *cx, JSObject *obj, jsval id,
                          JSAccessMode mode, jsval *vp)
{
  jsval checkAccess;
  if (tcb_global && JS_GetProperty(tcb_cx, tcb_global, "checkAccess",
                                   &checkAccess) &&
      JSVAL_IS_OBJECT(checkAccess) &&
      JS_ObjectIsFunction(tcb_cx, JSVAL_TO_OBJECT(checkAccess))) {
    jsval argv[2];
    argv[0] = OBJECT_TO_JSVAL(obj);
    argv[1] = id;
    return JS_CallFunctionValue(tcb_cx, tcb_global, checkAccess, 2, argv,
                                vp);
  }

  return JS_LookupPropertyById(cx, obj, id, vp);
}

static JSFunctionSpec tcb_global_functions[] = {
  JS_FS("print",   print,   1, 0, 0),
  JS_FS("wrap",    wrap,    2, 0, 0),
  JS_FS("stack",   stack,   0, 0, 0),
  JS_FS("require", require, 2, 0, 0),
  JS_FS("lookupProperty", lookupProperty, 2, 0, 0),
  JS_FS("functionInfo", functionInfo, 1, 0, 0),
  JS_FS_END
};

static JSSecurityCallbacks securityCallbacks = {
  checkAccess,
  NULL,
  NULL
};

int main(int argc, const char *argv[])
{
  /* JS variables. */
  JSRuntime *rt;

  /* Create a JS runtime. */
  rt = JS_NewRuntime(8L * 1024L * 1024L);
  if (rt == NULL)
    return 1;

  JS_SetRuntimeSecurityCallbacks(rt, &securityCallbacks);

  /* Create a context. */
  tcb_cx = JS_NewContext(rt, 8192);
  if (tcb_cx == NULL)
    return 1;
  JS_SetOptions(tcb_cx, JSOPTION_VAROBJFIX);
  JS_SetVersion(tcb_cx, JSVERSION_LATEST);

  /* Create the TCB's global object. */
  tcb_global = JS_NewObject(tcb_cx, &global_class, NULL, NULL);
  if (tcb_global == NULL)
    return 1;

  /* Populate the tcb_global object with the standard globals,
     like Object and Array. */
  if (!JS_InitStandardClasses(tcb_cx, tcb_global))
    return 1;

  if (!JS_DefineFunctions(tcb_cx, tcb_global, tcb_global_functions))
    return 1;

  // TODO: Check for return values here.
  JS_SetThrowHook(rt, throwHook, tcb_global);
  JS_DefineProperty(tcb_cx, tcb_global, "lastExceptionTraceback", JSVAL_NULL,
                    NULL, NULL, 0);
  JS_DefineProperty(tcb_cx, tcb_global, "lastException", JSVAL_NULL,
                    NULL, NULL, 0);

  /* Your application code here. This may include JSAPI calls
     to create your own custom JS objects and run scripts. */
  FILE *f = fopen(TCB_FILENAME, "r");
  if (!f)
    return 1;
  
  char source[MAX_SCRIPT_SIZE];
  fread(source, MAX_SCRIPT_SIZE, 1, f);
  fclose(f);

  jsval rval;
  if (!JS_EvaluateScript(tcb_cx, tcb_global, source,
                         strlen(source), TCB_FILENAME, 1,
                         &rval)) {
    jsval handleError;
    if (JS_GetProperty(tcb_cx, tcb_global, "handleError", &handleError) &&
        JSVAL_IS_OBJECT(handleError) &&
        JS_ObjectIsFunction(tcb_cx, JSVAL_TO_OBJECT(handleError))) {
      JS_CallFunctionValue(tcb_cx, tcb_global, handleError, 0, NULL, &rval);
    }
    return 1;
  }

  /* Cleanup. */
  JS_DestroyContext(tcb_cx);
  JS_DestroyRuntime(rt);
  JS_ShutDown();

  printf("Farewell.\n");
  return 0;
}