All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.pkl.thirdparty.graalvm.polyglot.Context Maven / Gradle / Ivy

Go to download

Shaded fat Jar for pkl-config-java, a Java config library based on the Pkl config language.

There is a newer version: 0.27.1
Show newest version
/*
 * 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: *

    *
  1. If the hostValue is null, then it will be interpreted as * polyglot {@link Value#isNull() null}. *
  2. If the hostValue is already a {@link Value polyglot value}, then it will be * cast to {@link Value}. *
  3. 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). *
  4. If the hostValue is an instance of {@link Character} or {@link String}, then * it will be interpreted as polyglot {@link Value#isString() string}. *
  5. If the hostValue is an instance of {@link Boolean}, then it will be * interpreted as polyglot {@link Value#isBoolean() boolean}. *
  6. 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}. *
  7. 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}. *
  8. 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}. *
  9. 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}. *
  10. If the hostValue is an instance of {@link Duration} then it will be * interpreted as polyglot {@link Value#isDuration() duration}. *
  11. 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. *
  12. 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. *
  13. 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 events = new ArrayDeque(); // list of arbitrary size
     *
     *   public void processEvents() {
     *     Context context = Context.getCurrent();
     *
     *     while (Object event = events.pop()) {
     *
     *         // process event
     *
     *         // allow cancellation and interruptions
     *         try {
     *             context.safepoint();
     *         } catch (PolyglotException e) {
     *             if (e.isInterrupted() || e.isCancelled()) {
     *                 // break event processing if interrupted or cancelled
     *                 throw e;
     *             }
     *             // other handling of guest errors or rethrow
     *         }
     *     }
     *  }
     * }
     * 
     *
     * @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))); } } } } } }