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

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

Go to download

Vaadin is a web application framework for Rich Internet Applications (RIA). Vaadin enables easy development and maintenance of fast and secure rich web applications with a stunning look and feel and a wide browser support. It features a server-side architecture with the majority of the logic running on the server. Ajax technology is used at the browser-side to ensure a rich and interactive user experience.

There is a newer version: 8.25.2
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 {

  /**
   * Line number used in a stack trace when it is unknown.
   */
  public static final int LINE_NUMBER_UNKNOWN = -1;

  /**
   * Replacement for function names that cannot be extracted from a stack.
   */
  private static final String ANONYMOUS = "anonymous";

  /**
   * 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 { static { increaseChromeStackTraceLimit(); } // TODO(cromwellian) make this a configurable? private static native void increaseChromeStackTraceLimit() /*-{ // 128 seems like a reasonable maximum Error.stackTraceLimit = 128; }-*/; @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 void createStackTrace(JavaScriptException e) { JsArrayString stack = inferFrom(e.getException()); parseStackTrace(e, stack); } @Override public void fillInStackTrace(Throwable t) { JsArrayString stack = StackTraceCreator.createStackTrace(); parseStackTrace(t, stack); } @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 (iOS doesn't). if (stack.get(0).startsWith(ANONYMOUS + "@@")) { stack = splice(stack, 1); } return stack; } } @Override protected String extractName(String fnToString) { String extractedName = ANONYMOUS; String location = ""; if (fnToString.length() == 0) { return extractedName; } String toReturn = fnToString.trim(); // Strip the "at " prefix: if (toReturn.startsWith("at ")) { toReturn = toReturn.substring(3); } // Strip square bracketed items from the end: int index = toReturn.indexOf("["); if (index != -1) { toReturn = toReturn.substring(0, index).trim() + toReturn.substring(toReturn.indexOf("]", index) + 1).trim(); } index = toReturn.indexOf("("); if (index == -1) { // No bracketed items found, try '@' (used by iOS). index = toReturn.indexOf("@"); if (index == -1) { // No bracketed items nor '@' found, hence no function name available location = toReturn; toReturn = ""; } else { location = toReturn.substring(index + 1).trim(); toReturn = toReturn.substring(0, index).trim(); } } else { // Bracketed items found: strip them off, parse location info int closeParen = toReturn.indexOf(")", index); location = toReturn.substring(index + 1, closeParen); toReturn = toReturn.substring(0, index).trim(); } // Strip the Type off t index = toReturn.indexOf('.'); if (index != -1) { toReturn = toReturn.substring(index + 1); } return (toReturn.length() > 0 ? toReturn : ANONYMOUS) + "@@" + location; } protected int replaceIfNoSourceMap(int line) { return line; } @Override protected int toSplice() { return 3; } private void parseStackTrace(Throwable e, JsArrayString stack) { StackTraceElement[] stackTrace = new StackTraceElement[stack.length()]; for (int i = 0, j = stackTrace.length; i < j; i++) { String stackElements[] = stack.get(i).split("@@"); int line = LINE_NUMBER_UNKNOWN; int col = -1; String fileName = "Unknown"; if (stackElements.length == 2 && stackElements[1] != null) { String location = stackElements[1]; // colon between line and column int lastColon = location.lastIndexOf(':'); // colon between file url and line number int endFileUrl = location.lastIndexOf(':', lastColon - 1); fileName = location.substring(0, endFileUrl); if (lastColon != -1 && endFileUrl != -1) { line = parseInt(location.substring(endFileUrl + 1, lastColon)); col = parseInt(location.substring(lastColon + 1)); } } stackTrace[i] = new StackTraceElement("Unknown", stackElements[0], fileName + "@" + col, replaceIfNoSourceMap(line < 0 ? -1 : line)); } e.setStackTrace(stackTrace); } } /** * Subclass that forces reported line numbers to -1 (fetch from symbolMap) if source maps are * disabled. */ static class CollectorChromeNoSourceMap extends CollectorChrome { protected int replaceIfNoSourceMap(int line) { return -1; } } private static native int parseInt(String number) /*-{ return parseInt(number) || -1; }-*/; /** * 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 } } /** * 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("("); int start = fnToString.startsWith("function") ? 8 : 0; if (index == -1) { // Firefox 14 does not include parenthesis and uses '@' symbol instead to terminate symbol index = fnToString.indexOf('@'); /** * Firefox 14 doesn't return strings like 'function()' for anonymous methods, so * we assert a space must trail 'function' keyword for a method named 'functionName', e.g. * functionName:file.js:2 won't accidentally strip off the 'function' prefix which is part * of the name. */ start = fnToString.startsWith("function ") ? 9 : 0; } if (index != -1) { 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