/* * Copyright (C) 2015 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF * THE POSSIBILITY OF SUCH DAMAGE. */ WebInspector.StackTrace = class StackTrace extends WebInspector.Object { constructor(callFrames, topCallFrameIsBoundary, truncated, parentStackTrace) { super(); console.assert(callFrames && callFrames.every((callFrame) => callFrame instanceof WebInspector.CallFrame)); this._callFrames = callFrames; this._topCallFrameIsBoundary = topCallFrameIsBoundary || false; this._truncated = truncated || false; this._parentStackTrace = parentStackTrace || null; } // Static static fromPayload(target, payload) { let result = null; let previousStackTrace = null; while (payload) { let callFrames = payload.callFrames.map((x) => WebInspector.CallFrame.fromPayload(target, x)); let stackTrace = new WebInspector.StackTrace(callFrames, payload.topCallFrameIsBoundary, payload.truncated); if (!result) result = stackTrace; if (previousStackTrace) previousStackTrace._parentStackTrace = stackTrace; previousStackTrace = stackTrace; payload = payload.parentStackTrace; } return result; } static fromString(target, stack) { let callFrames = WebInspector.StackTrace._parseStackTrace(stack); return WebInspector.StackTrace.fromPayload(target, {callFrames}); } // May produce false negatives; must not produce any false positives. // It may return false on a valid stack trace, but it will never return true on an invalid stack trace. static isLikelyStackTrace(stack) { // This function runs for every logged string. It penalizes the performance. // As most logged strings are not stack traces, exit as early as possible. const smallestPossibleStackTraceLength = "http://a.bc/:9:1".length; if (stack.length < smallestPossibleStackTraceLength.length * 2) return false; const approximateStackLengthOf50Items = 5000; if (stack.length > approximateStackLengthOf50Items) return false; if (/^[^a-z$_]/i.test(stack[0])) return false; const reasonablyLongLineLength = 500; const reasonablyLongNativeMethodLength = 120; const stackTraceLine = `(.{1,${reasonablyLongLineLength}}:\\d+:\\d+|eval code|.{1,${reasonablyLongNativeMethodLength}}@\\[native code\\])`; const stackTrace = new RegExp(`^${stackTraceLine}(\\n${stackTraceLine})*$`, "g"); return stackTrace.test(stack); } static _parseStackTrace(stack) { var lines = stack.split(/\n/g); var result = []; for (var line of lines) { var functionName = ""; var url = ""; var lineNumber = 0; var columnNumber = 0; var atIndex = line.indexOf("@"); if (atIndex !== -1) { functionName = line.slice(0, atIndex); ({url, lineNumber, columnNumber} = WebInspector.StackTrace._parseLocation(line.slice(atIndex + 1))); } else if (line.includes("/")) ({url, lineNumber, columnNumber} = WebInspector.StackTrace._parseLocation(line)); else functionName = line; result.push({functionName, url, lineNumber, columnNumber}); } return result; } static _parseLocation(locationString) { var result = {url: "", lineNumber: 0, columnNumber: 0}; var locationRegEx = /(.+?)(?::(\d+)(?::(\d+))?)?$/; var matched = locationString.match(locationRegEx); if (!matched) return result; result.url = matched[1]; if (matched[2]) result.lineNumber = parseInt(matched[2]); if (matched[3]) result.columnNumber = parseInt(matched[3]); return result; } // Public get callFrames() { return this._callFrames; } get firstNonNativeCallFrame() { for (let frame of this._callFrames) { if (!frame.nativeCode) return frame; } return null; } get firstNonNativeNonAnonymousCallFrame() { for (let frame of this._callFrames) { if (frame.nativeCode) continue; if (frame.sourceCodeLocation) { let sourceCode = frame.sourceCodeLocation.sourceCode; if (sourceCode instanceof WebInspector.Script && sourceCode.anonymous) continue; } return frame; } return null; } get topCallFrameIsBoundary() { return this._topCallFrameIsBoundary; } get truncated() { return this._truncated; } get parentStackTrace() { return this._parentStackTrace; } };