/*
* Copyright (c) 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package com.oracle.truffle.api.instrumentation;
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.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.InstrumentInfo;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleRuntime;
import com.oracle.truffle.api.instrumentation.InstrumentationHandler.AccessorInstrumentHandler;
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;
/**
* <p>
* The service provider interface (SPI) for Truffle
* {@linkplain com.oracle.truffle.api.vm.PolyglotEngine.Instrument Instruments}: clients of Truffle
* instrumentation that may observe and inject behavior into interpreters written using the Truffle
* framework.
* <p>
* Each registered instrument can be
* {@linkplain com.oracle.truffle.api.vm.PolyglotEngine.Instrument#setEnabled(boolean)
* enabled/disabled} multiple times during the lifetime of a
* {@link com.oracle.truffle.api.vm.PolyglotEngine PolyglotEngine}, but there is never more than one
* instance per engine. A new {@link TruffleInstrument} instance is created each time the instrument
* is enabled, and the currently enabled instance is disposed when the instrument is disabled.
* </p>
* <h4>Registration</h4>
* <p>
* Instrument implementation classes must use the {@link Registration} annotation to provide
* required metadata and to enable automatic discovery of the implementation.
* </p>
* <h4>Instrument Creation</h4>
* <ul>
* <li>When an instrument becomes
* {@linkplain com.oracle.truffle.api.vm.PolyglotEngine.Instrument#setEnabled(boolean) enabled}, a
* new instance is created and notified once via {@link #onCreate(Env)}.</li>
* <li>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.</li>
* </ul>
* <h4>Instrument Disposal</h4>
* <ul>
* <li>When an instrument becomes
* {@linkplain com.oracle.truffle.api.vm.PolyglotEngine.Instrument#setEnabled(boolean) disabled},
* the current instance is notified once via {@link #onDispose(Env)}.</li>
* <li>All active bindings created by a disposed instrument become disposed automatically.</li>
* <li>The {@link Instrumenter} instance available in the provided {@linkplain Env environment} may
* not be used after disposal.</li>
* <li>All enabled instruments in an engine become disabled automatically when the engine is
* disposed.</li>
* </ul>
* <h4>Example for a simple expression coverage instrument:</h4>
* {@codesnippet com.oracle.truffle.api.instrumentation.test.examples.CoverageExample}
*
* @since 0.12
*/
public abstract class TruffleInstrument {
/**
* Constructor for subclasses.
*
* @since 0.12
*/
protected TruffleInstrument() {
}
/**
* Invoked once on each newly allocated {@link TruffleInstrument} instance.
* <p>
* The method may {@link Env#registerService(java.lang.Object) register} additional
* {@link Registration#services() services} - e.g. objects to be exposed via
* {@link com.oracle.truffle.api.vm.PolyglotRuntime.Instrument#lookup lookup query}. For example
* to expose a debugger one could define an abstract debugger controller:
* </p>
*
* {@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} when it becomes
* {@linkplain com.oracle.truffle.api.vm.PolyglotEngine.Instrument#setEnabled(boolean) disabled}
* , possibly because the underlying {@linkplain com.oracle.truffle.api.vm.PolyglotEngine
* engine} has been disposed. 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
}
/**
* Access to instrumentation services as well as input, output, and error streams.
*
* @since 0.12
*/
@SuppressWarnings("static-method")
public static final class Env {
private final Object vmObject; // PolyglotRuntime.Instrument
private final Instrumenter instrumenter;
private final InputStream in;
private final OutputStream err;
private final OutputStream out;
private List<Object> services;
Env(Object vm, Instrumenter instrumenter, OutputStream out, OutputStream err, InputStream in) {
this.vmObject = vm;
this.instrumenter = instrumenter;
this.in = in;
this.err = err;
this.out = out;
}
/**
* Returns the instrumenter which lets you instrument guest language ASTs.
*
* @see Instrumenter
* @since 0.12
*/
public Instrumenter getInstrumenter() {
return instrumenter;
}
/**
* Input associated with {@link com.oracle.truffle.api.vm.PolyglotEngine} this
* {@link TruffleInstrument instrument} is being executed in.
*
* @return reader, never <code>null</code>
* @since 0.12
*/
public InputStream in() {
return in;
}
/**
* Standard output writer for {@link com.oracle.truffle.api.vm.PolyglotEngine} this
* {@link TruffleInstrument instrument} is being executed in.
*
* @return writer, never <code>null</code>
* @since 0.12
*/
public OutputStream out() {
return out;
}
/**
* Standard error writer for {@link com.oracle.truffle.api.vm.PolyglotEngine} this
* {@link TruffleInstrument instrument} is being executed in.
*
* @return writer, never <code>null</code>
* @since 0.12
*/
public OutputStream err() {
return err;
}
/**
* 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 com.oracle.truffle.api.vm.PolyglotEngine.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 com.oracle.truffle.api.vm.PolyglotEngine.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);
}
/**
* 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}.
*
* @param <S> the requested type
* @param language identification of the language to query
* @param type the class of the requested type
* @return the registered service or <code>null</code> if none is found
* @since 0.26
*/
public <S> S lookup(LanguageInfo language, Class<S> type) {
return AccessorInstrumentHandler.engineAccess().lookup(language, type);
}
/**
* 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 <S> the requested type
* @param instrument identification of the instrument to query
* @param type the class of the requested type
* @return the registered service or <code>null</code> if none is found
* @since 0.26
*/
public <S> S lookup(InstrumentInfo instrument, Class<S> type) {
Object vm = AccessorInstrumentHandler.langAccess().getVMObject(instrument);
if (vm == this.vmObject) {
throw new IllegalArgumentException("Not allowed to lookup services from the currrent instrument.");
}
return AccessorInstrumentHandler.engineAccess().lookup(instrument, type);
}
/**
* Returns a map mime-type to language identifier of all languages that are installed in the
* environment.
*
* @since 0.26
*/
public Map<String, LanguageInfo> getLanguages() {
return AccessorInstrumentHandler.engineAccess().getLanguages(vmObject);
}
/**
* Returns a map mime-type to instrument identifier of all instruments that are installed in
* the environment.
*
* @since 0.26
*/
public Map<String, InstrumentInfo> getInstruments() {
return AccessorInstrumentHandler.engineAccess().getInstruments(vmObject);
}
Object[] onCreate(TruffleInstrument instrument) {
List<Object> arr = new ArrayList<>();
services = arr;
try {
instrument.onCreate(this);
} finally {
services = null;
}
return arr.toArray();
}
/**
* Evaluates source of (potentially different) language using the current context.The names
* of arguments are parameters for the resulting {#link CallTarget} that allow the
* <code>source</code> 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
* @since 0.12
*/
public CallTarget parse(Source source, String... argumentNames) throws IOException {
TruffleLanguage.Env env = AccessorInstrumentHandler.engineAccess().getEnvForInstrument(vmObject, source.getMimeType());
return AccessorInstrumentHandler.langAccess().parse(env, source, null, argumentNames);
}
/**
* Returns <code>true</code> 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 <code>true</code> if engine root else <code>false</code>
* @since 0.17
*/
public boolean isEngineRoot(RootNode root) {
return AccessorInstrumentHandler.engineAccess().isEvalRoot(root);
}
/**
* Uses the original language of the node 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
* value.
*
* @param node a node
* @param value a known value of that language
* @return a human readable string representation of the value.
* @since 0.17
*/
public String toString(Node node, Object value) {
final TruffleLanguage.Env env = getLangEnv(node);
return AccessorInstrumentHandler.langAccess().toStringIfVisible(env, value, false);
}
/**
* Find a meta-object of a value, if any. The meta-object represents a description of the
* object, reveals it's kind and it's features. Some information that a meta-object might
* define includes the base object's type, interface, class, methods, attributes, etc. When
* no meta-object is known, <code>null</code> is returned.
*
* @param node a node
* @param value a value to find the meta-object of
* @return the meta-object, or <code>null</code>
* @since 0.22
*/
public Object findMetaObject(Node node, Object value) {
final TruffleLanguage.Env env = getLangEnv(node);
return AccessorInstrumentHandler.langAccess().findMetaObject(env, value);
}
/**
* Find a source location where a value is declared, if any.
*
* @param node a node
* @param value a value to get the source location for
* @return a source location of the object, or <code>null</code>
* @since 0.22
*/
public SourceSection findSourceLocation(Node node, Object value) {
final TruffleLanguage.Env env = getLangEnv(node);
return AccessorInstrumentHandler.langAccess().findSourceLocation(env, value);
}
private static TruffleLanguage.Env getLangEnv(Node node) {
LanguageInfo languageInfo = node.getRootNode().getLanguageInfo();
if (languageInfo == null) {
throw new IllegalArgumentException("No language available for given node.");
}
return AccessorInstrumentHandler.engineAccess().getEnvForInstrument(languageInfo);
}
}
/**
* Annotation that registers an {@link TruffleInstrument instrument} implementations for
* automatic discovery.
*
* @since 0.12
*/
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Registration {
/**
* A custom machine identifier for this instrument. If not defined then the fully qualified
* class name is used.
*/
String id() default "";
/**
* The name of the instrument in an arbitrary format for humans.
*/
String name() default "";
/**
* The version for instrument in an arbitrary format.
*/
String version() default "";
/**
* 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.
* <p>
* Instruments
* {@link com.oracle.truffle.api.vm.PolyglotEngine.Instrument#setEnabled(boolean) get
* automatically enabled} when their registered
* {@link com.oracle.truffle.api.vm.PolyglotEngine.Instrument#lookup(java.lang.Class)
* service is requested}.
*
* @since 0.25
* @return list of service types that this instrument can provide
*/
Class<?>[] services() default {};
}
static {
try {
// Instrument is loaded by PolyglotEngine 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);
}
}
}