com.oracle.truffle.js.runtime.GraalJSException Maven / Gradle / Ivy
/*
* Copyright (c) 2018, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.js.runtime;
import java.util.ArrayList;
import java.util.List;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.RootCallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.TruffleStackTraceElement;
import com.oracle.truffle.api.dsl.Cached.Shared;
import com.oracle.truffle.api.dsl.ImportStatic;
import com.oracle.truffle.api.dsl.Specialization;
import com.oracle.truffle.api.exception.AbstractTruffleException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.library.CachedLibrary;
import com.oracle.truffle.api.library.ExportLibrary;
import com.oracle.truffle.api.library.ExportMessage;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.api.utilities.TriState;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
import com.oracle.truffle.js.nodes.function.FunctionRootNode;
import com.oracle.truffle.js.nodes.promise.PerformPromiseAllNode.PromiseAllMarkerRootNode;
import com.oracle.truffle.js.nodes.promise.PromiseReactionJobNode.PromiseReactionJobRootNode;
import com.oracle.truffle.js.runtime.builtins.JSError;
import com.oracle.truffle.js.runtime.builtins.JSFunction;
import com.oracle.truffle.js.runtime.builtins.JSFunctionData;
import com.oracle.truffle.js.runtime.builtins.JSFunctionObject;
import com.oracle.truffle.js.runtime.builtins.JSProxy;
import com.oracle.truffle.js.runtime.objects.JSDynamicObject;
import com.oracle.truffle.js.runtime.objects.JSObject;
import com.oracle.truffle.js.runtime.objects.Null;
import com.oracle.truffle.js.runtime.objects.PropertyDescriptor;
import com.oracle.truffle.js.runtime.objects.Undefined;
@SuppressWarnings("serial")
@ImportStatic({JSConfig.class})
@ExportLibrary(InteropLibrary.class)
public abstract class GraalJSException extends AbstractTruffleException {
private static final JSStackTraceElement[] EMPTY_STACK_TRACE = new JSStackTraceElement[0];
private JSStackTraceElement[] jsStackTrace;
private Object location;
private int stackTraceLimit;
protected GraalJSException(String message, Throwable cause, Node node, int stackTraceLimit) {
super(message, cause, stackTraceLimit, node);
this.location = node;
this.stackTraceLimit = stackTraceLimit;
this.jsStackTrace = stackTraceLimit == 0 ? EMPTY_STACK_TRACE : null;
}
protected GraalJSException(String message, Node node, int stackTraceLimit) {
super(message, null, stackTraceLimit, node);
this.location = node;
this.stackTraceLimit = stackTraceLimit;
this.jsStackTrace = stackTraceLimit == 0 ? EMPTY_STACK_TRACE : null;
}
protected GraalJSException(String message, Throwable cause, SourceSection location, int stackTraceLimit) {
super(message, cause, stackTraceLimit, null);
this.location = location;
this.stackTraceLimit = stackTraceLimit;
this.jsStackTrace = stackTraceLimit == 0 ? EMPTY_STACK_TRACE : null;
}
protected static T fillInStackTrace(T exception, boolean capture, JSDynamicObject skipFramesUpTo, boolean customSkip) {
exception.fillInStackTrace(capture, skipFramesUpTo, customSkip);
return exception;
}
protected static T fillInStackTrace(T exception, boolean capture) {
exception.fillInStackTrace(capture, Undefined.instance, false);
return exception;
}
protected final GraalJSException fillInStackTrace(boolean capture, JSDynamicObject skipFramesUpTo, boolean customSkip) {
// We can only skip frames when capturing eagerly.
assert capture || skipFramesUpTo == Undefined.instance;
assert jsStackTrace == (stackTraceLimit == 0 ? EMPTY_STACK_TRACE : null);
if (capture || JSConfig.EagerStackTrace) {
if (stackTraceLimit > 0) {
this.jsStackTrace = getJSStackTrace(skipFramesUpTo, customSkip);
}
}
return this;
}
@ExportMessage
public boolean hasSourceLocation() {
if (location instanceof SourceSection) {
return true;
}
Node locationNode = getLocation();
SourceSection sourceSection = locationNode != null ? locationNode.getEncapsulatingSourceSection() : null;
return sourceSection != null;
}
@ExportMessage(name = "getSourceLocation")
public SourceSection getSourceLocationInterop() throws UnsupportedMessageException {
if (location instanceof SourceSection) {
return (SourceSection) location;
}
Node locationNode = getLocation();
SourceSection sourceSection = locationNode != null ? locationNode.getEncapsulatingSourceSection() : null;
if (sourceSection == null) {
throw UnsupportedMessageException.create();
}
return sourceSection;
}
/** Could still be null due to lazy initialization. */
public abstract Object getErrorObjectLazy();
/**
* Eager access to the ErrorObject. Use only if you must get a non-null error object.
*/
public abstract Object getErrorObject();
public JSStackTraceElement[] getJSStackTrace() {
if (jsStackTrace != null) {
return jsStackTrace;
}
jsStackTrace = materializeJSStackTrace();
return jsStackTrace;
}
@TruffleBoundary
private JSStackTraceElement[] materializeJSStackTrace() {
return getJSStackTrace(Undefined.instance, false);
}
@TruffleBoundary
private JSStackTraceElement[] getJSStackTrace(JSDynamicObject skipUpTo, boolean customSkip) {
assert stackTraceLimit > 0;
JSContext context = JavaScriptLanguage.getCurrentLanguage().getJSContext();
boolean nashornMode = context.isOptionNashornCompatibilityMode();
// Nashorn does not support skipping of frames
JSDynamicObject skipFramesUpTo = nashornMode ? Undefined.instance : skipUpTo;
boolean skippingFrames = JSFunction.isJSFunction(skipFramesUpTo);
if (skippingFrames && customSkip) {
FunctionRootNode.setOmitFromStackTrace(JSFunction.getFunctionData((JSFunctionObject) skipFramesUpTo));
}
List stackTrace = TruffleStackTrace.getStackTrace(this);
if (skippingFrames && customSkip) {
FunctionRootNode.setOmitFromStackTrace(null);
}
if (stackTrace == null) {
return EMPTY_STACK_TRACE;
}
FrameVisitorImpl visitor = new FrameVisitorImpl(getLocation(), stackTraceLimit, skipFramesUpTo, nashornMode);
boolean asyncStackTraces = context.isOptionAsyncStackTraces();
List> asyncStacks = null;
for (TruffleStackTraceElement element : stackTrace) {
if (!visitor.visitFrame(element)) {
asyncStacks = null;
break;
}
if (asyncStackTraces) {
List asyncStack = getAsynchronousStackTrace(element);
if (asyncStack != null && !asyncStack.isEmpty()) {
if (asyncStacks == null) {
asyncStacks = new ArrayList<>();
}
asyncStacks.add(asyncStack);
}
}
}
if (asyncStacks != null && !asyncStacks.isEmpty()) {
out: for (List asyncStack : asyncStacks) {
visitor.async = true;
for (TruffleStackTraceElement element : asyncStack) {
if (!visitor.visitFrame(element)) {
break out;
}
}
}
}
return visitor.getStackTrace().toArray(EMPTY_STACK_TRACE);
}
private static List getAsynchronousStackTrace(TruffleStackTraceElement element) {
if (element.getFrame() == null) {
// getAsynchronousStackTrace requires a frame.
return null;
}
RootNode rootNode = element.getTarget().getRootNode();
if (rootNode.getLanguageInfo() == null) {
// getAsynchronousStackTrace requires the RootNode to have language info.
return null;
}
if (rootNode instanceof JavaScriptRootNode) {
if (rootNode instanceof PromiseReactionJobRootNode) {
return JavaScriptRootNode.findAsynchronousFrames((JavaScriptRootNode) rootNode, element.getFrame());
} else {
// We do not want to include any of the extra stack trace elements available when
// getAsynchronousStackDepth() > 0.
return null;
}
}
return TruffleStackTrace.getAsynchronousStackTrace(element.getTarget(), element.getFrame());
}
public void setJSStackTrace(JSStackTraceElement[] jsStackTrace) {
this.jsStackTrace = jsStackTrace;
}
@TruffleBoundary
public static JSStackTraceElement[] getJSStackTrace(Node originatingNode) {
int stackTraceLimit = JavaScriptLanguage.get(originatingNode).getJSContext().getLanguageOptions().stackTraceLimit();
return getJSStackTrace(originatingNode, stackTraceLimit);
}
@TruffleBoundary
public static JSStackTraceElement[] getJSStackTrace(Node originatingNode, int stackTraceLimit) {
return UserScriptException.createCapture("", originatingNode, stackTraceLimit).getJSStackTrace();
}
private static final class FrameVisitorImpl {
private static final int STACK_FRAME_SKIP = 0;
private static final int STACK_FRAME_JS = 1;
private static final int STACK_FRAME_FOREIGN = 2;
private final List stackTrace = new ArrayList<>();
private final Node originatingNode;
private final int stackTraceLimit;
private final JSDynamicObject skipFramesUpTo;
private final boolean inNashornMode;
private boolean inStrictMode;
private boolean skippingFrames;
private boolean first = true;
boolean async;
FrameVisitorImpl(Node originatingNode, int stackTraceLimit, JSDynamicObject skipFramesUpTo, boolean nashornMode) {
this.originatingNode = originatingNode;
this.stackTraceLimit = stackTraceLimit;
this.skipFramesUpTo = skipFramesUpTo;
this.skippingFrames = (skipFramesUpTo != Undefined.instance);
this.inNashornMode = nashornMode;
}
private int stackFrameType(Node callNode) {
if (callNode == null) {
return STACK_FRAME_SKIP;
}
SourceSection sourceSection = callNode.getEncapsulatingSourceSection();
if (sourceSection == null) {
return STACK_FRAME_SKIP;
}
if (JSFunction.isBuiltinSourceSection(sourceSection)) {
return inNashornMode ? STACK_FRAME_SKIP : STACK_FRAME_JS;
}
if (sourceSection.getSource().isInternal() || !sourceSection.isAvailable()) {
return STACK_FRAME_SKIP;
}
if (JSRuntime.isJSRootNode(callNode.getRootNode())) {
return STACK_FRAME_JS;
} else {
return STACK_FRAME_FOREIGN;
}
}
private static RootNode rootNode(TruffleStackTraceElement element) {
CallTarget callTarget = element.getTarget();
return (callTarget instanceof RootCallTarget) ? ((RootCallTarget) callTarget).getRootNode() : null;
}
public boolean visitFrame(TruffleStackTraceElement element) {
Node callNode = element.getLocation();
if (first) {
first = false;
if (JSRuntime.isJSRootNode(rootNode(element))) {
callNode = originatingNode;
}
}
if (callNode == null) {
callNode = rootNode(element);
}
// this check for code style analyzers
if (callNode != null) {
switch (stackFrameType(callNode)) {
case STACK_FRAME_JS: {
RootNode rootNode = callNode.getRootNode();
assert JSRuntime.isJSRootNode(rootNode);
final Object[] arguments;
int promiseIndex = -1;
if (element.getFrame() == null) {
break;
} else if (JSRuntime.isJSFunctionRootNode(rootNode)) {
arguments = element.getFrame().getArguments();
} else if (((JavaScriptRootNode) rootNode).isResumption()) {
arguments = element.getFrame().getArguments();
} else if (rootNode instanceof PromiseAllMarkerRootNode) {
arguments = element.getFrame().getArguments();
if (JSArguments.getUserArgumentCount(arguments) > 0) {
Object promiseIndexArg = JSArguments.getUserArgument(arguments, 0);
if (promiseIndexArg instanceof Integer) {
promiseIndex = (int) promiseIndexArg;
}
}
} else {
break;
}
Object thisObj = JSArguments.getThisObject(arguments);
Object functionObj = JSArguments.getFunctionObject(arguments);
if (JSFunction.isJSFunction(functionObj)) {
JSFunctionObject function = (JSFunctionObject) functionObj;
JSFunctionData functionData = JSFunction.getFunctionData(function);
if (functionData.isBuiltin()) {
if (JSFunction.isStrictBuiltin(function, JSRealm.get(null))) {
inStrictMode = true;
}
} else if (functionData.isStrict()) {
inStrictMode = true;
}
if (skippingFrames && function == skipFramesUpTo) {
skippingFrames = false;
return true; // skip this frame as well
}
JSRealm realm = JSFunction.getRealm(function);
if (JSFunction.isBuiltinThatShouldNotAppearInStackTrace(realm, function)) {
return true;
}
if (!skippingFrames) {
if (functionData.isAsync() && !functionData.isGenerator() && JSRuntime.isJSFunctionRootNode(rootNode)) {
// async function calls produce two frames, skip one
return true;
}
stackTrace.add(processJSFrame(rootNode, callNode, thisObj, function, inStrictMode, inNashornMode, async, promiseIndex));
}
}
break;
}
case STACK_FRAME_FOREIGN:
if (!skippingFrames) {
JSStackTraceElement elem = processForeignFrame(callNode, inStrictMode, inNashornMode, async);
if (elem != null) {
stackTrace.add(elem);
}
}
break;
}
}
return stackTrace.size() < stackTraceLimit;
}
public List getStackTrace() {
return stackTrace;
}
}
private static JSStackTraceElement processJSFrame(RootNode rootNode, Node node, Object thisObj, JSFunctionObject functionObj, boolean inStrictMode, boolean inNashornMode, boolean async,
int promiseIndex) {
Node callNode = node;
while (callNode.getSourceSection() == null) {
callNode = callNode.getParent();
}
SourceSection callNodeSourceSection = callNode.getSourceSection();
Source source = callNodeSourceSection.getSource();
TruffleString fileName = getFileName(source);
TruffleString functionName;
if (JSFunction.isBuiltin(functionObj)) {
functionName = JSFunction.getName(functionObj);
} else if (rootNode instanceof FunctionRootNode) {
functionName = ((FunctionRootNode) rootNode).getNameTString();
} else {
functionName = Strings.fromJavaString(rootNode.getName());
}
boolean eval = false;
if (isEvalSource(source)) {
functionName = Strings.EVAL;
eval = true;
} else if (functionName == null || isInternalFunctionName(functionName)) {
functionName = Strings.EMPTY_STRING;
}
SourceSection targetSourceSection = null;
if (!inNashornMode) { // for V8
if (callNode instanceof JavaScriptFunctionCallNode) {
Node target = ((JavaScriptFunctionCallNode) callNode).getTarget();
targetSourceSection = target == null ? null : target.getSourceSection();
}
}
boolean global = (JSRuntime.isNullOrUndefined(thisObj) && !JSFunction.isStrict(functionObj)) || isGlobalObject(thisObj, JSFunction.getRealm(functionObj));
boolean hasPath = source.getPath() != null;
return new JSStackTraceElement(fileName, functionName, callNodeSourceSection, thisObj, functionObj, targetSourceSection,
inStrictMode, eval, global, inNashornMode, async, hasPath, promiseIndex);
}
private static boolean isEvalSource(Source source) {
return source.getName().startsWith(Evaluator.EVAL_AT_SOURCE_NAME_PREFIX);
}
private static boolean isInternalFunctionName(TruffleString functionName) {
return Strings.length(functionName) >= 1 && Strings.charAt(functionName, 0) == ':';
}
private static boolean isGlobalObject(Object object, JSRealm realm) {
return JSDynamicObject.isJSDynamicObject(object) && (realm != null) && (realm.getGlobalObject() == object);
}
private static JSStackTraceElement processForeignFrame(Node node, boolean strict, boolean inNashornMode, boolean async) {
RootNode rootNode = node.getRootNode();
SourceSection sourceSection = rootNode.getSourceSection();
if (sourceSection == null) {
// can happen around FastR root nodes, see GR-6604
return null;
}
Source source = sourceSection.getSource();
TruffleString fileName = getFileName(source);
TruffleString functionName = Strings.fromJavaString(rootNode.getName());
Object thisObj = null;
Object functionObj = null;
boolean hasPath = source.getPath() != null;
return new JSStackTraceElement(fileName, functionName, sourceSection, thisObj, functionObj, null, strict, false, false, inNashornMode, async, hasPath, -1);
}
private static TruffleString getPrimitiveConstructorName(Object thisObj) {
assert JSRuntime.isJSPrimitive(thisObj);
if (thisObj instanceof Boolean) {
return Strings.UC_BOOLEAN;
} else if (JSRuntime.isNumber(thisObj) || thisObj instanceof Long) {
return Strings.UC_NUMBER;
} else if (Strings.isTString(thisObj)) {
return Strings.UC_STRING;
} else if (thisObj instanceof Symbol) {
return Strings.UC_SYMBOL;
}
return null;
}
private static int sourceSectionOffset(SourceSection callNodeSourceSection, SourceSection targetSourceSection) {
int offset = 0;
String code = callNodeSourceSection.getCharacters().toString();
// skip code for the target
if (targetSourceSection != null) {
String targetCode = targetSourceSection.getCharacters().toString();
int index = code.indexOf(targetCode);
if (index != -1) {
index += targetCode.length();
offset += index;
code = code.substring(index);
}
}
// column number corresponds to the function invocation (left
// parenthesis) unless it is preceded by an identifier (column
// number is the beginning of the identified then)
int index = code.indexOf('(');
if (index != -1) {
index--;
int i = index;
while (i >= 0 && Character.isWhitespace(code.charAt(i))) {
i--;
}
if (i >= 0 && Character.isJavaIdentifierPart(code.charAt(i))) {
do {
i--;
} while (i >= 0 && Character.isJavaIdentifierPart(code.charAt(i)));
index = i;
}
offset += index + 1;
}
return offset;
}
private static TruffleString getFileName(Source source) {
String fileName = source.getPath();
if (fileName == null) {
fileName = source.getName();
}
return Strings.fromJavaString(fileName);
}
public void printJSStackTrace() {
System.err.println(getMessage());
for (JSStackTraceElement jsste : jsStackTrace) {
System.err.println(jsste);
}
}
@TruffleBoundary
public static void printJSStackTrace(Node originatingNode) {
JSStackTraceElement[] jsstes = getJSStackTrace(originatingNode);
for (JSStackTraceElement jsste : jsstes) {
System.err.println(jsste);
}
}
@SuppressWarnings("static-method")
@ExportMessage
public final boolean hasLanguage() {
return true;
}
@SuppressWarnings("static-method")
@ExportMessage
public final Class extends TruffleLanguage>> getLanguage() {
return JavaScriptLanguage.class;
}
@ExportMessage
public final Object toDisplayString(boolean allowSideEffects) {
return JSRuntime.toDisplayString(this, allowSideEffects);
}
@ImportStatic({JSConfig.class})
@ExportMessage
public static final class IsIdenticalOrUndefined {
@Specialization
public static TriState doException(GraalJSException receiver, GraalJSException other,
@CachedLibrary(limit = "InteropLibraryLimit") @Shared InteropLibrary thisLib,
@CachedLibrary(limit = "InteropLibraryLimit") @Shared InteropLibrary otherLib) {
if (receiver == other) {
return TriState.TRUE;
}
Object thisObj = receiver.getErrorObjectLazy();
if (thisObj == null) {
// Cannot be identical since this is a lazily allocated Error and receiver != other.
return TriState.FALSE;
}
Object otherObj = other.getErrorObjectLazy();
if (otherObj == null) {
return TriState.FALSE;
}
// If the values are not JS objects, we need to delegate to InteropLibrary.
if (thisLib.hasIdentity(thisObj) && otherLib.hasIdentity(other)) {
return TriState.valueOf(thisLib.isIdentical(thisObj, other, otherLib));
} else {
return TriState.UNDEFINED;
}
}
@Specialization
public static TriState doJSObject(GraalJSException receiver, JSDynamicObject other) {
Object thisObj = receiver.getErrorObjectLazy();
if (thisObj == null) {
// Cannot be identical since this is lazily allocated Error.
return TriState.FALSE;
}
return TriState.valueOf(thisObj == other);
}
@Specialization(guards = {"!isGraalJSException(other)"}, replaces = {"doJSObject"})
public static TriState doOther(GraalJSException receiver, Object other,
@CachedLibrary(limit = "InteropLibraryLimit") @Shared InteropLibrary thisLib,
@CachedLibrary(limit = "InteropLibraryLimit") @Shared InteropLibrary otherLib) {
Object thisObj = receiver.getErrorObjectLazy();
if (thisObj == null) {
// The error object cannot be identical since this is a lazily allocated Error.
// Note: `other` could still be an identity-preserving wrapper of the receiver,
// in which case we must not return FALSE but UNDEFINED.
return (other instanceof JSDynamicObject) ? TriState.FALSE : TriState.UNDEFINED;
}
// If the values are not JS objects, we need to delegate to InteropLibrary.
if (thisLib.hasIdentity(thisObj) && otherLib.hasIdentity(other)) {
return TriState.valueOf(thisLib.isIdentical(thisObj, other, otherLib));
} else {
return TriState.UNDEFINED;
}
}
static boolean isGraalJSException(Object value) {
return value instanceof GraalJSException;
}
}
@ExportMessage
@TruffleBoundary
public final int identityHashCode(
@CachedLibrary(limit = "InteropLibraryLimit") @Shared InteropLibrary thisLib) throws UnsupportedMessageException {
return thisLib.identityHashCode(getErrorObject());
}
public static final class JSStackTraceElement {
private final TruffleString fileName;
private final TruffleString functionName;
private final SourceSection sourceSection;
private final Object thisObj;
private final Object functionObj;
private final SourceSection targetSourceSection;
private final boolean strict;
private final boolean eval;
private final boolean global;
private final boolean inNashornMode;
private final boolean async;
private final boolean hasPath;
private final int promiseIndex;
private JSStackTraceElement(TruffleString fileName, TruffleString functionName, SourceSection sourceSection, Object thisObj, Object functionObj, SourceSection targetSourceSection,
boolean strict, boolean eval, boolean global, boolean inNashornMode, boolean async, boolean hasPath, int promiseIndex) {
CompilerAsserts.neverPartOfCompilation();
this.fileName = fileName;
this.functionName = functionName;
this.sourceSection = sourceSection;
this.thisObj = thisObj;
this.functionObj = functionObj;
this.targetSourceSection = targetSourceSection;
this.strict = strict;
this.eval = eval;
this.global = global;
this.inNashornMode = inNashornMode;
this.async = async;
this.hasPath = hasPath;
this.promiseIndex = promiseIndex;
}
@TruffleBoundary
public TruffleString getFileName() {
if (eval) {
return Evaluator.TS_EVAL_SOURCE_NAME;
}
return fileName;
}
public TruffleString getClassName() {
return getTypeName(false);
}
public TruffleString getTypeName() {
return getTypeName(true);
}
@TruffleBoundary
public TruffleString getTypeName(boolean checkGlobal) {
if (inNashornMode) {
return Strings.concatAll(Strings.ANGLE_BRACKET_OPEN, fileName, Strings.ANGLE_BRACKET_CLOSE);
} else {
if (checkGlobal && global) {
return Strings.GLOBAL;
}
Object thisObject = getThis();
if (thisObject == JSFunction.CONSTRUCT) {
return getFunctionName();
} else if (!JSRuntime.isNullOrUndefined(thisObject) && !global) {
if (JSDynamicObject.isJSDynamicObject(thisObject)) {
return JSRuntime.getConstructorName((JSDynamicObject) thisObject);
} else if (JSRuntime.isJSPrimitive(thisObject)) {
return getPrimitiveConstructorName(thisObject);
}
}
return null;
}
}
@TruffleBoundary
public TruffleString getFunctionName() {
if (JSFunction.isJSFunction(functionObj)) {
TruffleString dynamicName = findFunctionName((JSDynamicObject) functionObj);
// The default name of dynamic functions is "anonymous" as per the spec.
// Yet, in V8 stack traces it is "eval" unless overwritten.
if (dynamicName != null && !Strings.isEmpty(dynamicName) &&
(!isEval() || !Strings.equals(Strings.DYNAMIC_FUNCTION_NAME, dynamicName) || !JSObject.getJSContext((JSDynamicObject) functionObj).isOptionV8CompatibilityMode())) {
return dynamicName;
}
}
return functionName;
}
private static TruffleString findFunctionName(JSDynamicObject functionObj) {
assert JSFunction.isJSFunction(functionObj);
PropertyDescriptor desc = JSObject.getOwnProperty(functionObj, JSFunction.NAME);
if (desc != null) {
if (desc.isDataDescriptor()) {
Object name = desc.getValue();
if (name instanceof TruffleString nameStr) {
return nameStr;
}
}
}
return null;
}
@TruffleBoundary
public String getMethodName() {
return Strings.toJavaString(getMethodName(JavaScriptLanguage.getCurrentLanguage().getJSContext()));
}
@TruffleBoundary
public TruffleString getMethodName(JSContext context) {
if (context.isOptionNashornCompatibilityMode()) {
return JSError.correctMethodName(functionName, context);
}
if (JSRuntime.isNullOrUndefined(thisObj) || !JSDynamicObject.isJSDynamicObject(thisObj)) {
return null;
}
if (!JSFunction.isJSFunction(functionObj)) {
return null;
}
JSDynamicObject receiver = (JSDynamicObject) thisObj;
JSFunctionObject function = (JSFunctionObject) functionObj;
if (functionName != null && !Strings.isEmpty(functionName)) {
TruffleString name = findMethodPropertyNameByFunctionName(receiver, functionName, function);
if (name != null) {
return name;
}
}
return findMethodPropertyName(receiver, function);
}
private static TruffleString findMethodPropertyNameByFunctionName(JSDynamicObject receiver, TruffleString functionName, JSFunctionObject functionObj) {
TruffleString propertyName = functionName;
boolean accessor = false;
if (Strings.startsWith(propertyName, Strings.GET_SPC) || Strings.startsWith(propertyName, Strings.SET_SPC)) {
propertyName = Strings.lazySubstring(propertyName, 4);
accessor = true;
}
if (propertyName.isEmpty()) {
return null;
}
for (JSDynamicObject current = receiver; current != Null.instance && !JSProxy.isJSProxy(current); current = JSObject.getPrototype(current)) {
PropertyDescriptor desc = JSObject.getOwnProperty(current, propertyName);
if (desc != null) {
if (desc.isAccessorDescriptor() == accessor && (desc.getValue() == functionObj || desc.getGet() == functionObj || desc.getSet() == functionObj)) {
return propertyName;
}
break;
}
}
return null;
}
private static TruffleString findMethodPropertyName(JSDynamicObject receiver, JSDynamicObject functionObj) {
TruffleString name = null;
for (JSDynamicObject current = receiver; current != Null.instance && !JSProxy.isJSProxy(current); current = JSObject.getPrototype(current)) {
for (TruffleString key : JSObject.enumerableOwnNames(current)) {
PropertyDescriptor desc = JSObject.getOwnProperty(current, key);
if (desc.getValue() == functionObj || desc.getGet() == functionObj || desc.getSet() == functionObj) {
if (name == null) {
name = key;
} else {
return null; // method name is ambiguous
}
}
}
}
return name;
}
@TruffleBoundary
public int getLineNumber() {
if (sourceSection == null) {
return -1;
}
int lineNumber = sourceSection.getStartLine();
if (!inNashornMode && targetSourceSection != null) {
// for V8
int offset = sourceSectionOffset(sourceSection, targetSourceSection);
CharSequence chars = sourceSection.getCharacters();
for (int pos = 0; pos < offset; pos++) {
if (chars.charAt(pos) == '\n') {
lineNumber++;
}
}
}
return lineNumber;
}
@TruffleBoundary
public TruffleString getLine() {
int lineNumber = getLineNumber();
if (sourceSection == null || sourceSection.getSource() == null || lineNumber <= 0) {
return Strings.UNKNOWN_FILENAME;
}
return Strings.fromJavaString(sourceSection.getSource().getCharacters(lineNumber).toString());
}
@TruffleBoundary
public int getColumnNumber() {
if (sourceSection == null) {
return -1;
}
int columnNumber = sourceSection.getStartColumn();
if (!inNashornMode && targetSourceSection != null) {
// for V8
int offset = sourceSectionOffset(sourceSection, targetSourceSection);
CharSequence chars = sourceSection.getCharacters();
for (int pos = 0; pos < offset; pos++) {
if (chars.charAt(pos) == '\n') {
columnNumber = 1;
} else {
columnNumber++;
}
}
}
return columnNumber;
}
public int getPosition() {
return sourceSection != null ? sourceSection.getCharIndex() : -1;
}
public int getCharLength() {
return sourceSection != null ? sourceSection.getCharLength() : 0;
}
public Object getThis() {
return thisObj;
}
@TruffleBoundary
public Object getThisOrGlobal() {
if (global) {
if (JSRuntime.isNullOrUndefined(thisObj)) {
return JSFunction.getRealm((JSFunctionObject) functionObj).getGlobalObject();
} else {
assert thisObj == JSFunction.getRealm((JSFunctionObject) functionObj).getGlobalObject();
return thisObj;
}
}
return (thisObj == JSFunction.CONSTRUCT) ? Undefined.instance : thisObj;
}
public Object getFunction() {
return functionObj;
}
public boolean isStrict() {
return strict;
}
@TruffleBoundary
public boolean isConstructor() {
if (thisObj == JSFunction.CONSTRUCT) {
return true;
} else if (!JSRuntime.isNullOrUndefined(thisObj) && JSDynamicObject.isJSDynamicObject(thisObj)) {
Object constructor = JSRuntime.getDataProperty((JSDynamicObject) thisObj, JSObject.CONSTRUCTOR);
return constructor != null && constructor == functionObj;
}
return false;
}
public boolean isEval() {
return eval;
}
@TruffleBoundary
public TruffleString getEvalOrigin() {
if (eval && !Strings.startsWith(fileName, Strings.ANGLE_BRACKET_OPEN)) {
return fileName;
}
return null;
}
public int getPromiseIndex() {
return promiseIndex;
}
public boolean isPromiseAll() {
return promiseIndex >= 0;
}
public boolean isAsync() {
return async;
}
public boolean hasPath() {
return hasPath;
}
@TruffleBoundary
@Override
public String toString() {
JSContext context = JavaScriptLanguage.getCurrentJSRealm().getContext();
return Strings.toJavaString(toString(context));
}
@TruffleBoundary
public TruffleString toString(JSContext context) {
var sb = Strings.builderCreate();
if (isPromiseAll()) {
Strings.builderAppend(sb, Strings.ASYNC_PROMISE_ALL_BEGIN);
Strings.builderAppend(sb, promiseIndex);
Strings.builderAppend(sb, Strings.PAREN_CLOSE);
return Strings.builderToString(sb);
}
TruffleString className = getClassName();
TruffleString methodName = JSError.correctMethodName(getFunctionName(), context);
if (methodName == null || Strings.isEmpty(methodName)) {
TruffleString name = getMethodName(context);
if (name == null) {
methodName = JSError.getAnonymousFunctionNameStackTrace(context);
} else {
methodName = name;
}
}
boolean includeMethodName = className != null || !JSError.getAnonymousFunctionNameStackTrace(context).equals(methodName);
if (includeMethodName) {
if (async) {
Strings.builderAppend(sb, Strings.ASYNC_SPC);
}
if (className != null) {
if (className.equals(methodName)) {
if (isConstructor()) {
Strings.builderAppend(sb, Strings.NEW_SPACE);
}
} else {
Strings.builderAppend(sb, className);
Strings.builderAppend(sb, Strings.DOT);
}
}
Strings.builderAppend(sb, methodName);
Strings.builderAppend(sb, Strings.SPACE_PAREN_OPEN);
}
if (JSFunction.isBuiltinSourceSection(sourceSection)) {
Strings.builderAppend(sb, Strings.NATIVE);
} else {
TruffleString evalOrigin = getEvalOrigin();
TruffleString sourceName = evalOrigin != null ? evalOrigin : getFileNameForStackTrace(context);
Strings.builderAppend(sb, sourceName);
if (eval) {
Strings.builderAppend(sb, Strings.COMMA_ANONYMOUS_BRACKETS);
}
Strings.builderAppend(sb, Strings.COLON);
Strings.builderAppend(sb, getLineNumber());
Strings.builderAppend(sb, Strings.COLON);
Strings.builderAppend(sb, getColumnNumber());
}
if (includeMethodName) {
Strings.builderAppend(sb, Strings.PAREN_CLOSE);
}
return Strings.builderToString(sb);
}
public TruffleString getFileNameForStackTrace(JSContext context) {
if (hasPath && context.isOptionNashornCompatibilityMode() && sourceSection != null) {
return Strings.fromJavaString(sourceSection.getSource().getName());
} else {
return getFileName();
}
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy