All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.gwt.core.client.impl.StackTraceCreator Maven / Gradle / Ivy

There is a newer version: 2.10.0
Show newest version
/*
 * Copyright 2009 Google Inc.
 * 
 * Licensed under the Apache License, Version 2.0 (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.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.core.client.impl;

import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;

/**
 * Encapsulates logic to create a stack trace. This class should only be used in
 * Production Mode.
 */
public class StackTraceCreator {
  /**
   * This class acts as a deferred-binding hook point to allow more optimal
   * versions to be substituted. This base version simply crawls
   * arguments.callee.caller.
   */
  static class Collector {
    public native JsArrayString collect() /*-{
      var seen = {};
      var toReturn = [];

      // Ignore the collect() and fillInStackTrace call
      var callee = arguments.callee.caller.caller;
      while (callee) {
        var name = this.@com.google.gwt.core.client.impl.StackTraceCreator.Collector::extractName(Ljava/lang/String;)(callee.toString());
        toReturn.push(name);

        // Avoid infinite loop by associating names to function objects.  We
        // record each caller in the withThisName variable to handle functions
        // with identical names but separate identity (such as 'anonymous')
        var keyName = ':' + name;
        var withThisName = seen[keyName];
        if (withThisName) {
          var i, j;
          for (i = 0, j = withThisName.length; i < j; i++) {
            if (withThisName[i] === callee) {
              return toReturn;
            }
          }
        }

        (withThisName || (seen[keyName] = [])).push(callee);
        callee = callee.caller;
      }
      return toReturn;
    }-*/;

    public void createStackTrace(JavaScriptException e) {
      JsArrayString stack = inferFrom(e.getException());

      StackTraceElement[] stackTrace = new StackTraceElement[stack.length()];
      for (int i = 0, j = stackTrace.length; i < j; i++) {
        stackTrace[i] = new StackTraceElement("Unknown", stack.get(i), null,
            LINE_NUMBER_UNKNOWN);
      }
      e.setStackTrace(stackTrace);
    }

    public void fillInStackTrace(Throwable t) {
      JsArrayString stack = StackTraceCreator.createStackTrace();
      StackTraceElement[] stackTrace = new StackTraceElement[stack.length()];
      for (int i = 0, j = stackTrace.length; i < j; i++) {
        stackTrace[i] = new StackTraceElement("Unknown", stack.get(i), null,
            LINE_NUMBER_UNKNOWN);
      }
      t.setStackTrace(stackTrace);
    }

    /**
     * Returns the list of properties of an unexpected JavaScript exception.
     */
    public native String getProperties(JavaScriptObject e) /*-{
      var result = "";
      try {
        for (var prop in e) {
          if (prop != "name" && prop != "message" && prop != "toString") {
            try {
              result += "\n " + prop + ": " + e[prop];
            } catch (ignored) {
              // Skip the property if it threw an exception.
            }
          }
        }
      } catch (ignored) {
        // If we can't do "in" on the exception, just return what we have.
      }
      return result;
    }-*/;

    /**
     * Attempt to infer the stack from an unknown JavaScriptObject that had been
     * thrown. The default implementation just returns an empty array.
     * 
     * @param e a JavaScriptObject
     */
    public JsArrayString inferFrom(JavaScriptObject e) {
      return JavaScriptObject.createArray().cast();
    }

    /**
     * Extract the name of a function from it's toString() representation.
     * Package-access for testing.
     */
    protected String extractName(String fnToString) {
      return extractNameFromToString(fnToString);
    }

    /**
     * Raise an exception and return it.
     */
    protected native JavaScriptObject makeException() /*-{
      try {
        null.a();
      } catch (e) {
        return e;
      }
    }-*/;
  }

  /**
   * Collaborates with JsStackEmulator.
   */
  static class CollectorEmulated extends Collector {

    @Override
    public JsArrayString collect() {
      JsArrayString toReturn = JsArrayString.createArray().cast();
      JsArray stack = getStack();
      for (int i = 0, j = getStackDepth(); i < j; i++) {
        String name = stack.get(i) == null ? "anonymous"
            : extractName(stack.get(i).toString());
        // Reverse the order
        toReturn.set(j - i - 1, name);
      }

      return toReturn;
    }

    @Override
    public void createStackTrace(JavaScriptException e) {
      // No-op, relying on initializer call to collect()
    }

    @Override
    public void fillInStackTrace(Throwable t) {
      JsArrayString stack = collect();
      JsArrayString locations = getLocation();
      StackTraceElement[] stackTrace = new StackTraceElement[stack.length()];
      for (int i = 0, j = stackTrace.length; i < j; i++) {
        // Locations is also backwards
        String location = locations.get(j - i - 1);
        String fileName = null;
        int lineNumber = LINE_NUMBER_UNKNOWN;
        if (location != null) {
          int idx = location.indexOf(':');
          if (idx != -1) {
            fileName = location.substring(0, idx);
            lineNumber = Integer.parseInt(location.substring(idx + 1));
          } else {
            lineNumber = Integer.parseInt(location);
          }
        }
        stackTrace[i] = new StackTraceElement("Unknown", stack.get(i),
            fileName, lineNumber);
      }
      t.setStackTrace(stackTrace);
    }

    /**
     * When compiler.stackMode = emulated, return an empty string, rather than a
     * list of properties, since the additional information regarding the origin
     * of the JavaScriptException, relative to compiled JavaScript source code,
     * adds no real value, since we have fully emulated stack traces.
     */
    @Override
    public String getProperties(JavaScriptObject e) {
      return "";
    }

    @Override
    public JsArrayString inferFrom(JavaScriptObject e) {
      throw new RuntimeException("Should not reach here");
    }

    private native JsArrayString getLocation()/*-{
      return $location;
    }-*/;

    private native JsArray getStack()/*-{
      return $stack;
    }-*/;

    private native int getStackDepth() /*-{
      return $stackDepth;
    }-*/;
  }

  /**
   * Mozilla provides a stack property in thrown objects.
   */
  static class CollectorMoz extends Collector {
    /**
     * This implementation doesn't suffer from the limitations of crawling
     * caller since Mozilla provides proper activation records.
     */
    @Override
    public JsArrayString collect() {
      return splice(inferFrom(makeException()), toSplice());
    }

    @Override
    public JsArrayString inferFrom(JavaScriptObject e) {
      JsArrayString stack = getStack(e);
      for (int i = 0, j = stack.length(); i < j; i++) {
        stack.set(i, extractName(stack.get(i)));
      }
      return stack;
    }

    protected native JsArrayString getStack(JavaScriptObject e) /*-{
      return (e && e.stack) ? e.stack.split('\n') : [];
    }-*/;

    protected int toSplice() {
      return 2;
    }
  }

  /**
   * Chrome uses a slightly different format to Mozilla.
   * 
   * See http://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/
   * messages.js?r=2340#712 for formatting code.
   * 
   * Function calls can be of the four following forms:
   * 
   * 
   * at file.js:1:2
   * at functionName (file.js:1:2)
   * at Type.functionName (file.js:1:2)
   * at Type.functionName [as methodName] (file.js:1:2)
   * 
*/ static class CollectorChrome extends CollectorMoz { @Override public JsArrayString collect() { JsArrayString res = super.collect(); if (res.length() == 0) { /* * Ensure Safari falls back to default Collector implementation. * Remember to remove this method call from the stack: */ res = splice(new Collector().collect(), 1); } return res; } @Override public JsArrayString inferFrom(JavaScriptObject e) { JsArrayString stack = super.inferFrom(e); if (stack.length() == 0) { // Safari should fall back to default Collector: return new Collector().inferFrom(e); } else { // Chrome contains the error itself as the first line of the stack: return splice(stack, 1); } } @Override protected String extractName(String fnToString) { if (fnToString.length() == 0) { return "anonymous"; } String toReturn = fnToString.trim(); // Strip the "at " prefix: if (toReturn.startsWith("at ")) { toReturn = toReturn.substring(3); } // Strip bracketed items from the end: int index = toReturn.indexOf("["); if (index == -1) { index = toReturn.indexOf("("); } if (index == -1) { // No bracketed items found, hence no function name available: return "anonymous"; } else { // Bracketed items found: strip them off. toReturn = toReturn.substring(0, index).trim(); } // Strip the Type off to leave just the functionName: index = toReturn.indexOf('.'); if (index != -1) { toReturn = toReturn.substring(index + 1); } return toReturn.length() > 0 ? toReturn : "anonymous"; } @Override protected int toSplice() { return 3; } } /** * Opera encodes stack trace information in the error's message. */ static class CollectorOpera extends CollectorMoz { /** * We have much a much simpler format to work with. */ @Override protected String extractName(String fnToString) { return fnToString.length() == 0 ? "anonymous" : fnToString; } /** * Opera has the function name on every-other line. */ @Override protected JsArrayString getStack(JavaScriptObject e) { JsArrayString toReturn = getMessage(e); assert toReturn.length() % 2 == 0 : "Expecting an even number of lines"; int i, i2, j; for (i = 0, i2 = 0, j = toReturn.length(); i2 < j; i++, i2 += 2) { int idx = toReturn.get(i2).lastIndexOf("function "); if (idx == -1) { toReturn.set(i, ""); } else { toReturn.set(i, toReturn.get(i2).substring(idx + 9).trim()); } } setLength(toReturn, i); return toReturn; } @Override protected int toSplice() { return 3; } private native JsArrayString getMessage(JavaScriptObject e) /*-{ return (e && e.message) ? e.message.split('\n') : []; }-*/; private native void setLength(JsArrayString obj, int length) /*-{ obj.length = length; }-*/; } /** * When compiler.stackMode = strip, we stub out the collector. */ static class CollectorNull extends Collector { @Override public JsArrayString collect() { return JsArrayString.createArray().cast(); } @Override public void createStackTrace(JavaScriptException e) { // empty, since Throwable.getStackTrace() properly handles null } @Override public void fillInStackTrace(Throwable t) { // empty, since Throwable.getStackTrace() properly handles null } } private static final int LINE_NUMBER_UNKNOWN = -1; /** * Create a stack trace based on a JavaScriptException. This method should * only be called in Production Mode. */ public static void createStackTrace(JavaScriptException e) { if (!GWT.isScript()) { throw new RuntimeException( "StackTraceCreator should only be called in Production Mode"); } GWT. create(Collector.class).createStackTrace(e); } /** * Fill in a stack trace based on the current execution stack. This method * should only be called in Production Mode. */ public static void fillInStackTrace(Throwable t) { if (!GWT.isScript()) { throw new RuntimeException( "StackTraceCreator should only be called in Production Mode"); } GWT. create(Collector.class).fillInStackTrace(t); } /** * Returns the list of properties of an unexpected JavaScript exception, * unless compiler.stackMode = emulated, in which case the empty string is * returned. This method should only be called in Production Mode. */ public static String getProperties(JavaScriptObject e) { if (!GWT.isScript()) { throw new RuntimeException( "StackTraceCreator should only be called in Production Mode"); } return GWT. create(Collector.class).getProperties(e); } /** * Create a stack trace based on the current execution stack. This method * should only be called in Production Mode. */ static JsArrayString createStackTrace() { if (!GWT.isScript()) { throw new RuntimeException( "StackTraceCreator should only be called in Production Mode"); } return GWT. create(Collector.class).collect(); } static String extractNameFromToString(String fnToString) { String toReturn = ""; fnToString = fnToString.trim(); int index = fnToString.indexOf("("); if (index != -1) { int start = fnToString.startsWith("function") ? 8 : 0; toReturn = fnToString.substring(start, index).trim(); } return toReturn.length() > 0 ? toReturn : "anonymous"; } private static native JsArrayString splice(JsArrayString arr, int length) /*-{ (arr.length >= length) && arr.splice(0, length); return arr; }-*/; }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy