// Copyright (c) 2022, Compiler Explorer Authors // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * 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 THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT HOLDER OR 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. // Based on stack-trace https://github.com/felixge/node-stack-trace type StackFrame = { fileName: string | undefined; lineNumber?: number; functionName?: string; typeName?: string; methodName?: string; columnNumber?: number; native?: boolean; }; enum TraceFormat { V8, Firefox, } export function parse(err: Error) { if (!err.stack) { return []; } let format: TraceFormat; if (typeof window === 'undefined') { // node format = TraceFormat.V8; } else { if (navigator.userAgent.includes('AppleWebKit')) { // Just going with V8 for now... format = TraceFormat.V8; } else if ((window as any).chrome) { format = TraceFormat.V8; } else if (navigator.userAgent.toLowerCase().includes('firefox')) { format = TraceFormat.Firefox; } else { // We'll just default to V8 for now... format = TraceFormat.V8; } } if (format === TraceFormat.V8) { return err.stack .split('\n') .slice(1) .map((line): StackFrame | undefined => { if (/^\s*-{4,}$/.test(line)) { return { fileName: line, }; } const lineMatch = line.match(/at (?:(.+?)\s+\()?(?:(.+?):(\d+)(?::(\d+))?|([^)]+))\)?/); if (!lineMatch) { return; } let object: string | undefined; let method: string | undefined; let functionName: string | undefined; let typeName: string | undefined; let methodName: string | undefined; const isNative = lineMatch[5] === 'native'; if (lineMatch[1]) { functionName = lineMatch[1]; let methodStart = functionName.lastIndexOf('.'); if (functionName[methodStart - 1] === '.') methodStart--; if (methodStart > 0) { object = functionName.substring(0, methodStart); method = functionName.substring(methodStart + 1); const objectEnd = object.indexOf('.Module'); if (objectEnd > 0) { functionName = functionName.substring(objectEnd + 1); object = object.substring(0, objectEnd); } } } if (method) { typeName = object; methodName = method; } if (method === '') { methodName = undefined; functionName = undefined; } return { fileName: lineMatch[2] || undefined, lineNumber: parseInt(lineMatch[3], 10) || undefined, functionName: functionName, typeName: typeName, methodName: methodName, columnNumber: parseInt(lineMatch[4], 10) || undefined, native: isNative, }; }) .filter(frame => frame !== undefined) as StackFrame[]; } else { return err.stack .split('\n') .map((line): StackFrame | undefined => { const lineMatch = line.match(/(.*)@(.*):(\d+):(\d+)/); if (!lineMatch) { return; } let object: string | undefined; let method: string | undefined; let functionName: string | undefined; let typeName: string | undefined; let methodName: string | undefined; if (lineMatch[1]) { functionName = lineMatch[1]; let methodStart = functionName.lastIndexOf('.'); if (functionName[methodStart - 1] === '.') methodStart--; if (methodStart > 0) { object = functionName.substring(0, methodStart); method = functionName.substring(methodStart + 1); const objectEnd = object.indexOf('.Module'); if (objectEnd > 0) { functionName = functionName.substring(objectEnd + 1); object = object.substring(0, objectEnd); } } } if (method) { typeName = object; methodName = method; } if (method === '') { methodName = undefined; functionName = undefined; } return { fileName: lineMatch[2] || undefined, lineNumber: parseInt(lineMatch[3], 10) || undefined, functionName: functionName, typeName: typeName, methodName: methodName, columnNumber: parseInt(lineMatch[4], 10) || undefined, }; }) .filter(frame => frame !== undefined) as StackFrame[]; } }