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

com.oracle.truffle.api.instrumentation.TruffleInstrument Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 24.1.1
Show newest version
/*
 * Copyright (c) 2016, 2020, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * The Universal Permissive License (UPL), Version 1.0
 *
 * Subject to the condition set forth below, permission is hereby granted to any
 * person obtaining a copy of this software, associated documentation and/or
 * data (collectively the "Software"), free of charge and under any and all
 * copyright rights in the Software, and any and all patent rights owned or
 * freely licensable by each licensor hereunder covering either (i) the
 * unmodified Software as contributed to or provided by such licensor, or (ii)
 * the Larger Works (as defined below), to deal in both
 *
 * (a) the Software, and
 *
 * (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
 * one is included with the Software each a "Larger Work" to which the Software
 * is contributed by such licensors),
 *
 * without restriction, including without limitation the rights to copy, create
 * derivative works of, display, perform, and distribute the Software and make,
 * use, sell, offer for sale, import, export, have made, and have sold the
 * Software and the Larger Work(s), and to sublicense the foregoing rights on
 * either these or other terms.
 *
 * This license is subject to the following condition:
 *
 * The above copyright notice and either this complete permission notice or at a
 * minimum a reference to the UPL must be included in all copies or substantial
 * portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.oracle.truffle.api.instrumentation;

import static com.oracle.truffle.api.instrumentation.InstrumentAccessor.ENGINE;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.net.URI;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.ServiceLoader;

import org.graalvm.options.OptionDescriptor;
import org.graalvm.options.OptionDescriptors;
import org.graalvm.options.OptionValues;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.io.MessageEndpoint;
import org.graalvm.polyglot.io.MessageTransport;
import org.graalvm.polyglot.proxy.Proxy;

import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.ContextLocal;
import com.oracle.truffle.api.ContextThreadLocal;
import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.Option;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.TruffleRuntime;
import com.oracle.truffle.api.TruffleStackTrace;
import com.oracle.truffle.api.frame.Frame;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.MaterializedFrame;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.InstrumentationHandler.InstrumentClientInstrumenter;
import com.oracle.truffle.api.interop.InteropLibrary;
import com.oracle.truffle.api.interop.UnsupportedMessageException;
import com.oracle.truffle.api.nodes.ExecutableNode;
import com.oracle.truffle.api.nodes.LanguageInfo;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;

/**
 * The service provider interface (SPI) for Truffle instruments: clients of Truffle instrumentation
 * that may observe and inject behavior into interpreters written using the Truffle framework.
 * 

* Instrument implementation classes must use the {@link Registration} annotation to provide * required metadata and to enable automatic discovery of the implementation. *

* An instrument is {@link #onCreate(Env) created } if at least one instrument * {@link TruffleInstrument.Env#getOptions() option} was specified or if a * {@link TruffleInstrument.Env#registerService(Object) service} was looked up. The * {@link Instrumenter} available in the provided {@linkplain Env environment} allows the instrument * instance to bind listeners for {@linkplain ExecutionEventListener execution} and * {@linkplain LoadSourceListener source} events, as well as {@linkplain ExecutionEventNodeFactory * node factories} for code injection at guest language code locations. *

* An instrument is disposed when the associated polyglot {@linkplain Engine engine} is disposed. * All active bindings created by a disposed instrument become disposed automatically. The * {@link Instrumenter} instance available in the provided {@linkplain Env environment} may not be * used after disposal. *

*

Example for a simple expression coverage instrument:

* {@codesnippet com.oracle.truffle.api.instrumentation.test.examples.CoverageExample} * * @since 0.12 */ public abstract class TruffleInstrument { /** * Constructor for subclasses. * * @since 0.12 */ protected TruffleInstrument() { } List> contextThreadLocals; List> contextLocals; /** * Invoked once on each newly allocated {@link TruffleInstrument} instance. *

* The method may {@link Env#registerService(java.lang.Object) register} additional * {@link Registration#services() services} - e.g. objects to be exposed via * {@link org.graalvm.polyglot.Instrument#lookup lookup query}. For example to expose a debugger * one could define an abstract debugger controller: *

* * {@codesnippet DebuggerController} * * and declare it as a {@link Registration#services() service} associated with the instrument, * implement it, instantiate and {@link Env#registerService(java.lang.Object) register} in own's * instrument {@link #onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env) * onCreate} method: * * {@codesnippet DebuggerExample} * * @param env environment information for the instrument * * @see Env#getInstrumenter() * @since 0.12 */ protected abstract void onCreate(Env env); /** * Invoked once on an {@linkplain TruffleInstrument instance} just before all instruments and * languages are going to be disposed, possibly because the underlying * {@linkplain org.graalvm.polyglot.Engine engine} is going to be closed. This method is called * before {@link #onDispose(Env)} and the instrument must remain usable after finalization. The * instrument can prepare for disposal while still having other instruments not disposed yet. * * @param env environment information for the instrument * @since 19.0 */ protected void onFinalize(Env env) { // default implementation does nothing } /** * Invoked once on an {@linkplain TruffleInstrument instance} when it becomes disabled, possibly * because the underlying {@linkplain org.graalvm.polyglot.Engine engine} has been closed. A * disposed instance is no longer usable. If the instrument is re-enabled, the engine will * create a new instance. * * @param env environment information for the instrument * @since 0.12 */ protected void onDispose(Env env) { // default implementation does nothing } /** * Returns a set of option descriptors that are supported by this instrument. Option values are * accessible using the {@link Env#getOptions() environment} when the instrument is * {@link #onCreate(Env) created}. By default no options are available for an instrument. * Options returned by this method must specify the {@link Registration#id() instrument id} as * {@link OptionDescriptor#getName() name} prefix for each option. For example if the id of the * instrument is "debugger" then a valid option name would be "debugger.Enabled". The instrument * will automatically be {@link #onCreate(Env) created} if one of the specified options was * provided by the engine. To construct option descriptors from a list then * {@link OptionDescriptors#create(List)} can be used. *

* By default option descriptors may only be specified per engine or bound engine, but option * values may also be specified per context. In this case the context specific options can be * specified with {@link #getContextOptionDescriptors()} and the values can be accessed with * {@link Env#getOptions(TruffleContext)}. * * @see Option For an example of declaring the option descriptor using an annotation. * @since 0.27 */ protected OptionDescriptors getOptionDescriptors() { return OptionDescriptors.EMPTY; } /** * Returns a set of option descriptors for instrument options that can be specified per context. * This can be specified in addition to options specified on the engine level, instruments may * specify options for each context. Option descriptors specified per context must not overlap * with option descriptors specified per instrument instance. *

* Example usage: * *

     *
     * @Option.Group(MyInstrument.ID)
     * final class MyContext {
     *
     *     @Option(category = OptionCategory.EXPERT, help = "Description...")
     *     static final OptionKey MyContextOption = new OptionKey<>(Boolean.FALSE);
     * }
     *
     * @Registration(...)
     * class MyInstrument extends TruffleInstruement {
     *
     *   static final OptionDescriptors CONTEXT_OPTIONS = new MyContextOptionDescriptors();
     *
     *   //...
     *
     *   protected OptionDescriptors getContextOptionDescriptors() {
     *      return CONTEXT_OPTIONS;
     *   }
     * }
     * 
* * @see Env#getOptions(TruffleContext) to lookup the option values for a context. * @since 20.3 */ protected OptionDescriptors getContextOptionDescriptors() { return OptionDescriptors.EMPTY; } /** * Creates a new context local reference for this instrument. Context locals for instruments * allow to store additional top-level values for each context similar to language contexts. * This enables instruments to use context local values just as languages using their language * context. Context local factories are guaranteed to be invoked after the instrument is * {@link #onCreate(Env) created}. *

* Context local references must be created during the invocation in the * {@link TruffleInstrument} constructor. Calling this method at a later point in time will * throw an {@link IllegalStateException}. For each registered {@link TruffleInstrument} * subclass it is required to always produce the same number of context local references. The * values produced by the factory must not be null and use a stable exact value * type for each instance of a registered instrument class. If the return value of the factory * is not stable or null then an {@link IllegalStateException} is thrown. These * restrictions allow the Truffle runtime to read the value more efficiently. *

* Usage example: * *

     * @TruffleInstrument.Registration(id = "example", name = "Example Instrument")
     * public static class ExampleInstrument extends TruffleInstrument {
     *
     *     final ContextLocal local = createContextLocal(ExampleLocal::new);
     *
     *     @Override
     *     protected void onCreate(Env env) {
     *         ExecutionEventListener listener = new ExecutionEventListener() {
     *             public void onEnter(EventContext context, VirtualFrame frame) {
     *                 ExampleLocal value = local.get();
     *                 // use context local value;
     *             }
     *
     *             public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
     *             }
     *
     *             public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
     *             }
     *         };
     *
     *         env.getInstrumenter().attachExecutionEventListener(
     *                         SourceSectionFilter.ANY,
     *                         listener);
     *     }
     *
     *     static class ExampleLocal {
     *
     *         final TruffleContext context;
     *
     *         ExampleLocal(TruffleContext context) {
     *             this.context = context;
     *         }
     *
     *     }
     *
     * }
     * 
* * @since 20.3 */ protected final ContextLocal createContextLocal(ContextLocalFactory factory) { ContextLocal local = ENGINE.createInstrumentContextLocal(factory); if (contextLocals == null) { contextLocals = new ArrayList<>(); } try { contextLocals.add(local); } catch (UnsupportedOperationException e) { throw new IllegalStateException("The set of context locals is frozen. Context locals can only be created during construction of the TruffleInstrument subclass."); } return local; } /** * Creates a new context thread local reference for this Truffle language. Context thread locals * for languages allow to store additional top-level values for each context and thread. The * factory may be invoked on any thread other than the thread of the context thread local value. * Context thread local factories are guaranteed to be invoked after the instrument is * {@link #onCreate(Env) created}. *

* Context thread local references must be created during the invocation in the * {@link TruffleLanguage} constructor. Calling this method at a later point in time will throw * an {@link IllegalStateException}. For each registered {@link TruffleLanguage} subclass it is * required to always produce the same number of context thread local references. The values * produces by the factory must not be null and use a stable exact value type for * each instance of a registered language class. If the return value of the factory is not * stable or null then an {@link IllegalStateException} is thrown. These * restrictions allow the Truffle runtime to read the value more efficiently. *

* Context thread locals should not contain a strong reference to the provided thread. Use a * weak reference instance for that purpose. *

* Usage example: * *

     * @TruffleInstrument.Registration(id = "example", name = "Example Instrument")
     * public static class ExampleInstrument extends TruffleInstrument {
     *
     *     final ContextThreadLocal local = createContextThreadLocal(ExampleLocal::new);
     *
     *     @Override
     *     protected void onCreate(Env env) {
     *         ExecutionEventListener listener = new ExecutionEventListener() {
     *             public void onEnter(EventContext context, VirtualFrame frame) {
     *                 ExampleLocal value = local.get();
     *                 // use thread local value;
     *                 assert value.thread.get() == Thread.currentThread();
     *             }
     *
     *             public void onReturnValue(EventContext context, VirtualFrame frame, Object result) {
     *             }
     *
     *             public void onReturnExceptional(EventContext context, VirtualFrame frame, Throwable exception) {
     *             }
     *         };
     *
     *         env.getInstrumenter().attachExecutionEventListener(
     *                         SourceSectionFilter.ANY,
     *                         listener);
     *     }
     *
     *     static class ExampleLocal {
     *
     *         final TruffleContext context;
     *         final WeakReference thread;
     *
     *         ExampleLocal(TruffleContext context, Thread thread) {
     *             this.context = context;
     *             this.thread = new WeakReference<>(thread);
     *         }
     *     }
     *
     * }
     * 
* * @since 20.3 */ protected final ContextThreadLocal createContextThreadLocal(ContextThreadLocalFactory factory) { ContextThreadLocal local = ENGINE.createInstrumentContextThreadLocal(factory); if (contextThreadLocals == null) { contextThreadLocals = new ArrayList<>(); } try { contextThreadLocals.add(local); } catch (UnsupportedOperationException e) { throw new IllegalStateException("The set of context thread locals is frozen. Context thread locals can only be created during construction of the TruffleInstrument subclass."); } return local; } /** * Context local factory for Truffle instruments. Creates a new value per context. * * @since 20.3 */ @FunctionalInterface protected interface ContextLocalFactory { /** * Returns a new value for a context local of an instrument. The returned value must not be * null and must return a stable and exact type per registered instrument. A * thread local must always return the same {@link Object#getClass() class}, even for * multiple instances of the same {@link TruffleInstrument}. * * @see TruffleInstrument#createContextLocal(ContextLocalFactory) * @since 20.3 */ T create(TruffleContext context); } /** * Context local factory for Truffle instruments. Creates a new value per context and thread. * * @since 20.3 */ @FunctionalInterface protected interface ContextThreadLocalFactory { /** * Returns a new value for a context thread local for a context and thread. The returned * value must not be null and must return a stable and exact type per * registered instrument. A thread local must always return the same * {@link Object#getClass() class}, even for multiple instances of the same * {@link TruffleInstrument}. * * @see TruffleInstrument#createContextThreadLocal(ContextThreadLocalFactory) * @since 20.3 */ T create(TruffleContext context, Thread thread); } /** * Access to instrumentation services as well as input, output, and error streams. * * @since 0.12 */ @SuppressWarnings("static-method") public static final class Env { private static final InteropLibrary INTEROP = InteropLibrary.getFactory().getUncached(); private final Object polyglotInstrument; private final InputStream in; private final OutputStream err; private final OutputStream out; private final MessageTransport messageTransport; OptionValues options; InstrumentClientInstrumenter instrumenter; private List services; Env(Object polyglotInstrument, OutputStream out, OutputStream err, InputStream in, MessageTransport messageInterceptor) { this.polyglotInstrument = polyglotInstrument; this.in = in; this.err = err; this.out = out; this.messageTransport = messageInterceptor != null ? new MessageTransportProxy(messageInterceptor) : null; } Object getPolyglotInstrument() { return polyglotInstrument; } /** * Returns the instrumenter which lets you instrument guest language ASTs. * * @see Instrumenter * @since 0.12 */ public Instrumenter getInstrumenter() { return instrumenter; } /** * Input associated with {@link org.graalvm.polyglot.Engine} this {@link TruffleInstrument * instrument} is being executed in. * * @return reader, never null * @since 0.12 */ public InputStream in() { return in; } /** * Standard output writer for {@link org.graalvm.polyglot.Engine} this * {@link TruffleInstrument instrument} is being executed in. * * @return writer, never null * @since 0.12 */ public OutputStream out() { return out; } /** * Standard error writer for {@link org.graalvm.polyglot.Engine} this * {@link TruffleInstrument instrument} is being executed in. * * @return writer, never null * @since 0.12 */ public OutputStream err() { return err; } /** * Start a server at the provided URI via the {@link MessageTransport} service. Before an * instrument creates a server endpoint for a message protocol, it needs to check the result * of this method. When a virtual message transport is available, it blocks until a client * connects and {@link MessageEndpoint} representing the peer endpoint is returned. Those * endpoints need to be used instead of a direct creation of a server socket. If no virtual * message transport is available at that URI, null is returned and the * instrument needs to set up the server itself. *

* When {@link org.graalvm.polyglot.io.MessageTransport.VetoException} is thrown, the server * creation needs to be abandoned. *

* This method can be called concurrently from multiple threads. However, the * {@link MessageEndpoint} ought to be called on one thread at a time, unless you're sure * that the particular implementation can handle concurrent calls. The same holds true for * the returned endpoint, it's called synchronously. * * @param uri the URI of the server endpoint * @param server the handler of messages at the server side * @return an implementation of {@link MessageEndpoint} call back representing the client * side, or null when no virtual transport is available * @throws MessageTransport.VetoException if creation of a server at that URI is not allowed * @since 19.0 */ public MessageEndpoint startServer(URI uri, MessageEndpoint server) throws IOException, MessageTransport.VetoException { if (messageTransport == null) { return null; } return messageTransport.open(uri, server); } /** * Registers additional service. This method can be called multiple time, but only during * {@link #onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env) * initialization of the instrument}. These services are made available to users via * {@link org.graalvm.polyglot.Instrument#lookup} query method. * * This method can only be called from * {@link #onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env)} method - * then the services are collected and cannot be changed anymore. * * @param service a service to be returned from associated * {@link org.graalvm.polyglot.Instrument#lookup} * @throws IllegalStateException if the method is called later than from * {@link #onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env) } * method * @since 0.12 */ public void registerService(Object service) { if (services == null) { throw new IllegalStateException(); } services.add(service); } @SuppressWarnings("unchecked") @TruffleBoundary static RuntimeException engineToInstrumentException(Throwable t) { return InstrumentAccessor.engineAccess().engineToInstrumentException(t); } /** * Queries a {@link TruffleLanguage language implementation} for a special service. The * services can be provided by the language by directly implementing them when subclassing * {@link TruffleLanguage}. The truffle language needs to be entered on the current Thread * otherwise an {@link AssertionError} is thrown. * * @param the requested type * @param language identification of the language to query * @param type the class of the requested type * @return the registered service or null if none is found * @since 0.26 */ public S lookup(LanguageInfo language, Class type) { try { return InstrumentAccessor.engineAccess().lookup(language, type); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns an additional service provided by this instrument, specified by type. If an * instrument is not enabled, it will be enabled automatically by requesting a supported * service. If the instrument does not provide a service for a given type it will not be * enabled automatically. An {@link IllegalArgumentException} is thrown if a service is * looked up from the current instrument. * * @param the requested type * @param instrument identification of the instrument to query * @param type the class of the requested type * @return the registered service or null if none is found * @since 0.26 */ public S lookup(InstrumentInfo instrument, Class type) { try { Object vm = InstrumentAccessor.langAccess().getPolyglotInstrument(instrument); if (vm == this.polyglotInstrument) { throw new IllegalArgumentException("Not allowed to lookup services from the currrent instrument."); } return InstrumentAccessor.engineAccess().lookup(instrument, type); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns a map {@link LanguageInfo#getId() language id} to {@link LanguageInfo language * info} of all languages that are installed in the environment. * * @since 0.26 */ public Map getLanguages() { try { return InstrumentAccessor.engineAccess().getInternalLanguages(polyglotInstrument); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns a map {@link InstrumentInfo#getId() instrument id} to {@link InstrumentInfo * instrument info} of all instruments that are installed in the environment. * * @since 0.26 */ public Map getInstruments() { try { return InstrumentAccessor.engineAccess().getInstruments(polyglotInstrument); } catch (Throwable t) { throw engineToInstrumentException(t); } } Object[] onCreate(TruffleInstrument instrument) { List arr = new ArrayList<>(); services = arr; try { instrument.onCreate(this); } finally { services = null; } return arr.toArray(); } /** * Returns the context independent option values for the options described in * {@link TruffleInstrument#getOptionDescriptors()}. The returned options are never * null. * * @see #getOptions(TruffleContext) to return the context specific options for this * instrument. * @since 0.27 */ public OptionValues getOptions() { return options; } /** * Returns the context specific option values for the options described in * {@link TruffleInstrument#getContextOptionDescriptors()} and * {@link TruffleInstrument#getOptionDescriptors()}. Instrument context options can be * different for each TruffleContext, whereas regular options cannot. * * @see #getOptions() to get the context independent options set for this instrument * @since 20.3 */ @TruffleBoundary public OptionValues getOptions(TruffleContext context) { Objects.requireNonNull(context); return InstrumentAccessor.ENGINE.getInstrumentContextOptions(polyglotInstrument, InstrumentAccessor.LANGUAGE.getPolyglotContext(context)); } /** * Evaluates source of (potentially different) language using the current context.The names * of arguments are parameters for the resulting {#link CallTarget} that allow the * source to reference the actual parameters passed to * {@link CallTarget#call(java.lang.Object...)}. * * @param source the source to evaluate * @param argumentNames the names of {@link CallTarget#call(java.lang.Object...)} arguments * that can be referenced from the source * @return the call target representing the parsed result * @throws IOException if the parsing or evaluation fails for some reason * @throws SecurityException * @since 0.12 */ public CallTarget parse(Source source, String... argumentNames) throws IOException { try { TruffleLanguage.Env env = InstrumentAccessor.engineAccess().getEnvForInstrument(source.getLanguage(), source.getMimeType()); return InstrumentAccessor.langAccess().parse(env, source, null, argumentNames); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Parses source snippet of the node's language at the provided node location. The result is * an AST fragment represented by {@link ExecutableNode} that accepts frames valid at the * provided node location, or null when inline parsing is not supported by the * language. * * @param source a source snippet to parse at the provided node location * @param node a context location where the source is parsed at, must not be * null * @param frame a frame location where the source is parsed at, can be null * @return the executable fragment representing the parsed result, or null * @since 0.31 */ public ExecutableNode parseInline(Source source, Node node, MaterializedFrame frame) { try { if (node == null) { throw new IllegalArgumentException("Node must not be null."); } TruffleLanguage.Env env = InstrumentAccessor.engineAccess().getEnvForInstrument(source.getLanguage(), source.getMimeType()); // Assert that the languages match: assert InstrumentAccessor.langAccess().getLanguageInfo(env) == node.getRootNode().getLanguageInfo(); ExecutableNode fragment = InstrumentAccessor.langAccess().parseInline(env, source, node, frame); if (fragment != null) { TruffleLanguage languageSPI = InstrumentAccessor.langAccess().getSPI(env); fragment = new GuardedExecutableNode(languageSPI, fragment, frame); } return fragment; } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns a {@link TruffleFile} for given path. This must be called on a context thread * only. * * @param path the absolute or relative path to create {@link TruffleFile} for * @return {@link TruffleFile} * @since 19.0 */ public TruffleFile getTruffleFile(String path) { try { return InstrumentAccessor.engineAccess().getTruffleFile(path); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns a {@link TruffleFile} for given {@link URI}. This must be called on a context * thread only. * * @param uri the {@link URI} to create {@link TruffleFile} for * @return {@link TruffleFile} * @since 19.0 */ public TruffleFile getTruffleFile(URI uri) { try { return InstrumentAccessor.engineAccess().getTruffleFile(uri); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns the entered {@link TruffleContext} or {@code null} when no context is entered. * * @since 20.3 */ public TruffleContext getEnteredContext() { return InstrumentAccessor.ENGINE.getCurrentCreatorTruffleContext(); } private static class GuardedExecutableNode extends ExecutableNode { private final FrameDescriptor frameDescriptor; @Child private ExecutableNode fragment; GuardedExecutableNode(TruffleLanguage languageSPI, ExecutableNode fragment, MaterializedFrame frameLocation) { super(languageSPI); this.frameDescriptor = (frameLocation != null) ? frameLocation.getFrameDescriptor() : null; this.fragment = fragment; } @Override public Object execute(VirtualFrame frame) { assert frameDescriptor == null || frameDescriptor == frame.getFrameDescriptor(); assureAdopted(); Object ret = fragment.execute(frame); assert checkNullOrInterop(ret); return ret; } private void assureAdopted() { if (getParent() == null) { CompilerDirectives.transferToInterpreterAndInvalidate(); throw new IllegalStateException("Needs to be inserted into the AST before execution."); } } } private static boolean checkNullOrInterop(Object obj) { if (obj == null) { return true; } InstrumentAccessor.interopAccess().checkInteropType(obj); return true; } /** * Returns true if the given root node is considered an engine evaluation root * for the current execution context. Multiple such root nodes can appear on stack frames * returned by * {@link TruffleRuntime#iterateFrames(com.oracle.truffle.api.frame.FrameInstanceVisitor)}. * A debugger implementation might use this information to hide stack frames of other * engines. * * @param root the root node to check * @return true if engine root else false * @since 0.17 */ public boolean isEngineRoot(RootNode root) { try { return InstrumentAccessor.engineAccess().isEvalRoot(root); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Request for languages to provide stack frames of scheduled asynchronous execution. * Languages might not provide asynchronous stack frames by default for performance reasons. * At most depth asynchronous stack frames are asked for. When multiple * instruments call this method, the languages get a maximum depth of these calls and may * therefore provide longer asynchronous stacks than requested. Also, languages may provide * asynchronous stacks if it's of no performance penalty, or if requested by other options. *

* Asynchronous stacks can then be accessed via * {@link TruffleStackTrace#getAsynchronousStackTrace(CallTarget, Frame)}. * * @param depth the requested stack depth, 0 means no asynchronous stack frames are * required. * @see TruffleStackTrace#getAsynchronousStackTrace(CallTarget, Frame) * @since 20.1.0 */ public void setAsynchronousStackDepth(int depth) { InstrumentAccessor.engineAccess().setAsynchronousStackDepth(polyglotInstrument, depth); } /** * Returns the {@link LanguageInfo language info} for a given language class if available. * Language classes are typically obtained by invoking the * {@link InteropLibrary#getLanguage(Object)} message. Throws an * {@link IllegalArgumentException} if the provided language is not registered. Note that * languages may be returned that are not contained in {@link #getLanguages()}. For example, * values originating from the embedder like Java classes or {@link Proxy polyglot proxies}. * * @param languageClass the language class to convert * @return the associated language info * @throws IllegalArgumentException if the language class is not valid. * * @since 20.1 */ @TruffleBoundary public LanguageInfo getLanguageInfo(Class> languageClass) { try { Objects.requireNonNull(languageClass); return InstrumentAccessor.engineAccess().getLanguageInfo(polyglotInstrument, languageClass); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Wraps the provided value to provide language specific information for primitive and * foreign values. A typical implementation of a given language for this method does the * following: *

    *
  • Return the current language as their associated * {@link com.oracle.truffle.api.interop.InteropLibrary#getLanguage(Object) language}. *
  • Provide a language specific * {@link com.oracle.truffle.api.interop.InteropLibrary#toDisplayString(Object) display * string} for primitive and foreign values. *
  • Return a language specific * {@link com.oracle.truffle.api.interop.InteropLibrary#getMetaObject(Object) metaobject} * primitive or foreign values. *
  • Add members to the object that would implicitly be available for all objects. For * example, any JavaScript object is expected to have a prototype member. Foreign objects, * even if they do not have such a member, are interpreted as if they have. *
* * @param language the language to provide the view for * @param value the value to language specific information for. * @see TruffleLanguage#getLanguageView(Object, Object) * @since 20.1 */ @TruffleBoundary public Object getLanguageView(LanguageInfo language, Object value) { try { Objects.requireNonNull(language); return InstrumentAccessor.engineAccess().getLanguageView(language, value); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns the scoped view of a value for a location in the AST. Allows the language to * augment the perspective that tools have on values depending on location and frame. This * may be useful to apply local specific visibility and accessibility rules. A typical * implementation of this method may do the following: *
    *
  • Apply visiblity and scoping rules to the value hiding or removing members from the * object. *
  • Add or remove implicit members that are only available within this source location. *
*

* The provided language must match the language of the {@link Node#getRootNode() root node} * of the location provided. The frame must have the same {@link FrameDescriptor} associated * as the {@link RootNode} of the provided location. The provided location must be * {@link InstrumentableNode#isInstrumentable() instrumentable}. If any of these * pre-conditions are violated then an {@link IllegalArgumentException} is thrown. *

* If a value is not yet associated with the provided language, then a * {@link #getLanguageView(LanguageInfo, Object) language view} will be requested * implicitly. Only {@link InteropLibrary interop} messages should be used on the result of * this method. * * @param language the language must match the language * @param location the location to provide scope for. Never null and returns * true for {@link InstrumentableNode#isInstrumentable()}. E.g. any * location observed using {@link EventContext#getInstrumentedNode()}. * @param frame the frame of the current activation of the parent {@link RootNode}. * @param value the value to provide scope information for. * * @see com.oracle.truffle.api.interop.NodeLibrary#getView(Object, Frame, Object) * @since 20.1 * @deprecated in 20.3 for removal, use {@link #getLanguageView(LanguageInfo, Object)} * followed by * {@link com.oracle.truffle.api.interop.NodeLibrary#getView(Object, Frame, Object)} * instead. */ @Deprecated @TruffleBoundary public Object getScopedView(LanguageInfo language, Node location, Frame frame, Object value) { try { Objects.requireNonNull(language); return InstrumentAccessor.engineAccess().getScopedView(language, location, frame, value); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Uses the provided language to print a string representation of this value. The behavior * of this method is undefined if a type unknown to the language is passed as a value. * * @param language a language * @param value a known value of that language, must be an interop type (i.e. either * implementing TruffleObject or be a primitive value) * @return a human readable string representation of the value. * @see #findLanguage(java.lang.Object) * @since 0.27 * @deprecated in 20.1 for removal, use {@link #getLanguageView(LanguageInfo, Object)} and * {@link InteropLibrary#toDisplayString(Object)} instead. */ @Deprecated @TruffleBoundary public String toString(LanguageInfo language, Object value) { try { Object displayString = INTEROP.toDisplayString(getLanguageView(language, value)); try { return INTEROP.asString(displayString); } catch (UnsupportedMessageException e) { throw new AssertionError("Message toDisplayResult does not return a value string."); } } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Uses the provided language to find a metaobject of a value, if any. The metaobject * represents a description of the object, reveals it's kind and it's features. Some * information that a metaobject might define includes the base object's type, interface, * class, methods, attributes, etc. When no metaobject is known, null is * returned. For the best results, use the {@link #findLanguage(java.lang.Object) value's * language}, if any. * * @param language a language * @param value a value to find the metaobject of, must be an interop type (i.e. either * implementing TruffleObject or be a primitive value) * @return the metaobject, or null * @see #findLanguage(java.lang.Object) * @since 0.27 * @deprecated in 20.1 for removal, use {@link #getLanguageView(LanguageInfo, Object)} and * {@link InteropLibrary#getMetaObject(Object)} instead. */ @Deprecated public Object findMetaObject(LanguageInfo language, Object value) { try { InstrumentAccessor.interopAccess().checkInteropType(value); try { return INTEROP.getMetaObject(getLanguageView(language, value)); } catch (UnsupportedMessageException e) { return null; } } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Uses the provided language to find a source location where a value is declared, if any. * For the best results, use the {@link #findLanguage(java.lang.Object) value's language}, * if any. * * @param language a language * @param value a value to get the source location for, must be an interop type (i.e. either * implementing TruffleObject or be a primitive value) * @return a source location of the object, or null * @see #findLanguage(java.lang.Object) * @since 0.27 * @deprecated in 20.1 for removal, use {@link InteropLibrary#getSourceLocation(Object)} * instead. */ @Deprecated public SourceSection findSourceLocation(LanguageInfo language, Object value) { try { try { Object view = getLanguageView(language, value); if (INTEROP.hasSourceLocation(view)) { return INTEROP.getSourceLocation(view); } } catch (UnsupportedMessageException e) { } return null; } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Find a language that created the value, if any. This method will return null * for values representing a primitive value, or objects that are not associated with any * language. * * @param value the value to find a language of * @return the language, or null when there is no language associated with the * value. * @since 0.27 * @deprecated use {@link InteropLibrary#getLanguage(Object)} with * {@link #getLanguageInfo(Class)} instead. */ @Deprecated public LanguageInfo findLanguage(Object value) { try { LanguageInfo language = null; if (INTEROP.hasLanguage(value)) { try { language = getLanguageInfo(INTEROP.getLanguage(value)); } catch (UnsupportedMessageException e) { CompilerDirectives.transferToInterpreterAndInvalidate(); throw new AssertionError(e); } } return language; } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns the polyglot scope - symbols explicitly exported by languages. * * @return a read-only map of symbol names and their values * @since 0.30 * @deprecated Use {@link #getPolyglotBindings()} instead. */ @Deprecated public Map getExportedSymbols() { try { return InstrumentAccessor.engineAccess().getExportedSymbols(); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Returns the polyglot scope - symbols explicitly exported by languages. The polyglot * bindings of the current entered context are returned. * * @return an interop object having the symbol names as properties * @since 20.1 */ public Object getPolyglotBindings() { try { return InstrumentAccessor.engineAccess().getPolyglotBindingsObject(); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Find a list of local scopes enclosing the given {@link Node node}. The scopes contain * variables that are valid at the provided node and that have a relation to it. Unless the * node is in a global scope, it is expected that there is at least one scope provided, that * corresponds to the enclosing function. Global top scopes are provided by * {@link #findTopScopes(java.lang.String)}. The iteration order corresponds with the scope * nesting, from the inner-most to the outer-most. *

* Scopes may depend on the information provided by the frame.
* Lexical scopes are returned when frame argument is null. * * @param node a node to get the enclosing scopes for. The node needs to be inside a * {@link RootNode} associated with a language. * @param frame The current frame the node is in, or null for lexical access * when the program is not running, or is not suspended at the node's location. * @return an {@link Iterable} providing list of scopes from the inner-most to the * outer-most. * @see TruffleLanguage#findLocalScopes(java.lang.Object, com.oracle.truffle.api.nodes.Node, * com.oracle.truffle.api.frame.Frame) * @since 0.30 * @deprecated in 20.3, use NodeLibrary instead. */ @Deprecated @SuppressWarnings("deprecation") public Iterable findLocalScopes(Node node, Frame frame) { try { RootNode rootNode = node.getRootNode(); if (rootNode == null) { throw new IllegalArgumentException("The node " + node + " does not have a RootNode."); } LanguageInfo languageInfo = rootNode.getLanguageInfo(); if (languageInfo == null) { throw new IllegalArgumentException("The root node " + rootNode + " does not have a language associated."); } Iterable langScopes = InstrumentAccessor.engineAccess().findLibraryLocalScopesToLegacy(node, frame); assert langScopes != null : languageInfo.getId(); return langScopes; } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Find a list of top scopes of a language. The iteration order corresponds with the scope * nesting, from the inner-most to the outer-most. * * @param languageId a language id. * @return a list of top scopes, can be empty when no top scopes are provided by the * language * @see TruffleLanguage#findTopScopes(java.lang.Object) * @since 0.30 * @deprecated in 20.3, use {@link #getScope(LanguageInfo)} instead. */ @Deprecated @SuppressWarnings("deprecation") public Iterable findTopScopes(String languageId) { LanguageInfo languageInfo = getLanguages().get(languageId); if (languageInfo == null) { throw new IllegalArgumentException("Unknown language: " + languageId + ". Known languages are: " + getLanguages().keySet()); } Object scope = getScope(languageInfo); return InstrumentAccessor.engineAccess().topScopesToLegacy(scope); } /** * Provides top scope object of the language, if any. Uses the current context to find the * language scope associated with. The returned object is an * {@link com.oracle.truffle.api.interop.InteropLibrary#isScope(Object) interop scope * object}, or null. * * @param language a language * @return the top scope, or null if the language does not support such concept * @see TruffleLanguage#getScope(Object) * @since 20.3 */ public Object getScope(LanguageInfo language) { assert language != null; try { final TruffleLanguage.Env env = InstrumentAccessor.engineAccess().getEnvForInstrument(language); return InstrumentAccessor.langAccess().getScope(env); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Find or create an engine bound logger for an instrument. When a logging is required from * a thread which entered a context the context's logging handler and options are used. * Otherwise the engine's logging handler and options are used. *

* If a logger with given name already exists it's returned, otherwise a new logger is * created. *

* Unlike loggers created by * {@link TruffleLogger#getLogger(java.lang.String, java.lang.String) * TruffleLogger.getLogger} loggers created by this method are bound to engine, there may be * more logger instances having the same name but each bound to different engine instance. * Instruments should never store the returned logger into a static fields. A new logger * must be always created in * {@link TruffleInstrument#onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env) * onCreate} method. * * @param loggerName the the name of a {@link TruffleLogger}, if a {@code loggerName} is * null or empty a root logger for language or instrument is returned * @return a {@link TruffleLogger} * @since 19.0 */ public TruffleLogger getLogger(String loggerName) { try { return InstrumentAccessor.engineAccess().getLogger(polyglotInstrument, loggerName); } catch (Throwable t) { throw engineToInstrumentException(t); } } /** * Find or create an engine bound logger for an instrument. The engine bound loggers can be * used by threads executing without any current context. When a logging is required from a * thread which entered a context the context's logging handler and options are used. * Otherwise the engine's logging handler and options are used. *

* If a logger for the class already exists it's returned, otherwise a new logger is * created. *

* Unlike loggers created by * {@link TruffleLogger#getLogger(java.lang.String, java.lang.Class) * TruffleLogger.getLogger} loggers created by this method are bound to engine, there may be * more logger instances having the same name but each bound to different engine instance. * Instruments should never store the returned logger into a static fields. A new logger * must be always created in * {@link TruffleInstrument#onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env) * onCreate} method. * * @param forClass the {@link Class} to create a logger for * @return a {@link TruffleLogger} * @throws NullPointerException if {@code forClass} is null * @since 19.0 */ public TruffleLogger getLogger(Class forClass) { return getLogger(forClass.getName()); } @SuppressWarnings("deprecation") static Iterable findTopScopes(TruffleLanguage.Env env) { Iterable langScopes = InstrumentAccessor.langAccess().findTopScopes(env); assert langScopes != null : InstrumentAccessor.langAccess().getLanguageInfo(env).getId(); return langScopes; } private static class MessageTransportProxy implements MessageTransport { private final MessageTransport transport; MessageTransportProxy(MessageTransport transport) { this.transport = transport; } @Override public MessageEndpoint open(URI uri, MessageEndpoint peerEndpoint) throws IOException, VetoException { Objects.requireNonNull(peerEndpoint, "The peer endpoint must be non null."); MessageEndpoint openedEndpoint = transport.open(uri, new MessageEndpointProxy(peerEndpoint)); if (openedEndpoint == null) { return null; } return new MessageEndpointProxy(openedEndpoint); } private static class MessageEndpointProxy implements MessageEndpoint { private final MessageEndpoint endpoint; MessageEndpointProxy(MessageEndpoint endpoint) { this.endpoint = endpoint; } @Override public void sendText(String text) throws IOException { endpoint.sendText(text); } @Override public void sendBinary(ByteBuffer data) throws IOException { endpoint.sendBinary(data); } @Override public void sendPing(ByteBuffer data) throws IOException { endpoint.sendPing(data); } @Override public void sendPong(ByteBuffer data) throws IOException { endpoint.sendPong(data); } @Override public void sendClose() throws IOException { endpoint.sendClose(); } } } } /** * Annotation that registers an {@link TruffleInstrument instrument} implementations for * automatic discovery. * * @since 0.12 */ @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE) public @interface Registration { /** * A custom machine identifier for this instrument. If not defined then the fully qualified * class name is used. * * @since 0.12 */ String id() default ""; /** * The name of the instrument in an arbitrary format for humans. * * @since 0.12 */ String name() default ""; /** * The version for instrument in an arbitrary format. It inherits from * {@link org.graalvm.polyglot.Engine#getVersion()} by default. * * @since 0.12 */ String version() default "inherit"; /** * Specifies whether the instrument is accessible using the polyglot API. Internal * instruments are only accessible from other instruments or guest languages. * * @since 0.27 */ boolean internal() default false; /** * Declarative list of classes this instrument is known to provide. The instrument is * supposed to override its * {@link #onCreate(com.oracle.truffle.api.instrumentation.TruffleInstrument.Env) onCreate} * method and instantiate and {@link Env#registerService(java.lang.Object) register} all * here in defined services. *

* Instruments automatically get created when their registered * {@link org.graalvm.polyglot.Instrument#lookup(java.lang.Class) service is requested}. * * @since 0.25 * @return list of service types that this instrument can provide */ Class[] services() default { }; } /** * Used to register a {@link TruffleInstrument} using a {@link ServiceLoader}. This interface is * not intended to be implemented directly by an instrument developer, rather the implementation * is generated by the Truffle DSL. The generated implementation has to inherit the * {@link Registration} annotations from the {@link TruffleInstrument}. * * @since 19.3.0 */ public interface Provider { /** * Returns the name of a class implementing the {@link TruffleInstrument}. * * @since 19.3.0 */ String getInstrumentClassName(); /** * Creates a new instance of a {@link TruffleInstrument}. * * @since 19.3.0 */ TruffleInstrument create(); /** * Returns the class names of provided services. * * @since 19.3.0 */ Collection getServicesClassNames(); } static { try { // Instrument is loaded by Engine which should load InstrumentationHandler // this is important to load the accessors properly. Class.forName(InstrumentationHandler.class.getName(), true, InstrumentationHandler.class.getClassLoader()); } catch (ClassNotFoundException ex) { throw new IllegalStateException(ex); } } }