org.pkl.thirdparty.graalvm.polyglot.Context Maven / Gradle / Ivy
Show all versions of pkl-config-java-all Show documentation
/*
* Copyright (c) 2017, 2023, 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 org.pkl.thirdparty.graalvm.polyglot;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Path;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.stream.StreamSupport;
import org.pkl.thirdparty.graalvm.polyglot.impl.AbstractPolyglotImpl.IOAccessor;
import org.pkl.thirdparty.graalvm.polyglot.impl.AbstractPolyglotImpl.AbstractContextDispatch;
import org.pkl.thirdparty.graalvm.polyglot.impl.AbstractPolyglotImpl.LogHandler;
import org.pkl.thirdparty.graalvm.polyglot.io.FileSystem;
import org.pkl.thirdparty.graalvm.polyglot.io.IOAccess;
import org.pkl.thirdparty.graalvm.polyglot.io.MessageTransport;
import org.pkl.thirdparty.graalvm.polyglot.io.ProcessHandler;
import org.pkl.thirdparty.graalvm.polyglot.proxy.Proxy;
/**
* A polyglot context for Graal guest languages that allows to {@link #eval(Source) evaluate} code.
* A polyglot context represents the global runtime state of all {@link Engine#getLanguages()
* installed} and {@link #newBuilder(String...) permitted} languages. Permitted languages are
* {@link #initialize(String) initialized} lazily, when they are used for the first time. For many
* context operations, a language identifier needs to be specified. A language identifier is
* unique for each language.
*
* Evaluation
*
* A context allows to evaluate a guest language source code using {@link #eval(Source)}. This is
* possible by evaluating {@link Source} objects or a given language identifier and code
* {@link String}. The {@link #eval(Source) evaluation} returns either the result value or throws a
* {@link PolyglotException} if a guest language error occurs.
*
* Example for evaluation of a fragment of JavaScript code with a new context:
*
*
* Context context = Context.create();
* Value result = context.eval("js", "42");
* assert result.asInt() == 42;
* context.close();
*
*
* In this example:
*
* - First we create a new context with all permitted languages.
*
- Next, we evaluate the expression "42" with language "js", which is the language identifier
* for JavaScript. Since this is the first time we access JavaScript, it automatically gets
* {@link #initialize(String) initialized}.
*
- Then, we assert the result value by converting the result value as primitive
int
* .
* - Finally, if the context is no longer needed, it is necessary to close it to ensure that all
* resources are freed. Contexts are also {@link AutoCloseable} for use with the Java
* {@code try-with-resources} statement.
*
*
* Configuration
*
* Contexts may be created either with default configuration using the {@link #create(String...)
* create method} or with custom configuration using the {@link #newBuilder(String...) builder}.
* Both methods allow to specify a subset of the installed languages as permitted languages. If no
* language is specified then all installed languages are permitted. Using the builder method
* {@link Builder#in(InputStream) input}, {@link Builder#err(OutputStream) error} and
* {@link Builder#out(OutputStream) output} streams, {@link Builder#option(String, String) options},
* and {@link Builder#arguments(String, String[]) application arguments} may be configured.
*
* Options may be specified for {@link Engine#getLanguages() languages},
* {@link Engine#getInstruments() instruments}, the {@link Engine#getOptions() engine} and the
* {@link Engine#getOptions() compiler}. For {@link Language#getOptions() language options}, the
* option key consists of the {@link Language#getId() language id} plus a dot followed by the option
* name (e.g. "js.Strict"). For most languages the option names start with an upper-case letter by
* convention. A list of available options may be received using {@link Language#getOptions()}.
* {@link Instrument#getOptions() Instrument options} are structured in the same way as language
* options but start with the {@link Instrument#getId() instrument id} instead.
*
* If system properties are {@link Engine.Builder#useSystemProperties(boolean) enabled}, which they
* are by default, then all polyglot options maybe specified with the prefix "polyglot." (e.g.
* "-Dpolyglot.js.Strict=true"). The system properties are read only once when the context or engine
* instance is created. After that, changes to the system properties have no affect.
*
* Each Graal language performs an initialization step before it can be used to execute code, after
* which it remains initialized for the lifetime of the context. Initialization is by default lazy
* and automatic, but initialization can be forced {@link Context#initialize(String) manually} if
* needed.
*
* Example for custom configuration using the context builder:
*
*
* OutputStream myOut = new BufferedOutputStream()
* Context context = Context.newBuilder("js", "R")
* .out(myOut)
* .option("js.Strict", "true")
* .allowAllAccess(true)
* .build();
* context.eval("js", "42");
* context.eval("R", "42");
* context.close();
*
*
* In this example:
*
* - At first, we create a new context and specify permitted languages as parameters.
*
- Secondly, we set the standard output stream to be used for the context.
*
- Then, we specify an option for JavaScript language only, by structuring the option key with
* the language id followed by the option name;
*
- With {@link Builder#allowAllAccess(boolean)} we grant a new context instance with the same
* access privileges as the host virtual machine.
*
- Next, we evaluate the expression "42" with language "js", which is the language identifier
* for JavaScript. Since this is the first time we access JavaScript, it first gets
* {@link #initialize(String) initialized} as well.
*
- Similarly to the previous line, the R language expression gets evaluated.
*
- Finally, we close the context, since it is no longer needed, to free all allocated resources.
* Contexts are also {@link AutoCloseable} for use with the Java {@code try-with-resources}
* statement.
*
*
* Bindings
*
* The symbols of the top-most scope of a language can be accessed using the
* {@link #getBindings(String) language bindings}. Each language provides its own bindings object
* for a context. The bindings object may be used to read, modify, insert and delete members in the
* top-most scope of the language. Certain languages may not allow write access to the bindings
* object. See {@link #getBindings(String)} for details.
*
* A context instance also provides access to the {@link #getPolyglotBindings() polyglot} bindings.
* The polyglot bindings are shared between languages and may be used to exchange values. See
* {@link #getPolyglotBindings()} for details.
*
* Examples using language bindings from JavaScript:
*
*
* Context context = Context.create("js");
* Value jsBindings = context.getBindings("js")
*
* jsBindings.putMember("foo", 42);
* assert context.eval("js", "foo").asInt() == 42;
*
* context.eval("js", "var bar = 42");
* assert jsBindings.getMember("bar").asInt() == 42;
*
* assert jsBindings.getMember("Math")
* .getMember("abs")
* .execute(-42)
* .asInt() == 42;
* context.close();
*
*
* In this example:
*
* - We create a new context with JavaScript as the only permitted language.
*
- Next, we load the JavaScript bindings object and assign it to a local variable
*
jsBindings
.
* - Then, we insert a new member
foo
into to the bindings object and verify that the
* object is accessible within the language by reading from a global symbol with the same name.
* - After that, we declare a new global variable in JavaScript and verify that it is accessible
* as member of the language bindings object.
*
- Next, we access we access a JavaScript builtin named
Math.abs
symbol and execute
* it with -42. This result is asserted to be 42.
* - Finally, we close the context to free all allocated resources.
*
*
* Host Interoperability
*
* It is often necessary to interact with values of the host runtime and Graal guest languages. Such
* objects are referred to as host objects. Every Java value that is passed to a Graal
* language is interpreted according to the specification described in {@link #asValue(Object)}.
* Also see {@link Value#as(Class)} for further details.
*
* By default only public classes, methods, and fields that are annotated with
* {@link HostAccess.Export @HostAccess.Export} are accessible to the guest language. This policy
* can be customized using {@link Builder#allowHostAccess(HostAccess)} when constructing the
* context.
*
*
* Example using a Java object from JavaScript:
*
*
* public class JavaRecord {
* @HostAccess.Export public int x;
*
* @HostAccess.Export
* public String name() {
* return "foo";
* }
* }
* Context context = Context.create();
*
* JavaRecord record = new JavaRecord();
* context.getBindings("js").putMember("javaRecord", record);
*
* context.eval("js", "javaRecord.x = 42");
* assert record.x == 42;
*
* context.eval("js", "javaRecord.name()").asString().equals("foo");
*
*
* Error Handling
*
* Program execution may fail when executing a guest language code or when accessing guest language
* object. Almost all methods in the {@link Context} and {@link Value} API throw a
* {@link PolyglotException} in case an error occurs. See {@link PolyglotException} for further
* details on error handling.
*
* Isolation
*
* Each context is by default isolated from all other instances with respect to both language
* evaluation semantics and resource consumption. By default, a new context instance has no access
* to host resources, like threads, files or loading new host classes. To allow access to such
* resources either the individual access right must be granted or
* {@link Builder#allowAllAccess(boolean) all access} must be set to true
.
*
*
* Contexts can be {@linkplain Builder#engine(Engine) configured} to share certain system resources
* like ASTs or optimized code by specifying a single underlying engine. See {@link Engine} for more
* details about code sharing.
*
*
* Context can be configured to allow value sharing between multiple contexts (allowed by default).
* See {@link Builder#allowValueSharing(boolean)} for details.
*
*
Proxies
*
* The {@link Proxy proxy interfaces} allow to mimic guest language objects, arrays, executables,
* primitives and native objects in Graal languages. Every Graal language will treat proxy instances
* like objects of that particular language. Multiple proxy interfaces can be implemented at the
* same time. For example, it is useful to provide proxy values that are objects with members and
* arrays at the same time.
*
* Thread-Safety
*
* It is safe to use a context instance from a single thread. It is also safe to use it with
* multiple threads if they do not access the context at the same time. Whether a single context
* instance may be used from multiple threads at the same time depends on if all initialized
* languages support it. If initialized languages support multi-threading, then the context instance
* may be used from multiple threads at the same time. If a context is used from multiple threads
* and the language does not fit, then an {@link IllegalStateException} is thrown by the accessing
* method.
*
* Meta-data from the context's underlying {@link #getEngine() engine} can be retrieved safely by
* any thread at any time.
*
* A context may be {@linkplain #close() closed} from any thread, but only if the context is not
* currently executing code. If the context is currently executing some code, a different thread may
* kill the running execution and close the context using {@link #close(boolean)}.
*
*
Context Exit
*
* A context is exited naturally by calling the {@link #close} method. A context may also be exited
* at the guest application request. There are two ways a guest language may exit.
*
* - Soft exit. A guest language throws a special exception that causes the embedder thread to
* eventually throw a {@link PolyglotException} with {@link PolyglotException#isExit()} returning
*
true
and {@link PolyglotException#getExitStatus()} returning the exit status code
* specified by the guest application. The special exception does not influence other threads and
* does not trigger context close on its own. Closing the context is up to the embedder.
* - Hard exit. A guest language uses a builtin command that unwinds all context threads and
* closes the context by force. Embedder threads also throw a {@link PolyglotException} with
* {@link PolyglotException#isExit()} returning
true
and
* {@link PolyglotException#getExitStatus()} returning the exit status code specified by the guest
* application. However, the context is closed automatically. The hard exit can be customized using
* {@link Builder#useSystemExit(boolean)}. If true
, the context threads are unwound by
* calling {@link System#exit(int)} with the exit status parameter specified by the guest
* application. This operation terminates the whole host application.
*
*
* Pre-Initialization
*
* The context pre-initialization can be used to perform expensive builtin creation in the time of
* native compilation.
*
* The context pre-initialization is enabled by setting the system property
* {@code polyglot.image-build-time.PreinitializeContexts} to a comma separated list of language ids
* which should be pre-initialized, for example:
* {@code -Dpolyglot.image-build-time.PreinitializeContexts=js,python}
*
* See
* {@code org.pkl.thirdparty.truffle.api.TruffleLanguage.patchContext(java.lang.Object, org.pkl.thirdparty.truffle.api.TruffleLanguage.Env)}
* for details about pre-initialization for language implementers.
*
* @since 19.0
*/
public final class Context implements AutoCloseable {
final AbstractContextDispatch dispatch;
final Object receiver;
final Context currentAPI;
final Engine engine;
@SuppressWarnings("unchecked")
Context(AbstractContextDispatch dispatch, T receiver, Engine engine) {
this.dispatch = dispatch;
this.receiver = receiver;
this.engine = engine;
this.currentAPI = new Context(this);
dispatch.setAPI(receiver, this);
}
private Context() {
this.dispatch = null;
this.receiver = null;
this.engine = null;
this.currentAPI = null;
}
private Context(Context creatorAPI) {
this.dispatch = creatorAPI.dispatch;
this.receiver = creatorAPI.receiver;
this.engine = creatorAPI.engine.currentAPI;
this.currentAPI = null;
}
/**
* Provides access to meta-data about the underlying Graal {@linkplain Engine engine}.
*
* @return Graal {@link Engine} being used by this context
* @since 19.0
*/
public Engine getEngine() {
return engine;
}
/**
* Evaluates a source object by using the {@linkplain Source#getLanguage() language} specified
* in the source. The result is accessible as {@link Value value} and never returns
* null
. The first time a source is evaluated, it will be parsed. Consecutive
* invocations of eval with the same source will only execute the already parsed code.
*
* Basic Example:
*
*
* try (Context context = Context.create()) {
* Source source = Source.newBuilder("js", "42", "mysource.js").build();
* Value result = context.eval(source);
* assert result.asInt() == 42;
* }
*
*
* @param source a source object to evaluate
* @throws PolyglotException in case the guest language code parsing or evaluation failed.
* @throws IllegalStateException if the context is already closed and the current thread is not
* allowed to access it.
* @throws IllegalArgumentException if the language of the given source is not installed or the
* {@link Source#getMimeType() MIME type} is not supported with the language.
* @return the evaluation result. The returned instance is never null
, but the
* result might represent a {@link Value#isNull() null} value.
* @since 19.0
*/
public Value eval(Source source) {
return dispatch.eval(receiver, source.getLanguage(), source);
}
/**
* Evaluates a guest language code literal, using a provided {@link Language#getId() language
* id}. The result is accessible as {@link Value value} and never returns null
. The
* provided {@link CharSequence} must represent an immutable String.
*
* Basic Example:
*
*
* try (Context context = Context.create()) {
* Value result = context.eval("js", "42");
* assert result.asInt() == 42;
* }
*
*
* @throws PolyglotException in case the guest language code parsing or evaluation failed.
* @throws IllegalArgumentException if the language does not exist or is not accessible.
* @throws IllegalStateException if the context is already closed and the current thread is not
* allowed to access it, or if the given language is not installed.
* @return the evaluation result. The returned instance is never null
, but the
* result might represent a {@link Value#isNull() null} value.
* @since 19.0
*/
public Value eval(String languageId, CharSequence source) {
return eval(Source.create(languageId, source));
}
/**
* Parses but does not evaluate a given source by using the {@linkplain Source#getLanguage()
* language} specified in the source and returns a {@link Value value} that can be
* {@link Value#execute(Object...) executed}. If a parsing fails, e.g. due to a syntax error in
* the source, then a {@link PolyglotException} will be thrown. In case of a syntax error the
* {@link PolyglotException#isSyntaxError()} will return true
. There is no
* guarantee that only syntax errors will be thrown by this method. Any other guest language
* exception might be thrown. If the validation succeeds then the method completes without
* throwing an exception.
*
* The result value only supports an empty set of arguments to {@link Value#execute(Object...)
* execute}. If executed repeatedly then the source is evaluated multiple times.
* {@link Source.Builder#interactive(boolean) Interactive} sources will print their result for
* each execution of the parsing result to the {@link Builder#out(OutputStream) output} stream.
*
* If the parsing succeeds and the source is {@link Source.Builder#cached(boolean) cached} then
* the result will automatically be reused for consecutive calls to {@link #parse(Source)} or
* {@link #eval(Source)}. If the validation should be performed for each invocation or the
* result should not be remembered then {@link Source.Builder#cached(boolean) cached} can be set
* to false
. By default sources are cached.
*
* Basic Example:
*
*
* try (Context context = Context.create()) {
* Source source = Source.create("js", "42");
* Value value;
* try {
* value = context.parse(source);
* // parsing succeeded
* } catch (PolyglotException e) {
* if (e.isSyntaxError()) {
* SourceSection location = e.getSourceLocation();
* // syntax error detected at location
* } else {
* // other guest error detected
* }
* throw e;
* }
* // evaluate the parsed script
* value.execute();
* }
*
*
* @param source a source object to parse
* @throws PolyglotException in case the guest language code parsing or validation failed.
* @throws IllegalArgumentException if the language does not exist or is not accessible.
* @throws IllegalStateException if the context is already closed and the current thread is not
* allowed to access it, or if the given language is not installed.
* @since 20.2
*/
public Value parse(Source source) throws PolyglotException {
return dispatch.parse(receiver, source.getLanguage(), source);
}
/**
* Parses but does not evaluate a guest language code literal using a provided
* {@link Language#getId() language id} and character sequence and returns a {@link Value value}
* that can be {@link Value#execute(Object...) executed}. The provided {@link CharSequence} must
* represent an immutable String. This method represents a short-hand for {@link #parse(Source)}
* .
*
* The result value only supports an empty set of arguments to {@link Value#execute(Object...)
* execute}. If executed repeatedly then the source is evaluated multiple times.
* {@link Source.Builder#interactive(boolean) Interactive} sources will print their result for
* each execution of the parsing result to the {@link Builder#out(OutputStream) output} stream.
*
*
*
* try (Context context = Context.create()) {
* Value value;
* try {
* value = context.parse("js", "42");
* // parsing succeeded
* } catch (PolyglotException e) {
* if (e.isSyntaxError()) {
* SourceSection location = e.getSourceLocation();
* // syntax error detected at location
* } else {
* // other guest error detected
* }
* throw e;
* }
* // evaluate the parsed script
* value.execute();
* }
*
*
* @throws PolyglotException in case the guest language code parsing or evaluation failed.
* @throws IllegalArgumentException if the language does not exist or is not accessible.
* @throws IllegalStateException if the context is already closed and the current thread is not
* allowed to access it, or if the given language is not installed.
* @since 20.2
*/
public Value parse(String languageId, CharSequence source) {
return parse(Source.create(languageId, source));
}
/**
* Returns polyglot bindings that may be used to exchange symbols between the host and guest
* languages. All languages have unrestricted access to the polyglot bindings. The returned
* bindings object always has {@link Value#hasMembers() members} and its members are
* {@link Value#getMember(String) readable}, {@link Value#putMember(String, Object) writable}
* and {@link Value#removeMember(String) removable}.
*
* Guest languages may put and get members through language specific APIs. For example, in
* JavaScript, symbols of the polyglot bindings can be accessed using
* Polyglot.import("name")
and set using
* Polyglot.export("name", value)
. Please see the individual language reference on
* how to access these symbols.
*
* @throws IllegalStateException if context is already closed.
* @since 19.0
*/
public Value getPolyglotBindings() {
return dispatch.getPolyglotBindings(receiver);
}
/**
* Returns a value that represents the top-most bindings of a language. The top most bindings of
* the language returns a {@link Value#getMember(String) member} for a symbol in the scope.
* Languages may allow modifications of members of the returned bindings object at the
* language's discretion. If the language has not been {@link #initialize(String) initialized}
* yet, it will be initialized when the bindings are requested.
*
* @throws IllegalArgumentException if the language does not exist or is not accessible.
* @throws IllegalStateException if the context is already closed.
* @throws PolyglotException in case the lazy initialization failed due to a guest language
* error.
* @since 19.0
*/
public Value getBindings(String languageId) {
return dispatch.getBindings(receiver, languageId);
}
/**
* Forces the initialization of a language. It is not necessary to explicitly initialize a
* language, it will be initialized the first time it is used.
*
* @param languageId the identifier of the language to initialize.
* @return true
if the language was initialized. Returns false
if it
* was already initialized.
* @throws PolyglotException in case the initialization failed due to a guest language error.
* @throws IllegalArgumentException if the language does not exist or is not accessible.
* @throws IllegalStateException if the context is already closed.
* @since 19.0
*/
public boolean initialize(String languageId) {
return dispatch.initializeLanguage(receiver, languageId);
}
/**
* Resets all accumulators of resource limits for the associated context to zero.
*
* @since 19.3
*/
public void resetLimits() {
dispatch.resetLimits(receiver);
}
/**
* Converts a host value to a polyglot {@link Value value} representation. This conversion is
* applied implicitly whenever {@link Value#execute(Object...) execution} or
* {@link Value#newInstance(Object...) instantiation} arguments are provided,
* {@link Value#putMember(String, Object) members} and
* {@link Value#setArrayElement(long, Object) array elements} are set or when a value is
* returned by a {@link Proxy polyglot proxy}. It is not required nor efficient to explicitly
* convert to polyglot values before performing these operations. This method is useful to
* convert a {@link Value#as(Class) mapped} host value back to a polyglot value while preserving
* the identity.
*
* When a host value is converted to a polyglot value the following rules apply:
*
* - If the
hostValue
is null
, then it will be interpreted as
* polyglot {@link Value#isNull() null}.
* - If the
hostValue
is already a {@link Value polyglot value}, then it will be
* cast to {@link Value}.
* - If the
hostValue
is an instance of {@link Byte}, {@link Short},
* {@link Integer}, {@link Long}, {@link Float} or {@link Double}, then it will be interpreted
* as polyglot {@link Value#isNumber() number}. Other subclasses of {@link Number} will be
* interpreted as {@link Value#isHostObject() host object} (see later).
* - If the
hostValue
is an instance of {@link Character} or {@link String}, then
* it will be interpreted as polyglot {@link Value#isString() string}.
* - If the
hostValue
is an instance of {@link Boolean}, then it will be
* interpreted as polyglot {@link Value#isBoolean() boolean}.
* - If the
hostValue
is an instance of {@link Instant}, {@link LocalTime},
* {@link ZonedDateTime}, {@link java.util.Date} but not {@link java.sql.Date} or
* {@link java.sql.Time} then it will be interpreted as polyglot {@link Value#isTime() time}.
* - If the
hostValue
is an instance of {@link Instant}, {@link LocalDate},
* {@link ZonedDateTime}, {@link java.util.Date} but not {@link java.sql.Time} or
* {@link java.sql.Date} then it will be interpreted as polyglot {@link Value#isDate() date}.
* - If the
hostValue
is an instance of {@link ZoneId}, {@link Instant},
* {@link ZonedDateTime}, {@link java.util.Date} but not {@link java.sql.Time} and
* {@link java.sql.Date} then it will be interpreted as polyglot {@link Value#isTimeZone() time
* zone}.
* - If the
hostValue
is an instance of {@link ZonedDateTime}, {@link Instant},
* {@link ZonedDateTime}, {@link java.util.Date} but not {@link java.sql.Time} and
* {@link java.sql.Date} then it will be interpreted as polyglot {@link Value#isInstant()
* instant}.
* - If the
hostValue
is an instance of {@link Duration} then it will be
* interpreted as polyglot {@link Value#isDuration() duration}.
* - If the
hostValue
is a {@link Proxy polyglot proxy}, then it will be
* interpreted according to the behavior specified by the proxy. See the javadoc of the proxy
* subclass for further details.
* - If the
hostValue
is a non-primitive {@link Value#as(Class) mapped Java
* value}, then the original value will be restored. For example, if a guest language object was
* mapped to {@link Map}, then the original object identity will be preserved when converting
* back to a polyglot value.
* - Any other
hostValue
will be interpreted as {@link Value#isHostObject() host
* object}. Host objects expose all their public java fields and methods as
* {@link Value#getMember(String) members}. In addition, Java arrays, subtypes of {@link List}
* and {@link Entry} will be interpreted as a value with {@link Value#hasArrayElements() array
* elements}. The subtypes of {@link Iterable} will be interpreted as a value with
* {@link Value#hasIterator()} iterator}. The subtypes of {@link Iterator} will be interpreted
* as an {@link Value#isIterator() iterator} value. The subtypes of {@link Map} will be
* interpreted as a value with {@link Value#hasHashEntries()} hash entries}. And single method
* interfaces annotated with {@link FunctionalInterface} are {@link Value#execute(Object...)
* executable} directly. Java {@link Class} instances are interpreted as
* {@link Value#canInstantiate() instantiable}, but they do not expose Class methods as members.
*
*
* Basic Examples:
*
* The following assertion statements always hold:
*
*
* Context context = Context.create();
* assert context.asValue(null).isNull();
* assert context.asValue(42).isNumber();
* assert context.asValue("42").isString();
* assert context.asValue('c').isString();
* assert context.asValue(new String[0]).hasArrayElements();
* assert context.asValue(new ArrayList<>()).isHostObject();
* assert context.asValue(new ArrayList<>()).hasArrayElements();
* assert context.asValue((Supplier) () -> 42).execute().asInt() == 42;
*
*
* Mapping to Java methods and fields
*
* When Java host objects are passed to guest languages, their public methods and fields are
* provided as {@link Value#getMember(String) members}. Methods and fields are grouped by name,
* so only one member is exposed for each name.
*
* {@link Class} objects have a member named {@code static} referring to the class's companion
* object containing the static methods of the class. Likewise, the companion object has a
* member named {@code class} that points back to the class object.
*
* When an argument value needs to be mapped to match a required Java method parameter type,
* then the semantics of {@link Value#as(Class) host value mapping} is used. The result of the
* mapping is equivalent of calling {@link Value#as(Class)} with the parameter type. Therefore,
* a {@link ClassCastException} or {@link NullPointerException} is thrown if a parameter value
* cannot be cast to the required parameter type.
*
* Overloaded java methods are selected based on the provided arguments. In case multiple mapped
* Java methods with the same name are applicable for {@link Value#execute(Object...)
* executions} or {@link Value#newInstance(Object...) instantiations}, then the method with the
* most concrete method with applicable arguments will be called.
*
* The following parameter type hierarchy is used for a method resolution. Left-most parameter
* types are prioritized over types to their right.
*
* - {@link Value#isBoolean() Boolean} values: boolean, Boolean, Object
*
- String values: char, Character, String, CharSequence, Object
*
- Number values: byte, Byte, short, Short, int, Integer, long, Long, float, Float, double,
* Double, Number, Object
*
- Other values are resolved based on their Java type hierarchy.
*
* If there are multiple concrete methods or too many arguments provided, then an illegal
* argument type error will be raised.
*
* Advanced Example:
*
* This example first creates a new instance of the Java class Record
and inspects
* it using the polyglot value API. Later, a host value is converted to a polyglot value using
* JavaScript guest language.
*
* In the following examples all assertions hold.
*
*
* class JavaRecord {
* public int x = 42;
* public double y = 42.0;
* public String name() {
* return "foo";
* }
* }
* Context context = Context.create();
* Value record = context.asValue(new JavaRecord());
* assert record.getMember("x").asInt() == 42;
* assert record.getMember("y").asDouble() == 42.0d;
* assert record.getMember("name").execute().asString().equals("foo");
*
* assert context.eval("js", "(function(record) record.x)")
* .execute(record).asInt() == 42;
* assert context.eval("js", "(function(record) record.y)")
* .execute(record).asDouble() == 42.0d;
* assert context.eval("js", "(function(record) record.name())")
* .execute(record).asString().equals("foo");
*
*
* @see Value#as(Class)
* @throws IllegalStateException if the context is already closed
* @param hostValue the host value to convert to a polyglot value.
* @return the polyglot value.
* @since 19.0
*/
public Value asValue(Object hostValue) {
return dispatch.asValue(receiver, hostValue);
}
/**
* Explicitly enters the context on the current thread. A context needs to be entered and left
* for any operation to be performed. For example, before and after invoking the
* {@link Value#execute(Object...) execute} method. This can be inefficient if a very high
* number of simple operations needs to be performed. By {@link #enter() entering} and
* {@link #leave() leaving} once explicitly, the overhead for entering/leaving the context for
* each operation can be eliminated. Contexts can be entered multiple times on the same thread.
*
* @throws IllegalStateException if the context is already {@link #close() closed}.
* @throws PolyglotException if a language has denied execution on the current thread.
* @see #leave() leave a context.
* @since 19.0
*/
public void enter() {
checkCreatorAccess("entered");
dispatch.explicitEnter(receiver);
}
/**
* {@inheritDoc}
*
* @since 19.0
*/
@Override
public boolean equals(Object obj) {
if (obj instanceof Context) {
Context other = ((Context) obj);
return receiver.equals(other.receiver);
}
return false;
}
/**
* {@inheritDoc}
*
* @since 19.0
*/
@Override
public int hashCode() {
return Objects.hashCode(receiver);
}
/**
* Explicitly leaves the context on the current thread. The context must be {@link #enter()
* entered} before calling this method.
*
* @throws IllegalStateException if the context is already closed or if the context was not
* {@link #enter() entered} on the current thread.
* @see #enter() enter a context.
* @since 19.0
*/
public void leave() {
checkCreatorAccess("left");
dispatch.explicitLeave(receiver);
}
private void checkCreatorAccess(String operation) {
if (this.currentAPI == null) {
throw new IllegalStateException(String.format("Context instances that were received using Context.get() cannot be %s.", operation));
}
}
/**
* Closes the context and frees up potentially allocated native resources. A context cannot free
* all native resources allocated automatically. For this reason it is necessary to close
* contexts after use. If a context is cancelled, then the executing thread will throw a
* {@link PolyglotException}. The exception indicates that it was
* {@link PolyglotException#isCancelled() cancelled}. Please note, canceling a single context
* can negatively affect the performance of other executing contexts constructed with the same
* engine.
*
* If internal errors occur during context closing, then they are printed to the configured
* {@link Builder#err(OutputStream) error output stream}. If a context was closed, then its
* methods will throw an {@link IllegalStateException} when invoked. If an attempt to close a
* context was successful, then consecutive calls to close have no effect.
*
* For convenience, before the actual closing process begins, the close method leaves the
* context on the current thread, if it was entered {@link #enter() explicitly}.
*
* @param cancelIfExecuting if true
then currently executing contexts will be
* {@link PolyglotException#isCancelled() cancelled}, else an
* {@link IllegalStateException} is thrown.
* @see Engine#close() close an engine.
* @throws PolyglotException in case the close failed due to a guest language error, or, if
* cancelIfExecuting is false
, the exception is also thrown when the
* context was {@link PolyglotException#isCancelled() cancelled} or the context was
* {@link PolyglotException#isExit() exited} at request of the guest application.
* @throws IllegalStateException if the context is still running and cancelIfExecuting is
* false
* @since 19.0
*/
public void close(boolean cancelIfExecuting) {
checkCreatorAccess("closed");
dispatch.close(receiver, cancelIfExecuting);
}
/**
* Closes this context and frees up potentially allocated native resources. A context may not
* free all native resources allocated automatically. For this reason it is recommended to close
* contexts after use. If the context is currently being executed on another thread, then an
* {@link IllegalStateException} is thrown. To close concurrently executing contexts see
* {@link #close(boolean)}.
*
* If internal errors occur during the context closure, then they are printed to the configured
* {@link Builder#err(OutputStream) error output stream}. If a context was closed, then its
* methods will throw an {@link IllegalStateException}, when invoked. If an attempt to close a
* context was successful, then consecutive calls to close have no effect.
*
* For convenience, before the actual closing process begins, the close method leaves the
* context on the current thread, if it was entered {@link #enter() explicitly}.
*
* @throws PolyglotException in case the close failed due to a guest language error, or the
* context was {@link PolyglotException#isCancelled() cancelled} or the context was
* {@link PolyglotException#isExit() exited} at request of the guest application.
* @throws IllegalStateException if the context is currently executing on another thread.
* @see Engine#close() close an engine.
* @since 19.0
*/
public void close() {
close(false);
}
/**
* Use this method to interrupt this context. The interruption is non-destructive meaning the
* context is still usable after this method finishes. Please note that guest finally blocks are
* executed during interrupt. A context thread may not be interruptiple if it uses
* non-interruptible waiting or executes non-interruptible host code.
*
* This method may be used as a "soft cancel", meaning that it can be used before
* {@link #close(boolean) close(true)} is executed.
*
* @param timeout specifies the duration the interrupt method will wait for the active threads
* of the context to be finished. Setting the duration to {@link Duration#ZERO 0}
* means wait indefinitely.
* @throws IllegalStateException in case the context is entered in the current thread.
* @throws TimeoutException in case the interrupt was not successful, i.e., not all threads were
* finished within the specified time limit.
*
* @since 20.3
*/
public void interrupt(Duration timeout) throws TimeoutException {
checkCreatorAccess("interrupted");
if (!dispatch.interrupt(receiver, timeout)) {
throw new TimeoutException("Interrupt timed out.");
}
}
/**
* Polls safepoints events and executes them for the current thread. This allows guest languages
* to run actions between long running host method calls. Polyglot embeddings that rely on
* cancellation should call this method whenev a potentially long running host operation is
* executed. For example, iterating an unbounded array. Guest language code and operations
* automatically poll safepoints regularly.
*
*
* In this example we allow {@link Context#interrupt(Duration) interruption} and
* {@link Context#close(boolean) cancellation} to stop the processing of our event queue.
*
*
* class EventProcessor {
*
* List
*
* @throws PolyglotException in case the close failed due to a guest language error.
* @throws IllegalStateException if the context is already {@link #close() closed}.
* @since 21.1
*/
public void safepoint() {
dispatch.safepoint(receiver);
}
/**
* Returns the currently entered polyglot context. A context will be entered if the current
* executing Java method is called by a Graal guest language or if a context is entered
* explicitly using {@link Context#enter()} on the current thread. The returned context may be
* used to:
*
* - Evaluate guest language code from {@link #eval(String, CharSequence) string literals} or
* {@link #eval(Source) file} sources.
*
- {@link #asValue(Object) Convert} Java values to {@link Value polyglot values}.
*
- Access top-level {@link #getBindings(String) bindings} of other languages.
*
- Access {@link #getPolyglotBindings() polyglot bindings}.
*
- Access meta-data like available {@link Engine#getLanguages() languages} or
* {@link Engine#getOptions() options} of the {@link #getEngine() engine}.
*
*
* The returned context can not be used to {@link #enter() enter} , {@link #leave()
* leave} or {@link #close() close} the context or {@link #getEngine() engine}. Invoking such
* methods will cause an {@link IllegalStateException} to be thrown. This ensures that only the
* {@link #create(String...) creator} of a context is allowed to enter, leave or close a
* context.
*
* The current entered context may change. It is therefore required to call {@link #getCurrent()
* getCurrent} every time a context is needed. The current entered context should not be cached
* in static fields.
*
* @throws IllegalStateException if no context is currently entered.
* @since 19.0
*/
public static Context getCurrent() {
Context context = Engine.getImpl().getCurrentContext();
if (context.currentAPI == null) {
return context;
} else {
return context.currentAPI;
}
}
/**
* Creates a context with default configuration. This method is a shortcut for
* {@link #newBuilder(String...) newBuilder(permittedLanuages).build()}.
*
* @see #newBuilder(String...)
* @since 19.0
*/
public static Context create(String... permittedLanguages) {
return newBuilder(permittedLanguages).build();
}
/**
* Creates a builder for constructing a context with custom configuration.
*
* @param permittedLanguages names of languages permitted in the context. If no languages are
* provided, then all installed languages will be permitted. If an explicit
* {@link Builder#engine(Engine) engine} was specified then only those languages may
* be used that were installed and {@link Engine#newBuilder(String...) permitted} by
* the specified engine. Languages are validated when the context is
* {@link Builder#build() built}. An {@link IllegalArgumentException} will be thrown
* if an unknown or a language denied by the engine was used.
* @return a builder that can create a context
* @since 19.0
*/
public static Builder newBuilder(String... permittedLanguages) {
return EMPTY.new Builder(permittedLanguages);
}
private static final Context EMPTY = new Context();
static final Predicate UNSET_HOST_LOOKUP = new Predicate<>() {
public boolean test(String t) {
return false;
}
};
static final Predicate NO_HOST_CLASSES = new Predicate<>() {
public boolean test(String t) {
return false;
}
};
static final Predicate ALL_HOST_CLASSES = new Predicate<>() {
public boolean test(String t) {
return true;
}
};
/**
* Builder class to construct {@link Context} instances. A builder instance is not thread-safe
* and must not be used from multiple threads at the same time.
*
* @see Context
* @since 19.0
*/
@SuppressWarnings("hiding")
public final class Builder {
private Engine sharedEngine;
private String[] permittedLanguages;
private OutputStream out;
private OutputStream err;
private InputStream in;
private Map options;
private Map arguments;
private Predicate hostClassFilter = UNSET_HOST_LOOKUP;
private Boolean allowNativeAccess;
private Boolean allowCreateThread;
private boolean allowAllAccess;
private Boolean allowIO;
private Boolean allowHostClassLoading;
private Boolean allowExperimentalOptions;
private Boolean allowHostAccess;
private boolean allowValueSharing = true;
private Boolean allowInnerContextOptions;
private PolyglotAccess polyglotAccess;
private HostAccess hostAccess;
private IOAccess ioAccess;
private FileSystem customFileSystem;
private MessageTransport messageTransport;
private Object customLogHandler;
private Boolean allowCreateProcess;
private ProcessHandler processHandler;
private EnvironmentAccess environmentAccess;
private ResourceLimits resourceLimits;
private Map environment;
private ZoneId zone;
private Path currentWorkingDirectory;
private ClassLoader hostClassLoader;
private boolean useSystemExit;
private SandboxPolicy sandboxPolicy;
Builder(String... permittedLanguages) {
Objects.requireNonNull(permittedLanguages);
for (String language : permittedLanguages) {
Objects.requireNonNull(language);
}
this.permittedLanguages = permittedLanguages;
}
/**
* Explicitly sets the underlying engine to use. By default, every context has its own
* isolated engine. If multiple contexts are created from one engine, then they may
* share/cache certain system resources like ASTs or optimized code by specifying a single
* underlying engine. See {@link Engine} for more details about system resource sharing.
*
* @since 19.0
*/
public Builder engine(Engine engine) {
Objects.requireNonNull(engine);
this.sharedEngine = engine;
return this;
}
/**
* Sets the standard output stream to be used for the context. If not set, then the standard
* output stream configured for the {@link #engine(Engine) engine} or standard error stream
* is used.
*
* @since 19.0
*/
public Builder out(OutputStream out) {
Objects.requireNonNull(out);
this.out = out;
return this;
}
/**
* Sets the error output stream to be used for the context. If not set, then either the
* error stream configured for the {@link #engine(Engine) engine} or standard error stream
* is used.
*
* @since 19.0
*/
public Builder err(OutputStream err) {
Objects.requireNonNull(err);
this.err = err;
return this;
}
/**
* Sets the input stream to be used for the context. If not set, then either the input
* stream configured for the {@link #engine(Engine) engine} or standard in stream is used.
*
* @since 19.0
*/
public Builder in(InputStream in) {
Objects.requireNonNull(in);
this.in = in;
return this;
}
/**
* Allows guest languages to access the host language by loading new classes. Default is
* false
. If {@link #allowAllAccess(boolean) all access} is set to
* true
, then host access is enabled if not disallowed explicitly.
*
* @since 19.0
* @deprecated use {@link #allowHostAccess(HostAccess)} or
* {@link #allowHostClassLookup(Predicate)} instead.
*/
@Deprecated(since = "19.0")
public Builder allowHostAccess(boolean enabled) {
this.allowHostAccess = enabled;
return this;
}
/**
* Configures which public constructors, methods or fields of public classes are accessible
* by guest applications. By default if {@link #allowAllAccess(boolean)} is
* false
the {@link HostAccess#EXPLICIT} policy will be used, otherwise
* {@link HostAccess#ALL}.
*
* @see HostAccess#EXPLICIT EXPLICIT - to allow explicitly annotated constructors, methods
* or fields.
* @see HostAccess#ALL ALL - to allow unrestricted access (use only for trusted guest
* applications)
* @see HostAccess#NONE NONE - to not allow any access
* @see HostAccess#newBuilder() newBuilder() - to create a custom configuration.
*
* @since 19.0
*/
public Builder allowHostAccess(HostAccess config) {
this.hostAccess = config;
return this;
}
/**
* Allows guest languages to access the native interface.
*
* @since 19.0
*/
public Builder allowNativeAccess(boolean enabled) {
this.allowNativeAccess = enabled;
return this;
}
/**
* If true
, allows guest languages to create new threads. Default is
* false
. If {@link #allowAllAccess(boolean) all access} is set to
* true
, then the creation of threads is enabled if not allowed explicitly.
* Threads created by guest languages are closed, when the context is {@link Context#close()
* closed}.
*
* @since 19.0
*/
public Builder allowCreateThread(boolean enabled) {
this.allowCreateThread = enabled;
return this;
}
/**
* Sets the default value for all privileges. If not explicitly specified, then all access
* is false
. If all access is enabled then certain privileges may still be
* disabled by configuring it explicitly using the builder (either before or after the call
* to {@link #allowAllAccess(boolean) allowAllAccess()}). Allowing all access should only be
* set if the guest application is fully trusted.
*
* If true
, grants the context the same access privileges as the host virtual
* machine. If the host VM runs without a {@link SecurityManager security manager} enabled,
* then enabling all access gives the guest languages full control over the host process.
* Otherwise, Java {@link SecurityManager security manager} is in control of restricting the
* privileges of the polyglot context. If new privilege restrictions are added to the
* polyglot API, then they will default to full access.
*
* Grants full access to the following privileges by default:
*
* - The {@link #allowCreateThread(boolean) creation} and use of new threads.
*
- The access to public {@link #allowHostAccess(HostAccess) host classes}.
*
- The loading of new {@link #allowHostClassLoading(boolean) host classes} by adding
* entries to the class path.
*
- Exporting new members into the polyglot {@link Context#getPolyglotBindings()
* bindings}.
*
- Unrestricted {@link #allowIO(IOAccess) IO operations} on host system.
*
- Passing {@link #allowExperimentalOptions(boolean) experimental options}.
*
- The {@link #allowCreateProcess(boolean) creation} and use of new sub-processes.
*
- The {@link #allowEnvironmentAccess(org.pkl.thirdparty.graalvm.polyglot.EnvironmentAccess) access} to
* process environment variables.
*
- The {@link #allowInnerContextOptions(boolean) changing} of options for of inner
* contexts spawned by the language.
*
*
* @param enabled true
for all access by default.
* @since 19.0
*/
public Builder allowAllAccess(boolean enabled) {
this.allowAllAccess = enabled;
return this;
}
/**
* If host class loading is enabled, then the guest language is allowed to load new host
* classes via jar or class files. If {@link #allowAllAccess(boolean) all access} is set to
* true
, then the host class loading is enabled if it is not disallowed
* explicitly. For host class loading to be useful, {@link #allowIO(IOAccess) IO} operations
* {@link #allowHostClassLookup(Predicate) host class lookup}, and the
* {@link #allowHostAccess(org.pkl.thirdparty.graalvm.polyglot.HostAccess) host access policy} needs to be
* configured as well.
*
* @see #allowHostAccess(HostAccess)
* @see #allowHostClassLookup(Predicate)
* @since 19.0
*/
public Builder allowHostClassLoading(boolean enabled) {
this.allowHostClassLoading = enabled;
return this;
}
/**
* Sets a filter that specifies the Java host classes that can be looked up by the guest
* application. If set to null
then no class lookup is allowed and relevant
* language builtins are not available (e.g. Java.type
in JavaScript). If the
* classFilter
parameter is set to a filter predicate, then language builtins
* are available and classes can be looked up if the filter predicate returns
* true
for the fully qualified class name. If the filter returns
* false
, then the class cannot be looked up and as a result throws a guest
* language error when accessed. By default and if {@link #allowAllAccess(boolean) all
* access} is false
, host class lookup is disabled. By default and if
* {@link #allowAllAccess(boolean) all access} is true
, then all classes may be
* looked up by the guest application.
*
* In order to access class members looked up by the guest application a
* {@link #allowHostAccess(org.pkl.thirdparty.graalvm.polyglot.HostAccess) host access policy} needs to be
* set or {@link #allowAllAccess(boolean) all access} needs to be set to true
.
*
* To load new classes the context uses the
* {@link Context.Builder#hostClassLoader(java.lang.ClassLoader) hostClassLoader} if
* specified or the {@link Thread#getContextClassLoader() context class loader} that will be
* captured when the context is {@link #build() built}. If an explicit
* {@link #engine(Engine) engine} was specified, then the context class loader at engine
* {@link Engine.Builder#build() build-time} will be used instead. When the Java module
* system is available (>= JDK 9) then only classes are accessible that are exported to the
* unnamed module of the captured class loader.
*
*
Example usage with JavaScript:
*
*
* public class MyClass {
* @HostAccess.Export
* public int accessibleMethod() {
* return 42;
* }
*
* public static void main(String[] args) {
* try (Context context = Context.newBuilder() //
* .allowHostClassLookup(c -> c.equals("myPackage.MyClass")) //
* .build()) {
* int result = context.eval("js", "" +
* "var MyClass = Java.type('myPackage.MyClass');" +
* "new MyClass().accessibleMethod()").asInt();
* assert result == 42;
* }
* }
* }
*
*
* In this example:
*
* - We create a new context with the {@link Builder#allowHostClassLookup(Predicate)
* permission} to look up the class
myPackage.MyClass
in the guest language
* application.
* - We evaluate a JavaScript code snippet that accesses the Java class
*
myPackage.MyClass
using the Java.type
builtin provided by the
* JavaScript language implementation. Other classes can only be looked up if the provided
* class filter returns true
for their name.
* - We create a new instance of the Java class
MyClass
by using the
* JavaScript new
keyword.
* - We call the method
accessibleMethod
which returns 42
. The
* method is accessible to the guest language because because the enclosing class and the
* declared method are public, as well as annotated with the
* {@link HostAccess.Export @HostAccess.Export} annotation. Which Java members of classes
* are accessible can be configured using the {@link #allowHostAccess(HostAccess) host
* access policy}.
*
*
* @param classFilter a predicate that returns true
or false
for a
* qualified Java class name or null
to disable host class lookup.
* @see #allowHostClassLoading(boolean) allowHostClassLoading - to allow loading of classes.
* @see #allowHostAccess(HostAccess) allowHostAccess - to configure the access policy of
* host values for guest languages.
* @since 19.0
*/
public Builder allowHostClassLookup(Predicate classFilter) {
this.hostClassFilter = classFilter;
return this;
}
/**
* Allow experimental options to be used for language options. Do not use experimental
* options in production environments. If set to {@code false} (the default), then passing
* an experimental option results in an {@link IllegalArgumentException} when the context is
* built.
*
* Alternatively {@link Engine.Builder#allowExperimentalOptions(boolean)} may be used when
* constructing the context using an {@link #engine(Engine) explicit engine}.
*
* @since 19.0
*/
public Builder allowExperimentalOptions(boolean enabled) {
this.allowExperimentalOptions = enabled;
return this;
}
/**
* Allow polyglot access using the provided policy. If {@link #allowAllAccess(boolean) all
* access} is true
then the default polyglot access policy is
* {@link PolyglotAccess#ALL}, otherwise {@link PolyglotAccess#NONE}. The provided access
* policy must not be null
.
*
* @since 19.0
*/
public Builder allowPolyglotAccess(PolyglotAccess accessPolicy) {
Objects.requireNonNull(accessPolicy);
this.polyglotAccess = accessPolicy;
return this;
}
/**
* Enables or disables sharing of any {@link Value value} between contexts. Value sharing is
* enabled by default and is not affected by {@link #allowAllAccess(boolean)}.
*
* If this option is set to true
(default) then any value that is associated
* with one context will be automatically migrated when passed to another context.
* Primitive, {@link Value#isHostObject() host} and {@link Value#isHostObject() proxy}
* values can be migrated without limitation. When guest language values are migrated, they
* capture and remember their original context. Guest language objects need to be accessed
* when their respective context is {@link Context#enter() entered}, therefore before any
* access their original context is entered and subsequently left. Entering the original
* context may fail, for example when the context of a original context value only allows
* single threaded access to values or if it was {@link Context#close() closed} in the mean
* time.
*
* If this option is set to false
then any value passed from one context to
* another will fail with an error indicating that sharing is disallowed. Turning sharing
* off can be useful when strict safety is required and it would be considered an error if a
* value of one context is passed to another.
*
* Values of a guest language that are passed from one context to another are restricted to
* using the interoperability protocol only. In practice this often leads to slight changes
* and incompatibilities in behavior. For example, the prototype of a JavaScript object
* passed from one context to another is not writable, as the interoperability protocol does
* not allow such an operation (yet). A typical use-case is passing big immutable data
* structures that are infrequently accessed from one context to another, without copying
* them. In practice, passing values from one context to another should be avoided if
* possible, as their access is slower and their language compatibility reduced. This
* feature was introduced in 21.3. Older versions fail when guest values are passed from one
* context to another.
*
* @since 21.3
*/
public Builder allowValueSharing(boolean enabled) {
this.allowValueSharing = enabled;
return this;
}
/**
* Allows this context to spawn inner contexts that may change option values set for the
* outer context. If this privilege is set to false
then inner contexts are
* only allowed to use the same option values as its outer context. If this privilege is set
* to true
then the context may modify option values for inner contexts it
* creates. This privilege should not be enabled for security sensitive use-cases. The
* default value for this privilege is inherited from {@link #allowAllAccess(boolean)}.
*
* Inner contexts are spawned by language implementations to implement language embeddings
* of their own. For example, some languages provide dedicated APIs to spawn language
* virtual machines. Such APIs are often implemented using the inner context mechanism.
*
* @see #allowAllAccess(boolean)
* @since 22.3
*/
public Builder allowInnerContextOptions(boolean enabled) {
this.allowInnerContextOptions = enabled;
return this;
}
/**
* Sets a class filter that allows to limit the classes that are allowed to be loaded by
* guest languages. If the filter returns true
, then the class is accessible,
* otherwise it is not accessible and throws a guest language error when accessed. In order
* to have an effect, {@link #allowHostAccess(org.pkl.thirdparty.graalvm.polyglot.HostAccess)} or
* {@link #allowAllAccess(boolean)} needs to be set to true
.
*
* @param classFilter a predicate that returns true
or false
for a
* java qualified class name.
* @since 19.0
* @deprecated use {@link #allowHostClassLookup(Predicate)} instead.
*/
@Deprecated(since = "19.0")
public Builder hostClassFilter(Predicate classFilter) {
Objects.requireNonNull(classFilter);
this.hostClassFilter = classFilter;
return this;
}
/**
* Sets an option for this {@link Context context}. By default, any options for the
* {@link Engine#getOptions() engine}, {@link Language#getOptions() language} or
* {@link Instrument#getOptions() instrument} can be set for a context. If an
* {@link #engine(Engine) explicit engine} is set for the context, then only language
* options can be set. Instrument and engine options can be set exclusively on the explicit
* engine instance. If a language option is set for the context and the engine, then the
* option of the context is going to take precedence.
*
* If one of the set option keys or values is invalid, then an
* {@link IllegalArgumentException} is thrown when the context is {@link #build() built}.
* The given key and value must not be null
.
*
* @see Engine.Builder#option(String, String) To specify an option for the engine.
* @since 19.0
*/
public Builder option(String key, String value) {
Objects.requireNonNull(key);
Objects.requireNonNull(value);
if (this.options == null) {
this.options = new HashMap<>();
}
this.options.put(key, value);
return this;
}
/**
* Shortcut for setting multiple {@link #option(String, String) options} using a map. All
* values of the provided map must be non-null.
*
* @param options a map options.
* @see #option(String, String) To set a single option.
* @since 19.0
*/
public Builder options(Map options) {
for (var entry : options.entrySet()) {
option(entry.getKey(), entry.getValue());
}
return this;
}
/**
* Sets the guest language application arguments for a language {@link Context context}.
* Application arguments are typically made available to guest language implementations. It
* depends on the language if and how they are accessible within the
* {@link Context#eval(Source) evaluated} guest language scripts. Passing no arguments to a
* language is equivalent to providing an empty arguments array.
*
* @param language the language id of the primary language.
* @param args an array of arguments passed to the guest language program.
* @throws IllegalArgumentException if an invalid language id was specified.
* @since 19.0
*/
public Builder arguments(String language, String[] args) {
Objects.requireNonNull(language);
Objects.requireNonNull(args);
String[] newArgs = args;
if (args.length > 0) {
newArgs = new String[args.length];
for (int i = 0; i < args.length; i++) { // defensive copy
newArgs[i] = Objects.requireNonNull(args[i]);
}
}
if (arguments == null) {
arguments = new HashMap<>();
}
arguments.put(language, newArgs);
return this;
}
/**
* Configures guest language access to host IO. Default is {@link IOAccess#NONE}. If
* {@link #allowAllAccess(boolean) all access} is set to {@code true}, then
* {@link IOAccess#ALL} is used unless explicitly set. This method can be used to virtualize
* file system access in the guest language by using an {@link IOAccess} with a custom
* filesystem.
*
* @see IOAccess#ALL - to allow full host IO access
* @see IOAccess#NONE - to disable host IO access
* @see IOAccess#newBuilder() - to create a custom configuration.
*
* @param ioAccess the IO configuration
* @return the {@link Builder}
* @since 23.0
*/
public Builder allowIO(IOAccess ioAccess) {
this.ioAccess = ioAccess;
return this;
}
/**
* If true
, allows guest language to perform unrestricted IO operations on host
* system. Default is false
. If {@link #allowAllAccess(boolean) all access} is
* set to true
, then IO is enabled if not allowed explicitly.
*
* @param enabled {@code true} to enable Input/Output
* @return the {@link Builder}
* @since 19.0
* @deprecated If the value was previously {@code true} pass {@link IOAccess#ALL}, otherwise
* pass {@link IOAccess#NONE}.
*/
@Deprecated(since = "23.0")
public Builder allowIO(final boolean enabled) {
allowIO = enabled;
return this;
}
/**
* Installs a new {@link FileSystem}.
*
* @param fileSystem the file system to be installed
* @return the {@link Builder}
* @since 19.0
* @deprecated If a file system was previously set use
* {@code allowIO(IOAccess.newBuilder().fileSystem(fileSystem).build())}.
*/
@Deprecated(since = "23.0")
public Builder fileSystem(final FileSystem fileSystem) {
Objects.requireNonNull(fileSystem, "FileSystem must be non null.");
this.customFileSystem = fileSystem;
return this;
}
/**
* Take over the transport of messages communication with a server peer. Provide an
* implementation of {@link MessageTransport} to virtualize a transport of messages to a
* server endpoint.
* {@link MessageTransport#open(java.net.URI, org.pkl.thirdparty.graalvm.polyglot.io.MessageEndpoint)}
* corresponds to accept of a server socket.
*
* @param serverTransport an implementation of message transport interceptor
* @see MessageTransport
* @since 19.0
*/
public Builder serverTransport(final MessageTransport serverTransport) {
Objects.requireNonNull(serverTransport, "MessageTransport must be non null.");
this.messageTransport = serverTransport;
return this;
}
/**
* Installs a new logging {@link Handler}. The logger's {@link Level} configuration is done
* using the {@link #options(java.util.Map) Context's options}. The level option key has the
* following format: {@code log.languageId.loggerName.level} or
* {@code log.instrumentId.loggerName.level}. The value is either the name of a pre-defined
* {@link Level} constant or a numeric {@link Level} value. If not explicitly set in
* options, the level is inherited from the parent logger.
*
* Examples of setting log level options:
* {@code builder.option("log.level","FINE");} sets the {@link Level#FINE FINE level} to all
* {@code TruffleLogger}s.
* {@code builder.option("log.js.level","FINE");} sets the {@link Level#FINE FINE level} to
* JavaScript {@code TruffleLogger}s.
* {@code builder.option("log.js.com.oracle.truffle.js.parser.JavaScriptLanguage.level","FINE");}
* sets the {@link Level#FINE FINE level} to {@code TruffleLogger} for the
* {@code JavaScriptLanguage} class.
*
* If the {@code logHandler} is not set on {@link Engine} nor on {@link Context}, the log
* messages are printed to {@link #err(java.io.OutputStream) Context's error output stream}.
*
* @param logHandler the {@link Handler} to use for logging in built {@link Context}. The
* passed {@code logHandler} is closed when the context is {@link Context#close()
* closed}.
* @return the {@link Builder}
* @since 19.0
*/
public Builder logHandler(final Handler logHandler) {
Objects.requireNonNull(logHandler, "Handler must be non null.");
this.customLogHandler = logHandler;
return this;
}
/**
* Sets the default time zone to be used for this context. If not set, or explicitly set to
* null
then the {@link ZoneId#systemDefault() system default} zone will be
* used.
*
* @return the {@link Builder}
* @see ZoneId#systemDefault()
* @since 19.2.0
*/
public Builder timeZone(final ZoneId zone) {
this.zone = zone;
return this;
}
/**
* Installs a new logging {@link Handler} using given {@link OutputStream}. The logger's
* {@link Level} configuration is done using the {@link #options(java.util.Map) Context's
* options}. The level option key has the following format:
* {@code log.languageId.loggerName.level} or {@code log.instrumentId.loggerName.level}. The
* value is either the name of pre-defined {@link Level} constant or a numeric {@link Level}
* value. If not explicitly set in options the level is inherited from the parent logger.
*
* Examples of setting log level options:
* {@code builder.option("log.level","FINE");} sets the {@link Level#FINE FINE level} to all
* {@code TruffleLogger}s.
* {@code builder.option("log.js.level","FINE");} sets the {@link Level#FINE FINE level} to
* JavaScript {@code TruffleLogger}s.
* {@code builder.option("log.js.com.oracle.truffle.js.parser.JavaScriptLanguage.level","FINE");}
* sets the {@link Level#FINE FINE level} to {@code TruffleLogger} for the
* {@code JavaScriptLanguage} class.
*
* If the {@code logHandler} is not set on {@link Engine} nor on {@link Context} the log
* messages are printed to {@link #out(java.io.OutputStream) Context's standard output
* stream}.
*
* @param logOut the {@link OutputStream} to use for logging in built {@link Context}. The
* passed {@code logOut} stream is closed when the context is
* {@link Context#close() closed}.
* @return the {@link Builder}
* @since 19.0
*/
public Builder logHandler(final OutputStream logOut) {
Objects.requireNonNull(logOut, "LogOut must be non null.");
this.customLogHandler = logOut;
return this;
}
/**
* If true
, allows guest language to execute external processes. Default is
* false
. If {@link #allowAllAccess(boolean) all access} is set to
* true
, then process creation is enabled if not denied explicitly.
*
* @param enabled {@code true} to enable external process creation
* @since 19.1.0
*/
public Builder allowCreateProcess(boolean enabled) {
this.allowCreateProcess = enabled;
return this;
}
/**
* Installs a {@link ProcessHandler} responsible for external process creation.
*
* @param handler the handler to be installed
* @since 19.1.0
*/
public Builder processHandler(ProcessHandler handler) {
Objects.requireNonNull(handler, "Handler must be non null.");
this.processHandler = handler;
return this;
}
/**
* Assigns resource limit configuration to a context. By default no resource limits are
* assigned. The limits will be enabled for all contexts created using this builder.
* Assigning a limit may have performance impact of all contexts that run with the same
* engine.
*
* @see ResourceLimits for usage examples
* @since 19.3.0
*/
public Builder resourceLimits(ResourceLimits limits) {
this.resourceLimits = limits;
return this;
}
/**
* Sets a code sandbox policy to a context. By default, the context's sandbox policy is
* {@link SandboxPolicy#TRUSTED}, there are no restrictions to the context configuration.
*
* @see SandboxPolicy
* @since 23.0
*/
public Builder sandbox(SandboxPolicy policy) {
Engine.validateSandboxPolicy(this.sandboxPolicy, policy);
this.sandboxPolicy = policy;
return this;
}
/**
* Allow environment access using the provided policy. If {@link #allowAllAccess(boolean)
* all access} is {@code true} then the default environment access policy is
* {@link EnvironmentAccess#INHERIT}, otherwise {@link EnvironmentAccess#NONE}. The provided
* access policy must not be {@code null}.
*
* @param accessPolicy the {@link EnvironmentAccess environment access policy}
* @since 19.1.0
*/
public Builder allowEnvironmentAccess(EnvironmentAccess accessPolicy) {
Objects.requireNonNull(accessPolicy, "AccessPolicy must be non null.");
this.environmentAccess = accessPolicy;
return this;
}
/**
* Sets an environment variable.
*
* @param name the environment variable name
* @param value the environment variable value
* @since 19.1.0
*/
public Builder environment(String name, String value) {
Objects.requireNonNull(name, "Name must be non null.");
Objects.requireNonNull(value, "Value must be non null.");
if (this.environment == null) {
this.environment = new HashMap<>();
}
this.environment.put(name, value);
return this;
}
/**
* Shortcut for setting multiple {@link #environment(String, String) environment variables}
* using a map. All values of the provided map must be non-null.
*
* @param env environment variables
* @see #environment(String, String) To set a single environment variable.
* @since 19.1.0
*/
public Builder environment(Map env) {
Objects.requireNonNull(env, "Env must be non null.");
for (Map.Entry e : env.entrySet()) {
environment(e.getKey(), e.getValue());
}
return this;
}
/**
* Sets the current working directory used by the guest application to resolve relative
* paths. When the Context is built, the given directory is set as the current working
* directory on the Context's file system using the
* {@link FileSystem#setCurrentWorkingDirectory(java.nio.file.Path)
* FileSystem.setCurrentWorkingDirectory} method.
*
* @param workingDirectory the new current working directory
* @throws NullPointerException when {@code workingDirectory} is {@code null}
* @throws IllegalArgumentException when {@code workingDirectory} is a relative path
* @since 20.0.0
*/
public Builder currentWorkingDirectory(Path workingDirectory) {
Objects.requireNonNull(workingDirectory, "WorkingDirectory must be non null.");
if (!workingDirectory.isAbsolute()) {
throw new IllegalArgumentException("WorkingDirectory must be an absolute path.");
}
this.currentWorkingDirectory = workingDirectory;
return this;
}
/**
* Sets a host class loader. If set the given {@code classLoader} is used to load host
* classes and it's also set as a {@link Thread#setContextClassLoader(java.lang.ClassLoader)
* context ClassLoader} during code execution. Otherwise the ClassLoader that was captured
* when the context was {@link #build() built} is used to to load host classes and the
* {@link Thread#setContextClassLoader(java.lang.ClassLoader) context ClassLoader} is not
* set during code execution. Setting the hostClassLoader has a negative effect on enter and
* leave performance.
*
* @param classLoader the host class loader
* @since 20.1.0
*/
public Builder hostClassLoader(ClassLoader classLoader) {
Objects.requireNonNull(classLoader, "ClassLoader must be non null.");
this.hostClassLoader = classLoader;
return this;
}
/**
* Specifies whether {@link System#exit(int)} may be used to improve efficiency of stack
* unwinding for context exit requested by the guest application.
*
* @since 22.0
* @see Context
*/
public Builder useSystemExit(boolean enabled) {
this.useSystemExit = enabled;
return this;
}
/**
* Creates a new context instance from the configuration provided in the builder. The same
* context builder can be used to create multiple context instances.
*
* @since 19.0
*/
public Context build() {
boolean nativeAccess = orAllAccess(allowNativeAccess);
boolean createThread = orAllAccess(allowCreateThread);
boolean hostClassLoading = orAllAccess(allowHostClassLoading);
boolean experimentalOptions = orAllAccess(allowExperimentalOptions);
boolean innerContextOptions = orAllAccess(allowInnerContextOptions);
if (this.allowHostAccess != null && this.hostAccess != null) {
throw new IllegalArgumentException("The method Context.Builder.allowHostAccess(boolean) and the method Context.Builder.allowHostAccess(HostAccess) are mutually exclusive.");
}
if (ioAccess != null && allowIO != null) {
throw new IllegalArgumentException("The method Context.Builder.allowIO(boolean) and the method Context.Builder.allowIO(IOAccess) are mutually exclusive.");
}
if (ioAccess != null && customFileSystem != null) {
throw new IllegalArgumentException("The method Context.Builder.allowIO(IOAccess) and the method Context.Builder.fileSystem(FileSystem) are mutually exclusive.");
}
SandboxPolicy useSandboxPolicy = resolveSandboxPolicy();
validateSandbox(useSandboxPolicy);
Predicate localHostLookupFilter = this.hostClassFilter;
HostAccess hostAccess = this.hostAccess;
if (this.allowHostAccess != null && this.allowHostAccess) {
if (localHostLookupFilter == UNSET_HOST_LOOKUP) {
// legacy behavior support
localHostLookupFilter = ALL_HOST_CLASSES;
}
// legacy behavior support
hostAccess = HostAccess.ALL;
}
if (hostAccess == null) {
hostAccess = switch (useSandboxPolicy) {
case TRUSTED -> allowAllAccess ? HostAccess.ALL : HostAccess.EXPLICIT;
case CONSTRAINED -> HostAccess.CONSTRAINED;
case ISOLATED -> HostAccess.ISOLATED;
case UNTRUSTED -> HostAccess.UNTRUSTED;
default -> throw new IllegalArgumentException(String.valueOf(useSandboxPolicy));
};
}
PolyglotAccess polyglotAccess = this.polyglotAccess;
if (polyglotAccess == null) {
polyglotAccess = this.allowAllAccess ? PolyglotAccess.ALL : PolyglotAccess.NONE;
}
if (localHostLookupFilter == UNSET_HOST_LOOKUP) {
if (allowAllAccess) {
localHostLookupFilter = ALL_HOST_CLASSES;
} else {
localHostLookupFilter = null;
}
}
boolean hostClassLookupEnabled = localHostLookupFilter != null;
if (localHostLookupFilter == null) {
localHostLookupFilter = NO_HOST_CLASSES;
}
boolean createProcess = orAllAccess(allowCreateProcess);
EnvironmentAccess useEnvironmentAccess = environmentAccess != null ? environmentAccess : allowAllAccess ? EnvironmentAccess.INHERIT : EnvironmentAccess.NONE;
Object limits;
if (resourceLimits != null) {
limits = resourceLimits.receiver;
} else {
limits = null;
}
IOAccess useIOAccess;
if (ioAccess != null) {
useIOAccess = ioAccess;
} else if (allowIO != null || customFileSystem != null) {
if (orAllAccess(allowIO)) {
if (customFileSystem != null) {
useIOAccess = IOAccess.newBuilder().fileSystem(customFileSystem).build();
} else {
useIOAccess = IOAccess.ALL;
}
} else {
if (customFileSystem != null) {
throw new IllegalStateException("Cannot install custom FileSystem when IO is disabled.");
} else {
useIOAccess = IOAccess.NONE;
}
}
} else {
useIOAccess = allowAllAccess ? IOAccess.ALL : IOAccess.NONE;
}
String localCurrentWorkingDirectory = currentWorkingDirectory == null ? null : currentWorkingDirectory.toString();
Engine engine = this.sharedEngine;
Context ctx;
OutputStream contextOut;
OutputStream contextErr;
InputStream contextIn;
Map contextOptions;
if (engine == null) {
org.pkl.thirdparty.graalvm.polyglot.Engine.Builder engineBuilder = Engine.newBuilder(permittedLanguages).options(options == null ? Collections.emptyMap() : options);
// for bound engines we just pass all the options to the engine so they can be
// processed in one step.
contextOptions = Collections.emptyMap();
contextOut = null;
contextErr = null;
contextIn = null;
if (out != null) {
engineBuilder.out(out);
}
if (err != null) {
engineBuilder.err(err);
}
if (in != null) {
engineBuilder.in(in);
}
if (messageTransport != null) {
engineBuilder.serverTransport(messageTransport);
}
if (customLogHandler instanceof Handler) {
engineBuilder.logHandler((Handler) customLogHandler);
} else if (customLogHandler instanceof OutputStream) {
engineBuilder.logHandler((OutputStream) customLogHandler);
}
engineBuilder.sandbox(useSandboxPolicy);
engineBuilder.allowExperimentalOptions(experimentalOptions);
engineBuilder.setBoundEngine(true);
engine = engineBuilder.build();
} else {
if (messageTransport != null) {
throw new IllegalStateException("Cannot use MessageTransport in a context that shares an Engine.");
}
contextOptions = options == null ? Collections.emptyMap() : options;
contextOut = out;
contextErr = err;
contextIn = in;
}
LogHandler logHandler = customLogHandler != null ? Engine.getImpl().newLogHandler(customLogHandler) : null;
ctx = engine.dispatch.createContext(engine.receiver, useSandboxPolicy, contextOut, contextErr, contextIn, hostClassLookupEnabled,
hostAccess, polyglotAccess, nativeAccess, createThread, hostClassLoading, innerContextOptions,
experimentalOptions, localHostLookupFilter, contextOptions, arguments == null ? Collections.emptyMap() : arguments,
permittedLanguages, useIOAccess, logHandler, createProcess, processHandler, useEnvironmentAccess, environment, zone, limits,
localCurrentWorkingDirectory, hostClassLoader, allowValueSharing, useSystemExit);
return ctx;
}
private boolean orAllAccess(Boolean optionalBoolean) {
return optionalBoolean != null ? optionalBoolean : allowAllAccess;
}
/**
* Resolves the actual value of {@code sandboxPolicy}. It can be explicitly set by the
* embedder or inherited from a shared engine.
*/
private SandboxPolicy resolveSandboxPolicy() {
SandboxPolicy engineSandboxPolicy = sharedEngine != null ? sharedEngine.dispatch.getSandboxPolicy(sharedEngine.receiver) : null;
if (sandboxPolicy != null && engineSandboxPolicy != null && sandboxPolicy != engineSandboxPolicy) {
throw Engine.Builder.throwSandboxException(sandboxPolicy,
String.format("The engine and context must have the same SandboxPolicy. The Engine.Builder.sandbox(SandboxPolicy) is set to %s, while the Context.Builder.sandbox(SandboxPolicy) is set to %s.",
engineSandboxPolicy, sandboxPolicy),
String.format("set Engine.Builder.sandbox(SandboxPolicy) to SandboxPolicy.%s or set Context.Builder.sandbox(SandboxPolicy) to SandboxPolicy.%s",
sandboxPolicy,
engineSandboxPolicy));
}
SandboxPolicy useSandboxPolicy;
if (sandboxPolicy != null) {
useSandboxPolicy = sandboxPolicy;
} else if (engineSandboxPolicy != null) {
useSandboxPolicy = engineSandboxPolicy;
} else {
useSandboxPolicy = SandboxPolicy.TRUSTED;
}
return useSandboxPolicy;
}
/**
* Validates configured sandbox policy constrains.
*
* @throws IllegalArgumentException if the context configuration is not compatible with the
* requested sandbox policy.
*/
private void validateSandbox(SandboxPolicy useSandboxPolicy) {
if (useSandboxPolicy == SandboxPolicy.TRUSTED) {
return;
}
if (useSandboxPolicy.isStricterOrEqual(SandboxPolicy.CONSTRAINED)) {
if (permittedLanguages.length == 0 && sharedEngine == null) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder does not have a list of permitted languages.",
"create a Builder with a list of permitted languages, for example, Context.newBuilder(\"js\")");
}
if (allowAllAccess) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder.allowAllAccess(boolean) is set to true, but must not be set to true.",
"do not set Builder.allowAllAccess(boolean)");
}
if (Boolean.TRUE.equals(allowNativeAccess)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder.allowNativeAccess(boolean) is set to true, but must not be set to true.",
"do not set Builder.allowNativeAccess(boolean)");
}
if (Boolean.TRUE.equals(allowHostClassLoading)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder.allowHostClassLoading(boolean) is set to true, but must not be set to true.",
"do not set Builder.allowHostClassLoading(boolean)");
}
if (Boolean.TRUE.equals(allowCreateProcess)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder.allowCreateProcess(boolean) is set to true, but must not be set to true.",
"do not set Builder.allowCreateProcess(boolean)");
}
if (useSystemExit) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder.useSystemExit(boolean) is set to true, but must not be set to true.",
"do not set Builder.useSystemExit(boolean)");
}
if (Engine.isSystemStream(in)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder uses the standard input stream, but the input must be redirected.",
"do not set Builder.in(InputStream) to use InputStream.nullInputStream() or redirect it to other stream than System.in");
}
if (Engine.isSystemStream(out)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder uses the standard output stream, but the output must be redirected.",
"set Builder.out(OutputStream)");
}
if (Engine.isSystemStream(err)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder uses the standard error stream, but the error output must be redirected.",
"set Builder.err(OutputStream)");
}
FileSystem fileSystem;
if (ioAccess != null) {
IOAccessor ioAccessor = Engine.getImpl().getIO();
if (ioAccessor.hasHostFileAccess(ioAccess)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowIO(IOAccess) is set to an IOAccess, which allows access to the host file system, but access to the host file system must be disabled.",
"disable filesystem access using Builder.allowIO(IOAccess.NONE) or install a custom filesystem using Builder.allowIO(IOAccess.newBuilder().fileSystem(customFs))");
}
if (ioAccessor.hasHostSocketAccess(ioAccess)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowIO(IOAccess) is set to an IOAccess, which allows access to host sockets, but access to host sockets must be disabled.",
"do not set IOAccess.Builder.allowHostSocketAccess(boolean)");
}
assert customFileSystem == null;
fileSystem = ioAccessor.getFileSystem(ioAccess);
} else {
fileSystem = customFileSystem;
}
if (fileSystem != null && Engine.getImpl().isHostFileSystem(fileSystem)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowIO(IOAccess) is set to an IOAccess, which has a custom file system that allows access to the host file system, but access to the host file system must be disabled.",
"disable filesystem access using Builder.allowIO(IOAccess.NONE) or install a non-host custom filesystem using Builder.allowIO(IOAccess.newBuilder().fileSystem(customFs))");
}
if (Boolean.TRUE.equals(allowIO)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder.allowIO(boolean) is set to true, but must not be set to true.",
"disable filesystem access using Builder.allowIO(IOAccess.NONE) or install a custom filesystem using Builder.allowIO(IOAccess.newBuilder().fileSystem(customFs))");
}
if (environmentAccess != null && environmentAccess != EnvironmentAccess.NONE) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowEnvironmentAccess(EnvironmentAccess) is set to " + environmentAccess + ", but must be set to EnvironmentAccess.NONE.",
"do not set Builder.allowEnvironmentAccess(EnvironmentAccess) or set it to EnvironmentAccess.NONE");
}
if (Boolean.TRUE.equals(allowHostAccess)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy, "Builder.allowHostAccess(boolean) is set to true, but must not be set to true.",
"do not set Builder.allowHostAccess(boolean) to use the sandbox policy preset or set Builder.allowHostAccess(HostAccess)");
}
if (hostAccess != null) {
if (hostAccess.allowPublic) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowPublicAccess(boolean) set to true, " +
"but HostAccess.Builder.allowPublicAccess(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowPublicAccess(boolean)");
}
if (hostAccess.allowAccessInheritance) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowAccessInheritance(boolean) set to true, " +
"but HostAccess.Builder.allowAccessInheritance(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowAccessInheritance(boolean)");
}
if (hostAccess.allowAllInterfaceImplementations) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowAllImplementations(boolean) set to true, " +
"but HostAccess.Builder.allowAllImplementations(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowAllImplementations(boolean)");
}
if (hostAccess.allowAllClassImplementations) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowAllClassImplementations(boolean) set to true, " +
"but HostAccess.Builder.allowAllClassImplementations(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowAllClassImplementations(boolean)");
}
if (hostAccess.implementableAnnotations != null && hostAccess.implementableAnnotations.contains(FunctionalInterface.class)) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which allows FunctionalInterface implementations, " +
"but FunctionalInterface implementations must not be enabled.",
"do not set HostAccess.Builder.allowImplementationsAnnotatedBy(FunctionalInterface.class)");
}
HostAccess.MutableTargetMapping[] mutableTargetMappings = hostAccess.getMutableTargetMappings();
if (mutableTargetMappings.length > 0) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which allows host object mappings of mutable target types, but it must not be enabled.",
"disable host object mappings of mutable target types by setting HostAccess.Builder.allowMutableTargetMappings(MutableTargetMapping...) to an empty array");
}
}
}
if (useSandboxPolicy.isStricterOrEqual(SandboxPolicy.ISOLATED)) {
if (hostAccess != null && !hostAccess.isMethodScopingEnabled()) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which has no HostAccess.Builder.methodScoping(boolean) set to true, " +
"but HostAccess.Builder.methodScoping(boolean) must be enabled.",
"set HostAccess.Builder.methodScoping(boolean)");
}
}
if (useSandboxPolicy.isStricterOrEqual(SandboxPolicy.UNTRUSTED)) {
if (hostAccess != null) {
if (hostAccess.allowArrayAccess) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowArrayAccess(boolean) set to true, " +
"but HostAccess.Builder.allowArrayAccess(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowArrayAccess(boolean)");
}
if (hostAccess.allowListAccess) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowListAccess(boolean) set to true, " +
"but HostAccess.Builder.allowListAccess(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowListAccess(boolean)");
}
if (hostAccess.allowMapAccess) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowMapAccess(boolean) set to true, " +
"but HostAccess.Builder.allowMapAccess(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowMapAccess(boolean)");
}
if (hostAccess.allowBufferAccess) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowBufferAccess(boolean) set to true, " +
"but HostAccess.Builder.allowBufferAccess(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowBufferAccess(boolean)");
}
if (hostAccess.allowIterableAccess) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowIterableAccess(boolean) set to true, " +
"but HostAccess.Builder.allowIterableAccess(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowIterableAccess(boolean)");
}
if (hostAccess.allowIteratorAccess) {
throw Engine.Builder.throwSandboxException(useSandboxPolicy,
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which was created with HostAccess.Builder.allowIteratorAccess(boolean) set to true, " +
"but HostAccess.Builder.allowIteratorAccess(boolean) must not be set to true.",
"do not set HostAccess.Builder.allowIteratorAccess(boolean)");
}
if (hostAccess.implementableAnnotations != null && !hostAccess.implementableAnnotations.isEmpty()) {
var annotations = StreamSupport.stream(hostAccess.implementableAnnotations.spliterator(), false).map(Class::getSimpleName).toList();
var builderCommands = annotations.stream().map((n) -> String.format("HostAccess.Builder.allowImplementationsAnnotatedBy(%s.class)", n)).toList();
throw Engine.Builder.throwSandboxException(useSandboxPolicy, String.format(
"Builder.allowHostAccess(HostAccess) is set to a HostAccess which allows implementations of types annotated by %s, " +
"but implementations of annotated types must not be enabled.",
String.join(", ", annotations)),
String.format("do not set %s", String.join(", ", builderCommands)));
}
}
}
}
}
}