com.oracle.truffle.js.runtime.interop.JSInteropUtil 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.interop;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.interop.ArityException;
import com.oracle.truffle.api.interop.InteropException;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.InvalidArrayIndexException;
import com.oracle.truffle.api.interop.UnknownIdentifierException;
import com.oracle.truffle.api.interop.UnknownKeyException;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.interop.UnsupportedTypeException;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.profiles.BranchProfile;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.api.strings.TruffleString;
import com.oracle.truffle.js.nodes.interop.ExportValueNode;
import com.oracle.truffle.js.nodes.interop.ForeignObjectPrototypeNode;
import com.oracle.truffle.js.nodes.interop.ImportValueNode;
import com.oracle.truffle.js.runtime.BigInt;
import com.oracle.truffle.js.runtime.Errors;
import com.oracle.truffle.js.runtime.JSContext;
import com.oracle.truffle.js.runtime.JSRealm;
import com.oracle.truffle.js.runtime.JSRuntime;
import com.oracle.truffle.js.runtime.Strings;
import com.oracle.truffle.js.runtime.Symbol;
import com.oracle.truffle.js.runtime.builtins.JSAbstractArray;
import com.oracle.truffle.js.runtime.builtins.JSArrayBuffer;
import com.oracle.truffle.js.runtime.builtins.JSArrayBufferObject;
import com.oracle.truffle.js.runtime.builtins.JSError;
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;
/**
* Utility class for interop operations. Provides methods that can be used in Cached annotations of
* the TruffleDSL to create interop nodes just for specific specializations.
*
*/
public final class JSInteropUtil {
private JSInteropUtil() {
// this class should not be instantiated
}
public static long getArraySize(Object foreignObj, InteropLibrary interop, Node originatingNode) {
try {
return interop.getArraySize(foreignObj);
} catch (UnsupportedMessageException e) {
throw Errors.createTypeErrorInteropException(foreignObj, e, "getArraySize", originatingNode);
}
}
public static boolean setArraySize(Object obj, Object value, boolean isStrict, InteropLibrary interop, Node originatingNode, BranchProfile errorBranch) {
long newLen = JSAbstractArray.toArrayLengthOrRangeError(value, originatingNode);
long oldLen;
try {
oldLen = interop.getArraySize(obj);
} catch (UnsupportedMessageException e) {
if (errorBranch != null) {
errorBranch.enter();
}
throw Errors.createTypeErrorInteropException(obj, e, "getArraySize", originatingNode);
}
String message = null;
try {
if (newLen < oldLen) {
message = "removeArrayElement";
for (long idx = oldLen - 1; idx >= newLen; idx--) {
interop.removeArrayElement(obj, idx);
}
} else {
message = "writeArrayElement";
for (long idx = oldLen; idx < newLen; idx++) {
interop.writeArrayElement(obj, idx, Undefined.instance);
}
}
} catch (InteropException e) {
if (isStrict) {
if (errorBranch != null) {
errorBranch.enter();
}
throw Errors.createTypeErrorInteropException(obj, e, message, originatingNode);
} else {
return false;
}
}
return true;
}
@TruffleBoundary
public static Object getOrDefault(JSContext context, Object target, Object propertyKey, Object receiver, Object defaultValue) {
assert JSRuntime.isPropertyKey(propertyKey);
InteropLibrary interop = InteropLibrary.getUncached();
ImportValueNode importValue = ImportValueNode.getUncached();
boolean hasArrayElements = interop.hasArrayElements(target);
if (hasArrayElements && JSRuntime.isArrayIndex(propertyKey)) {
return readArrayElementOrDefault(target, JSRuntime.parseArrayIndexIsIndexRaw(propertyKey), defaultValue, interop, importValue);
}
if (context.getLanguageOptions().hasForeignHashProperties() && interop.hasHashEntries(target)) {
try {
return readHashEntryOrDefault(target, propertyKey, defaultValue, interop, importValue);
} catch (UnknownKeyException ukex) {
// fall through: still need to try members
}
}
if (propertyKey instanceof Symbol) {
return maybeReadFromPrototype(context, target, propertyKey, receiver, defaultValue, interop);
}
TruffleString exportedKeyStr = (TruffleString) propertyKey;
if (hasArrayElements && Strings.equals(JSAbstractArray.LENGTH, exportedKeyStr)) {
return getArraySize(target, interop, null);
}
if (interop.hasMembers(target)) {
Object result = readMemberOrDefault(target, propertyKey, null, interop, importValue);
if (result != null) {
return result;
}
}
return maybeReadFromPrototype(context, target, propertyKey, receiver, defaultValue, interop);
}
private static Object maybeReadFromPrototype(JSContext context, Object truffleObject, Object key, Object receiver, Object defaultValue, InteropLibrary interop) {
if (context.getLanguageOptions().hasForeignObjectPrototype() || key instanceof Symbol || JSInteropUtil.isBoxedPrimitive(truffleObject, interop)) {
JSDynamicObject prototype = ForeignObjectPrototypeNode.getUncached().execute(truffleObject);
return JSObject.getOrDefault(prototype, key, receiver, defaultValue);
} else {
return defaultValue;
}
}
public static Object readMemberOrDefault(Object obj, Object member, Object defaultValue) {
return readMemberOrDefault(obj, member, defaultValue, InteropLibrary.getUncached(), ImportValueNode.getUncached());
}
public static Object readMemberOrDefault(Object obj, Object member, Object defaultValue, InteropLibrary interop, ImportValueNode importValue) {
if (!(member instanceof TruffleString memberName)) {
return defaultValue;
}
try {
return importValue.executeWithTarget(interop.readMember(obj, Strings.toJavaString(memberName)));
} catch (UnknownIdentifierException | UnsupportedMessageException e) {
return defaultValue;
}
}
public static Object readArrayElementOrDefault(Object obj, long index, Object defaultValue, InteropLibrary interop, ImportValueNode importValue) {
try {
return importValue.executeWithTarget(interop.readArrayElement(obj, index));
} catch (InvalidArrayIndexException | UnsupportedMessageException e) {
return defaultValue;
}
}
public static Object readArrayElementOrDefault(Object obj, long index, Object defaultValue) {
return readArrayElementOrDefault(obj, index, defaultValue, InteropLibrary.getUncached(), ImportValueNode.getUncached());
}
private static Object readHashEntryOrDefault(Object obj, Object propertyKey, Object defaultValue, InteropLibrary interop, ImportValueNode importValue) throws UnknownKeyException {
try {
return importValue.executeWithTarget(interop.readHashValue(obj, propertyKey));
} catch (UnsupportedMessageException e) {
return defaultValue;
}
}
@TruffleBoundary
public static boolean set(JSContext context, Object target, Object propertyKey, Object value, boolean strict) {
assert JSRuntime.isPropertyKey(propertyKey);
InteropLibrary interop = InteropLibrary.getUncached();
ExportValueNode exportValue = ExportValueNode.getUncached();
boolean hasArrayElements = interop.hasArrayElements(target);
if (hasArrayElements && JSRuntime.isArrayIndex(propertyKey)) {
return writeArrayElement(target, JSRuntime.parseArrayIndexIsIndexRaw(propertyKey), value, interop, exportValue, strict);
}
if (context.getLanguageOptions().hasForeignHashProperties() && interop.hasHashEntries(target)) {
return writeHashEntry(target, propertyKey, value, interop, exportValue, strict);
}
if (propertyKey instanceof Symbol) {
return false;
}
TruffleString stringKey = (TruffleString) propertyKey;
if (hasArrayElements && Strings.equals(JSAbstractArray.LENGTH, stringKey)) {
return setArraySize(target, value, strict, interop, null, null);
}
return writeMember(target, propertyKey, value, interop, exportValue, strict, null);
}
private static boolean writeArrayElement(Object obj, long index, Object value, InteropLibrary interop, ExportValueNode exportValue, boolean strict) {
try {
interop.writeArrayElement(obj, index, exportValue.execute(value));
return true;
} catch (InvalidArrayIndexException | UnsupportedTypeException | UnsupportedMessageException e) {
if (strict) {
throw Errors.createTypeErrorInteropException(obj, e, "writeArrayElement", null);
}
return false;
}
}
private static boolean writeHashEntry(Object obj, Object propertyKey, Object value, InteropLibrary interop, ExportValueNode exportValue, boolean strict) {
try {
interop.writeHashEntry(obj, propertyKey, exportValue.execute(value));
return true;
} catch (UnknownKeyException | UnsupportedMessageException | UnsupportedTypeException e) {
if (strict) {
throw Errors.createTypeErrorInteropException(obj, e, "writeHashEntry", null);
}
return false;
}
}
public static boolean writeMember(Object obj, Object member, Object value) {
return writeMember(obj, member, value, InteropLibrary.getUncached(), ExportValueNode.getUncached(), false, null);
}
public static boolean writeMember(Object obj, Object member, Object value, InteropLibrary interop, ExportValueNode exportValue, boolean strict, Node originatingNode) {
if (!(member instanceof TruffleString memberName)) {
return false;
}
try {
interop.writeMember(obj, Strings.toJavaString(memberName), exportValue.execute(value));
return true;
} catch (UnsupportedMessageException | UnknownIdentifierException | UnsupportedTypeException e) {
if (strict) {
throw Errors.createTypeErrorInteropException(obj, e, "writeMember", member, originatingNode);
}
return false;
}
}
/**
* Converts foreign objects to JS primitive values, coercing all numbers to double precision.
*/
@InliningCutoff
public static Object toPrimitiveOrDefaultLossy(Object obj, Object defaultValue, InteropLibrary interop, Node originatingNode) {
if (interop.isNull(obj)) {
return Null.instance;
}
try {
if (interop.isBoolean(obj)) {
return interop.asBoolean(obj);
} else if (interop.isString(obj)) {
return Strings.interopAsTruffleString(obj, interop);
} else if (interop.isNumber(obj)) {
if (interop.fitsInInt(obj)) {
return interop.asInt(obj);
} else if (interop.fitsInDouble(obj)) {
return interop.asDouble(obj);
} else if (interop.fitsInLong(obj)) {
return (double) interop.asLong(obj);
} else if (interop.fitsInBigInteger(obj)) {
return BigInt.doubleValueOf(interop.asBigInteger(obj));
}
}
} catch (UnsupportedMessageException e) {
throw Errors.createTypeErrorUnboxException(obj, e, originatingNode);
}
return defaultValue;
}
/**
* Converts a foreign object to a JS primitive value. Attempts to keep the precise numeric value
* when converting foreign numbers, relevant for comparisons and ToString/ToBigInt conversion.
* Returned BigInt values are marked as foreign so that they are handled correctly by subsequent
* ToNumeric, ToNumber (i.e., coerced to double), or ToBigInt.
*/
@InliningCutoff
public static Object toPrimitiveOrDefaultLossless(Object obj, Object defaultValue, InteropLibrary interop, TruffleString.SwitchEncodingNode switchEncoding, Node originatingNode) {
if (interop.isNull(obj)) {
return Null.instance;
}
try {
if (interop.isBoolean(obj)) {
return interop.asBoolean(obj);
} else if (interop.isString(obj)) {
return Strings.interopAsTruffleString(obj, interop, switchEncoding);
} else if (interop.isNumber(obj)) {
if (interop.fitsInInt(obj)) {
return interop.asInt(obj);
} else if (interop.fitsInLong(obj)) {
return interop.asLong(obj);
} else if (interop.fitsInBigInteger(obj)) {
return BigInt.fromForeignBigInteger(interop.asBigInteger(obj));
} else if (interop.fitsInDouble(obj)) {
return interop.asDouble(obj);
}
}
} catch (UnsupportedMessageException e) {
throw Errors.createTypeErrorUnboxException(obj, e, originatingNode);
}
return defaultValue;
}
@TruffleBoundary
public static List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy