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.frame.Frame;
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.object.DynamicObject;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.utilities.TriState;
import com.oracle.truffle.js.lang.JavaScriptLanguage;
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.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;
@ImportStatic({JSConfig.class})
@ExportLibrary(InteropLibrary.class)
public abstract class GraalJSException extends AbstractTruffleException {
private static final long serialVersionUID = -6624166672101791072L;
private static final JSStackTraceElement[] EMPTY_STACK_TRACE = new JSStackTraceElement[0];
private JSStackTraceElement[] jsStackTrace;
private Object location;
private int stackTraceLimit;
private static final String DYNAMIC_FUNCTION_NAME = "anonymous";
protected GraalJSException(String message, Throwable cause, Node node, int stackTraceLimit) {
super(message, cause, truffleStackTraceLimit(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, truffleStackTraceLimit(stackTraceLimit), node);
this.location = node;
this.stackTraceLimit = stackTraceLimit;
this.jsStackTrace = stackTraceLimit == 0 ? EMPTY_STACK_TRACE : null;
}
protected GraalJSException(String message, SourceSection location, int stackTraceLimit) {
super(message, null, truffleStackTraceLimit(stackTraceLimit), null);
this.location = location;
this.stackTraceLimit = stackTraceLimit;
this.jsStackTrace = stackTraceLimit == 0 ? EMPTY_STACK_TRACE : null;
}
private static int truffleStackTraceLimit(int stackTraceLimit) {
return stackTraceLimit <= 0 ? 0 : UNLIMITED_STACK_TRACE;
}
protected static <T extends GraalJSException> T fillInStackTrace(T exception, DynamicObject skipFramesUpTo, boolean capture) {
exception.fillInStackTrace(skipFramesUpTo, capture);
return exception;
}
protected final GraalJSException fillInStackTrace(DynamicObject skipFramesUpTo, boolean capture) {
assert capture || skipFramesUpTo == Undefined.instance;
assert jsStackTrace == (stackTraceLimit == 0 ? EMPTY_STACK_TRACE : null);
if (capture || JSConfig.EagerStackTrace) {
if (stackTraceLimit > 0) {
this.jsStackTrace = getJSStackTrace(skipFramesUpTo);
}
}
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;
}
public abstract Object getErrorObject();
public Object getErrorObjectEager(@SuppressWarnings("unused") JSContext context) {
return getErrorObject();
}
public Object getErrorObjectEager() {
return getErrorObject();
}
public JSStackTraceElement[] getJSStackTrace() {
if (jsStackTrace != null) {
return jsStackTrace;
}
jsStackTrace = materializeJSStackTrace();
return jsStackTrace;
}
@TruffleBoundary
private JSStackTraceElement[] materializeJSStackTrace() {
return getJSStackTrace(Undefined.instance);
}
@TruffleBoundary
private JSStackTraceElement[] getJSStackTrace(DynamicObject skipUpTo) {
assert stackTraceLimit > 0;
JSContext context = JavaScriptLanguage.getCurrentJSRealm().getContext();
boolean nashornMode = context.isOptionNashornCompatibilityMode();
DynamicObject skipFramesUpTo = nashornMode ? Undefined.instance : skipUpTo;
List<TruffleStackTraceElement> stackTrace = TruffleStackTrace.getStackTrace(this);
if (stackTrace == null) {
return EMPTY_STACK_TRACE;
}
FrameVisitorImpl visitor = new FrameVisitorImpl(getLocation(), stackTraceLimit, skipFramesUpTo, nashornMode);
boolean asyncStackTraces = context.isOptionAsyncStackTraces();
List<List<TruffleStackTraceElement>> asyncStacks = null;
for (TruffleStackTraceElement element : stackTrace) {
if (!visitor.visitFrame(element)) {
asyncStacks = null;
break;
}
if (asyncStackTraces) {
List<TruffleStackTraceElement> 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<TruffleStackTraceElement> 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<TruffleStackTraceElement> getAsynchronousStackTrace(TruffleStackTraceElement element) {
if (element.getFrame() == null) {
return null;
}
RootNode rootNode = element.getTarget().getRootNode();
if (rootNode.getLanguageInfo() == null) {
return null;
}
if (rootNode instanceof JavaScriptRootNode) {
if (rootNode instanceof PromiseReactionJobRootNode) {
return JavaScriptRootNode.findAsynchronousFrames((JavaScriptRootNode) rootNode, element.getFrame());
} else {
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.getCurrentJSRealm().getContext().getContextOptions().getStackTraceLimit();
return UserScriptException.createCapture("", originatingNode, stackTraceLimit, Undefined.instance).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<JSStackTraceElement> stackTrace = new ArrayList<>();
private final Node originatingNode;
private final int stackTraceLimit;
private final DynamicObject skipFramesUpTo;
private final boolean inNashornMode;
private boolean inStrictMode;
private boolean skippingFrames;
private boolean first = true;
boolean async;
FrameVisitorImpl(Node originatingNode, int stackTraceLimit, DynamicObject 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);
}
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()) {
if (element.getFrame().getArguments()[0] instanceof Frame) {
Frame frame = (Frame) element.getFrame().getArguments()[0];
arguments = frame.getArguments();
} else {
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)) {
DynamicObject function = (DynamicObject) functionObj;
JSFunctionData functionData = JSFunction.getFunctionData(function);
if (functionData.isBuiltin()) {
if (JSFunction.isStrictBuiltin(function)) {
inStrictMode = true;
}
} else if (functionData.isStrict()) {
inStrictMode = true;
}
if (skippingFrames && function == skipFramesUpTo) {
skippingFrames = false;
return true;
}
JSRealm realm = JSFunction.getRealm(function);
if (JSFunction.isBuiltinThatShouldNotAppearInStackTrace(realm, function)) {
return true;
}
if (!skippingFrames) {
if (functionData.isAsync() && !functionData.isGenerator() && JSRuntime.isJSFunctionRootNode(rootNode)) {
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<JSStackTraceElement> getStackTrace() {
return stackTrace;
}
}
private static JSStackTraceElement processJSFrame(RootNode rootNode, Node node, Object thisObj, DynamicObject 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();
String fileName = getFileName(source);
String functionName;
if (JSFunction.isBuiltin(functionObj)) {
functionName = JSFunction.getName(functionObj);
} else {
functionName = rootNode.getName();
}
boolean eval = false;
if (isEvalSource(source)) {
functionName = "eval";
eval = true;
} else if (functionName == null || isInternalFunctionName(functionName)) {
functionName = "";
}
SourceSection targetSourceSection = null;
if (!inNashornMode) {
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));
return new JSStackTraceElement(fileName, functionName, callNodeSourceSection, thisObj, functionObj, targetSourceSection, inStrictMode, eval, global, inNashornMode, async, promiseIndex);
}
private static boolean isEvalSource(Source source) {
return source != null && source.getName().startsWith(Evaluator.EVAL_AT_SOURCE_NAME_PREFIX);
}
private static boolean isInternalFunctionName(String functionName) {
return functionName.length() >= 1 && functionName.charAt(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) {
return null;
}
String fileName = getFileName(sourceSection.getSource());
String functionName = rootNode.getName();
Object thisObj = null;
Object functionObj = null;
return new JSStackTraceElement(fileName, functionName, sourceSection, thisObj, functionObj, null, strict, false, false, inNashornMode, async, -1);
}
private static String getPrimitiveConstructorName(Object thisObj) {
assert JSRuntime.isJSPrimitive(thisObj);
if (thisObj instanceof Boolean) {
return "Boolean";
} else if (JSRuntime.isNumber(thisObj)) {
return "Number";
} else if (JSRuntime.isString(thisObj)) {
return "String";
} else if (thisObj instanceof Symbol) {
return "Symbol";
}
return null;
}
private static int sourceSectionOffset(SourceSection callNodeSourceSection, SourceSection targetSourceSection) {
int offset = 0;
String code = callNodeSourceSection.getCharacters().toString();
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);
}
}
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 String getFileName(Source source) {
return source != null ? source.getName() : "<unknown>";
}
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("thisLib") InteropLibrary thisLib,
@CachedLibrary(limit = "InteropLibraryLimit") @Shared("otherLib") InteropLibrary otherLib) {
if (receiver == other) {
return TriState.TRUE;
}
Object thisObj = receiver.getErrorObject();
if (thisObj == null) {
return TriState.FALSE;
}
Object otherObj = other.getErrorObject();
if (otherObj == null) {
return TriState.FALSE;
}
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.getErrorObject();
if (thisObj == null) {
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("thisLib") InteropLibrary thisLib,
@CachedLibrary(limit = "InteropLibraryLimit") @Shared("otherLib") InteropLibrary otherLib) {
Object thisObj = receiver.getErrorObject();
if (thisObj == null) {
return (other instanceof JSDynamicObject) ? TriState.FALSE : TriState.UNDEFINED;
}
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") InteropLibrary delegateLib) throws UnsupportedMessageException {
return delegateLib.identityHashCode(getErrorObjectEager());
}
public static final class JSStackTraceElement {
private final String fileName;
private final String 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 int promiseIndex;
private JSStackTraceElement(String fileName, String functionName, SourceSection sourceSection, Object thisObj, Object functionObj, SourceSection targetSourceSection, boolean strict,
boolean eval, boolean global, boolean inNashornMode, boolean async, 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.promiseIndex = promiseIndex;
}
@TruffleBoundary
public String getFileName() {
if (fileName.startsWith(Evaluator.EVAL_AT_SOURCE_NAME_PREFIX)) {
return Evaluator.EVAL_SOURCE_NAME;
}
return fileName;
}
public String getClassName() {
return getTypeName(false);
}
public String getTypeName() {
return getTypeName(true);
}
@TruffleBoundary
public String getTypeName(boolean checkGlobal) {
if (inNashornMode) {
return "<" + fileName + ">";
} else {
if (checkGlobal && global) {
return "global";
}
Object thisObject = getThis();
if (thisObject == JSFunction.CONSTRUCT) {
return getFunctionName();
} else if (!JSRuntime.isNullOrUndefined(thisObject) && !global) {
if (JSDynamicObject.isJSDynamicObject(thisObject)) {
return JSRuntime.getConstructorName((DynamicObject) thisObject);
} else if (JSRuntime.isJSPrimitive(thisObject)) {
return getPrimitiveConstructorName(thisObject);
}
}
return null;
}
}
@TruffleBoundary
public String getFunctionName() {
if (JSFunction.isJSFunction(functionObj)) {
String dynamicName = findFunctionName((DynamicObject) functionObj);
if (dynamicName != null && !dynamicName.isEmpty() &&
(!isEval() || !dynamicName.equals(DYNAMIC_FUNCTION_NAME) || !JSObject.getJSContext((DynamicObject) functionObj).isOptionV8CompatibilityMode())) {
return dynamicName;
}
}
return functionName;
}
private static String findFunctionName(DynamicObject functionObj) {
assert JSFunction.isJSFunction(functionObj);
PropertyDescriptor desc = JSObject.getOwnProperty(functionObj, JSFunction.NAME);
if (desc != null) {
if (desc.isDataDescriptor()) {
Object name = desc.getValue();
if (JSRuntime.isString(name)) {
return JSRuntime.javaToString(name);
}
}
}
return null;
}
@TruffleBoundary
public String getMethodName() {
return getMethodName(JavaScriptLanguage.getCurrentJSRealm().getContext());
}
@TruffleBoundary
public String 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;
}
DynamicObject receiver = (DynamicObject) thisObj;
DynamicObject function = (DynamicObject) functionObj;
if (functionName != null && !functionName.isEmpty()) {
String name = findMethodPropertyNameByFunctionName(receiver, functionName, function);
if (name != null) {
return name;
}
}
return findMethodPropertyName(receiver, function);
}
private static String findMethodPropertyNameByFunctionName(DynamicObject receiver, String functionName, DynamicObject functionObj) {
String propertyName = functionName;
boolean accessor = false;
if (propertyName.startsWith("get ") || propertyName.startsWith("set ")) {
propertyName = propertyName.substring(4);
accessor = true;
}
if (propertyName.isEmpty()) {
return null;
}
for (DynamicObject 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 String findMethodPropertyName(DynamicObject receiver, DynamicObject functionObj) {
String name = null;
for (DynamicObject current = receiver; current != Null.instance && !JSProxy.isJSProxy(current); current = JSObject.getPrototype(current)) {
for (String 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;
}
}
}
}
return name;
}
@TruffleBoundary
public int getLineNumber() {
if (sourceSection == null) {
return -1;
}
int lineNumber = sourceSection.getStartLine();
if (!inNashornMode && targetSourceSection != null) {
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 String getLine() {
int lineNumber = getLineNumber();
if (sourceSection == null || sourceSection.getSource() == null || lineNumber <= 0) {
return "<unknown>";
}
return sourceSection.getSource().getCharacters(lineNumber).toString();
}
@TruffleBoundary
public int getColumnNumber() {
if (sourceSection == null) {
return -1;
}
int columnNumber = sourceSection.getStartColumn();
if (!inNashornMode && targetSourceSection != null) {
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 Object getThis() {
return thisObj;
}
@TruffleBoundary
public Object getThisOrGlobal() {
if (global) {
if (JSRuntime.isNullOrUndefined(thisObj)) {
return JSFunction.getRealm((DynamicObject) functionObj).getGlobalObject();
} else {
assert thisObj == JSFunction.getRealm((DynamicObject) 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((DynamicObject) thisObj, JSObject.CONSTRUCTOR);
return constructor != null && constructor == functionObj;
}
return false;
}
public boolean isEval() {
return eval;
}
@TruffleBoundary
public String getEvalOrigin() {
if (fileName.startsWith("<")) {
return null;
}
return fileName;
}
public int getPromiseIndex() {
return promiseIndex;
}
public boolean isPromiseAll() {
return promiseIndex >= 0;
}
public boolean isAsync() {
return async;
}
@TruffleBoundary
@Override
public String toString() {
JSContext context = JavaScriptLanguage.getCurrentJSRealm().getContext();
return toString(context);
}
@TruffleBoundary
public String toString(JSContext context) {
StringBuilder builder = new StringBuilder();
if (isPromiseAll()) {
builder.append("async").append(' ').append("Promise.all").append(" (").append("index").append(' ').append(promiseIndex).append(")");
return builder.toString();
}
String className = getClassName();
String methodName = JSError.correctMethodName(getFunctionName(), context);
if (methodName == null || methodName.isEmpty()) {
String 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) {
builder.append("async").append(' ');
}
if (className != null) {
if (className.equals(methodName)) {
if (isConstructor()) {
builder.append("new ");
}
} else {
builder.append(className).append('.');
}
}
builder.append(methodName);
builder.append(" (");
}
if (JSFunction.isBuiltinSourceSection(sourceSection)) {
builder.append("native");
} else {
String evalOrigin = getEvalOrigin();
String sourceName = evalOrigin != null ? evalOrigin : getFileName();
builder.append(sourceName);
if (eval) {
builder.append(", <anonymous>");
}
builder.append(":");
builder.append(getLineNumber());
builder.append(":");
builder.append(getColumnNumber());
}
if (includeMethodName) {
builder.append(")");
}
return builder.toString();
}
}
}