Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.oracle.truffle.api.instrumentation.InstrumentationHandler Maven / Gradle / Ivy
Go to download
Truffle is a multi-language framework for executing dynamic languages
that achieves high performance when combined with Graal.
/*
* 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.io.PrintStream;
import java.lang.ref.WeakReference;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReferenceArray;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.impl.Accessor;
import com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode;
import com.oracle.truffle.api.instrumentation.ProbeNode.EventChainNode;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Env;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.NodeUtil;
import com.oracle.truffle.api.nodes.NodeVisitor;
import com.oracle.truffle.api.nodes.RootNode;
import com.oracle.truffle.api.source.Source;
import com.oracle.truffle.api.source.SourceSection;
/**
* Central coordinator class for the Truffle instrumentation framework. Allocated once per
* {@linkplain com.oracle.truffle.api.vm.PolyglotEngine engine}.
*/
final class InstrumentationHandler {
/* Enable trace output to stdout. */
private static final boolean TRACE = Boolean.getBoolean("truffle.instrumentation.trace");
private final Map sources = Collections.synchronizedMap(new WeakHashMap());
/* Load order needs to be preserved for sources, thats why we store sources again in a list. */
private final Collection sourcesList = new WeakAsyncList<>(16);
private final Collection loadedRoots = new WeakAsyncList<>(256);
private final Collection executedRoots = new WeakAsyncList<>(64);
private final Collection> executionBindings = new EventBindingList(8);
private final Collection> sourceSectionBindings = new EventBindingList(8);
private final Collection> sourceBindings = new EventBindingList(8);
/*
* Fast lookup of instrumenter instances based on a key provided by the accessor.
*/
private final ConcurrentHashMap instrumenterMap = new ConcurrentHashMap<>();
private final OutputStream out;
private final OutputStream err;
private final InputStream in;
private final Map, Set>> cachedProvidedTags = new ConcurrentHashMap<>();
private InstrumentationHandler(OutputStream out, OutputStream err, InputStream in) {
this.out = out;
this.err = err;
this.in = in;
}
void onLoad(RootNode root) {
if (!AccessorInstrumentHandler.nodesAccess().isInstrumentable(root)) {
return;
}
SourceSection sourceSection = root.getSourceSection();
if (sourceSection != null) {
// notify sources
Source source = sourceSection.getSource();
boolean isNewSource = false;
synchronized (sources) {
if (!sources.containsKey(source)) {
sources.put(source, null);
sourcesList.add(source);
isNewSource = true;
}
}
// we don't want to invoke foreign code while we are holding a lock to avoid deadlocks.
if (isNewSource) {
notifySourceBindingsLoaded(sourceBindings, source);
}
}
loadedRoots.add(root);
// fast path no bindings attached
if (!sourceSectionBindings.isEmpty()) {
visitRoot(root, new NotifyLoadedListenerVisitor(sourceSectionBindings));
}
}
void onFirstExecution(RootNode root) {
if (!AccessorInstrumentHandler.nodesAccess().isInstrumentable(root)) {
return;
}
executedRoots.add(root);
// fast path no bindings attached
if (executionBindings.isEmpty()) {
return;
}
visitRoot(root, new InsertWrappersVisitor(executionBindings));
}
void addInstrument(Object key, Class> clazz) {
addInstrumenter(key, new InstrumentClientInstrumenter(clazz, out, err, in));
}
void disposeInstrumenter(Object key, boolean cleanupRequired) {
AbstractInstrumenter disposedInstrumenter = instrumenterMap.remove(key);
if (disposedInstrumenter == null) {
throw new AssertionError("Instrumenter already disposed.");
}
if (TRACE) {
trace("BEGIN: Dispose instrumenter %n", key);
}
disposedInstrumenter.dispose();
if (cleanupRequired) {
Collection> disposedExecutionBindings = filterBindingsForInstrumenter(executionBindings, disposedInstrumenter);
if (!disposedExecutionBindings.isEmpty()) {
visitRoots(executedRoots, new DisposeWrappersWithBindingVisitor(disposedExecutionBindings));
}
disposeBindingsBulk(disposedExecutionBindings);
disposeBindingsBulk(filterBindingsForInstrumenter(sourceSectionBindings, disposedInstrumenter));
disposeBindingsBulk(filterBindingsForInstrumenter(sourceBindings, disposedInstrumenter));
}
if (TRACE) {
trace("END: Disposed instrumenter %n", key);
}
}
private static void disposeBindingsBulk(Collection> list) {
for (EventBinding> binding : list) {
binding.disposeBulk();
}
}
Instrumenter forLanguage(TruffleLanguage.Env context, TruffleLanguage> language) {
return new LanguageClientInstrumenter<>(language, context);
}
EventBinding addExecutionBinding(EventBinding binding) {
if (TRACE) {
trace("BEGIN: Adding execution binding %s, %s%n", binding.getFilter(), binding.getElement());
}
this.executionBindings.add(binding);
if (!executedRoots.isEmpty()) {
visitRoots(executedRoots, new InsertWrappersWithBindingVisitor(binding));
}
if (TRACE) {
trace("END: Added execution binding %s, %s%n", binding.getFilter(), binding.getElement());
}
return binding;
}
EventBinding addSourceSectionBinding(EventBinding binding, boolean notifyLoaded) {
if (TRACE) {
trace("BEGIN: Adding binding %s, %s%n", binding.getFilter(), binding.getElement());
}
this.sourceSectionBindings.add(binding);
if (notifyLoaded) {
if (!loadedRoots.isEmpty()) {
visitRoots(loadedRoots, new NotifyLoadedWithBindingVisitor(binding));
}
}
if (TRACE) {
trace("END: Added binding %s, %s%n", binding.getFilter(), binding.getElement());
}
return binding;
}
EventBinding addSourceBinding(EventBinding binding, boolean notifyLoaded) {
if (TRACE) {
trace("BEGIN: Adding source binding %s, %s%n", binding.getFilter(), binding.getElement());
}
this.sourceBindings.add(binding);
if (notifyLoaded) {
for (Source source : sourcesList) {
notifySourceBindingLoaded(binding, source);
}
}
if (TRACE) {
trace("END: Added source binding %s, %s%n", binding.getFilter(), binding.getElement());
}
return binding;
}
private void visitRoots(Collection roots, AbstractNodeVisitor addBindingsVisitor) {
for (RootNode root : roots) {
visitRoot(root, addBindingsVisitor);
}
}
void disposeBinding(EventBinding> binding) {
if (TRACE) {
trace("BEGIN: Dispose binding %s, %s%n", binding.getFilter(), binding.getElement());
}
if (binding.isExecutionEvent()) {
visitRoots(executedRoots, new DisposeWrappersVisitor(binding));
}
if (TRACE) {
trace("END: Disposed binding %s, %s%n", binding.getFilter(), binding.getElement());
}
}
EventChainNode createBindings(ProbeNode probeNodeImpl) {
EventContext context = probeNodeImpl.getContext();
SourceSection sourceSection = context.getInstrumentedSourceSection();
if (TRACE) {
trace("BEGIN: Lazy update for %s%n", sourceSection);
}
RootNode rootNode = probeNodeImpl.getRootNode();
Node instrumentedNode = probeNodeImpl.findWrapper().getDelegateNode();
Set> providedTags = getProvidedTags(rootNode);
EventChainNode root = null;
EventChainNode parent = null;
for (EventBinding> binding : executionBindings) {
if (binding.isInstrumentedFull(providedTags, rootNode, instrumentedNode, sourceSection)) {
if (TRACE) {
trace(" Found binding %s, %s%n", binding.getFilter(), binding.getElement());
}
EventChainNode next = probeNodeImpl.createEventChainCallback(binding);
if (next == null) {
continue;
}
if (root == null) {
root = next;
} else {
assert parent != null;
parent.setNext(next);
}
parent = next;
}
}
if (TRACE) {
trace("END: Lazy updated for %s%n", sourceSection);
}
return root;
}
private static void notifySourceBindingsLoaded(Collection> bindings, Source source) {
for (EventBinding> binding : bindings) {
notifySourceBindingLoaded(binding, source);
}
}
private static void notifySourceBindingLoaded(EventBinding> binding, Source source) {
if (!binding.isDisposed() && binding.isInstrumentedSource(source)) {
try {
((LoadSourceListener) binding.getElement()).onLoad(new LoadSourceEvent(source));
} catch (Throwable t) {
if (binding.isLanguageBinding()) {
throw t;
} else {
ProbeNode.exceptionEventForClientInstrument(binding, "onLoad", t);
}
}
}
}
static void notifySourceSectionLoaded(EventBinding> binding, Node node, SourceSection section) {
LoadSourceSectionListener listener = (LoadSourceSectionListener) binding.getElement();
try {
listener.onLoad(new LoadSourceSectionEvent(section, node));
} catch (Throwable t) {
if (binding.isLanguageBinding()) {
throw t;
} else {
ProbeNode.exceptionEventForClientInstrument(binding, "onLoad", t);
}
}
}
private void addInstrumenter(Object key, AbstractInstrumenter instrumenter) throws AssertionError {
Object previousKey = instrumenterMap.putIfAbsent(key, instrumenter);
if (previousKey != null) {
throw new AssertionError("Instrumenter already present.");
}
instrumenter.initialize();
}
private static Collection> filterBindingsForInstrumenter(Collection> bindings, AbstractInstrumenter instrumenter) {
if (bindings.isEmpty()) {
return Collections.emptyList();
}
Collection> newBindings = new ArrayList<>();
for (EventBinding> binding : bindings) {
if (binding.getInstrumenter() == instrumenter) {
newBindings.add(binding);
}
}
return newBindings;
}
@SuppressWarnings("unchecked")
private void insertWrapper(Node instrumentableNode, SourceSection sourceSection) {
final Node node = instrumentableNode;
final Node parent = node.getParent();
if (parent instanceof WrapperNode) {
// already wrapped, need to invalidate the wrapper something changed
invalidateWrapperImpl((WrapperNode) parent, node);
return;
}
ProbeNode probe = new ProbeNode(InstrumentationHandler.this, sourceSection);
WrapperNode wrapper;
try {
Class> factory = null;
Class> currentClass = instrumentableNode.getClass();
while (currentClass != null) {
Instrumentable instrumentable = currentClass.getAnnotation(Instrumentable.class);
if (instrumentable != null) {
factory = instrumentable.factory();
break;
}
currentClass = currentClass.getSuperclass();
}
if (factory == null) {
if (TRACE) {
trace("No wrapper inserted for %s, section %s. Not annotated with @Instrumentable.%n", node, sourceSection);
}
// node or superclass is not annotated with @Instrumentable
return;
}
if (TRACE) {
trace("Insert wrapper for %s, section %s%n", node, sourceSection);
}
wrapper = ((InstrumentableFactory) factory.newInstance()).createWrapper(instrumentableNode, probe);
} catch (Exception e) {
throw new IllegalStateException("Failed to create wrapper node. ", e);
}
if (!(wrapper instanceof Node)) {
throw new IllegalStateException(String.format("Implementation of %s must be a subclass of %s.", WrapperNode.class.getSimpleName(), Node.class.getSimpleName()));
}
final Node wrapperNode = (Node) wrapper;
if (wrapperNode.getParent() != null) {
throw new IllegalStateException(String.format("Instance of provided %s is already adopted by another parent.", WrapperNode.class.getSimpleName()));
}
if (parent == null) {
throw new IllegalStateException(String.format("Instance of instrumentable %s is not adopted by a parent.", Node.class.getSimpleName()));
}
if (!NodeUtil.isReplacementSafe(parent, node, wrapperNode)) {
throw new IllegalStateException(
String.format("WrapperNode implementation %s cannot be safely replaced in parent node class %s.", wrapperNode.getClass().getName(), parent.getClass().getName()));
}
node.replace(wrapperNode, "Insert instrumentation wrapper node.");
}
private EventBinding attachFactory(AbstractInstrumenter instrumenter, SourceSectionFilter filter, T factory) {
return addExecutionBinding(new EventBinding<>(instrumenter, filter, factory, true));
}
private EventBinding attachListener(AbstractInstrumenter instrumenter, SourceSectionFilter filter, T listener) {
return addExecutionBinding(new EventBinding<>(instrumenter, filter, listener, true));
}
private EventBinding attachSourceListener(AbstractInstrumenter abstractInstrumenter, SourceSectionFilter filter, T listener, boolean notifyLoaded) {
return addSourceBinding(new EventBinding<>(abstractInstrumenter, filter, listener, false), notifyLoaded);
}
private EventBinding attachSourceSectionListener(AbstractInstrumenter abstractInstrumenter, SourceSectionFilter filter, T listener, boolean notifyLoaded) {
return addSourceSectionBinding(new EventBinding<>(abstractInstrumenter, filter, listener, false), notifyLoaded);
}
Set> getProvidedTags(Class> language) {
Set> tags = cachedProvidedTags.get(language);
if (tags == null) {
ProvidedTags languageTags = language.getAnnotation(ProvidedTags.class);
List> languageTagsList = languageTags != null ? Arrays.asList(languageTags.value()) : Collections.> emptyList();
tags = Collections.unmodifiableSet(new HashSet<>(languageTagsList));
cachedProvidedTags.put(language, tags);
}
return tags;
}
Set> getProvidedTags(RootNode root) {
Class> language = AccessorInstrumentHandler.nodesAccess().findLanguage(root);
if (language != null) {
return getProvidedTags(language);
} else {
return Collections.emptySet();
}
}
private static boolean isInstrumentableNode(Node node, SourceSection sourceSection) {
return !(node instanceof WrapperNode) && !(node instanceof RootNode) && sourceSection != null;
}
private static void trace(String message, Object... args) {
PrintStream out = System.out;
out.printf(message, args);
}
private void visitRoot(final RootNode root, final AbstractNodeVisitor visitor) {
if (TRACE) {
trace("BEGIN: Visit root %s for %s%n", root.toString(), visitor);
}
visitor.root = root;
visitor.providedTags = getProvidedTags(root);
if (visitor.shouldVisit()) {
if (TRACE) {
trace("BEGIN: Traverse root %s for %s%n", root.toString(), visitor);
}
root.accept(visitor);
if (TRACE) {
trace("END: Traverse root %s for %s%n", root.toString(), visitor);
}
}
if (TRACE) {
trace("END: Visited root %s for %s%n", root.toString(), visitor);
}
}
static void removeWrapper(ProbeNode node) {
if (TRACE) {
trace("Remove wrapper for %s%n", node.getContext().getInstrumentedSourceSection());
}
WrapperNode wrapperNode = node.findWrapper();
((Node) wrapperNode).replace(wrapperNode.getDelegateNode());
}
private static void invalidateWrapper(Node node) {
Node parent = node.getParent();
if (!(parent instanceof WrapperNode)) {
// not yet wrapped
return;
}
invalidateWrapperImpl((WrapperNode) parent, node);
}
private static void invalidateWrapperImpl(WrapperNode parent, Node node) {
ProbeNode probeNode = parent.getProbeNode();
if (TRACE) {
SourceSection section = probeNode.getContext().getInstrumentedSourceSection();
trace("Invalidate wrapper for %s, section %s %n", node, section);
}
if (probeNode != null) {
probeNode.invalidate();
}
}
static boolean hasTagImpl(Set> providedTags, Node node, Class> tag) {
if (providedTags.contains(tag)) {
return AccessorInstrumentHandler.nodesAccess().isTaggedWith(node, tag);
}
return false;
}
static Instrumentable getInstrumentable(Node node) {
Instrumentable instrumentable = node.getClass().getAnnotation(Instrumentable.class);
if (instrumentable != null && !(node instanceof WrapperNode)) {
return instrumentable;
}
return null;
}
private T lookup(Object key, Class type) {
AbstractInstrumenter value = instrumenterMap.get(key);
return value == null ? null : value.lookup(this, type);
}
private abstract static class AbstractNodeVisitor implements NodeVisitor {
RootNode root;
Set> providedTags;
abstract boolean shouldVisit();
}
private abstract class AbstractBindingVisitor extends AbstractNodeVisitor {
protected final EventBinding> binding;
AbstractBindingVisitor(EventBinding> binding) {
this.binding = binding;
}
@Override
boolean shouldVisit() {
return binding.isInstrumentedRoot(providedTags, root, root.getSourceSection());
}
public final boolean visit(Node node) {
SourceSection sourceSection = node.getSourceSection();
if (isInstrumentableNode(node, sourceSection)) {
if (binding.isInstrumentedLeaf(providedTags, node, sourceSection)) {
if (TRACE) {
traceFilterCheck("hit", providedTags, binding, node, sourceSection);
}
visitInstrumented(node, sourceSection);
} else {
if (TRACE) {
traceFilterCheck("miss", providedTags, binding, node, sourceSection);
}
}
}
return true;
}
protected abstract void visitInstrumented(Node node, SourceSection section);
}
private static void traceFilterCheck(String result, Set> providedTags, EventBinding> binding, Node node, SourceSection sourceSection) {
Set> tags = binding.getFilter().getReferencedTags();
Set> containedTags = new HashSet<>();
for (Class> tag : tags) {
if (hasTagImpl(providedTags, node, tag)) {
containedTags.add(tag);
}
}
trace(" Filter %4s %s section:%s tags:%s%n", result, binding.getFilter(), sourceSection, containedTags);
}
private abstract class AbstractBindingsVisitor extends AbstractNodeVisitor {
private final Collection> bindings;
private final boolean visitForEachBinding;
AbstractBindingsVisitor(Collection> bindings, boolean visitForEachBinding) {
this.bindings = bindings;
this.visitForEachBinding = visitForEachBinding;
}
@Override
boolean shouldVisit() {
if (bindings.isEmpty()) {
return false;
}
final RootNode localRoot = root;
if (localRoot == null) {
return false;
}
SourceSection sourceSection = localRoot.getSourceSection();
for (EventBinding> binding : bindings) {
if (binding.isInstrumentedRoot(providedTags, localRoot, sourceSection)) {
return true;
}
}
return false;
}
public final boolean visit(Node node) {
SourceSection sourceSection = node.getSourceSection();
if (isInstrumentableNode(node, sourceSection)) {
// no locking required for these atomic reference arrays
for (EventBinding> binding : bindings) {
if (binding.isInstrumentedFull(providedTags, root, node, sourceSection)) {
if (TRACE) {
traceFilterCheck("hit", providedTags, binding, node, sourceSection);
}
visitInstrumented(binding, node, sourceSection);
if (!visitForEachBinding) {
break;
}
} else {
if (TRACE) {
traceFilterCheck("miss", providedTags, binding, node, sourceSection);
}
}
}
}
return true;
}
protected abstract void visitInstrumented(EventBinding> binding, Node node, SourceSection section);
}
/* Insert wrappers for a single bindings. */
private final class InsertWrappersWithBindingVisitor extends AbstractBindingVisitor {
InsertWrappersWithBindingVisitor(EventBinding> filter) {
super(filter);
}
@Override
protected void visitInstrumented(Node node, SourceSection section) {
insertWrapper(node, section);
}
}
private final class DisposeWrappersVisitor extends AbstractBindingVisitor {
DisposeWrappersVisitor(EventBinding> binding) {
super(binding);
}
@Override
protected void visitInstrumented(Node node, SourceSection section) {
invalidateWrapper(node);
}
}
private final class InsertWrappersVisitor extends AbstractBindingsVisitor {
InsertWrappersVisitor(Collection> bindings) {
super(bindings, false);
}
@Override
protected void visitInstrumented(EventBinding> binding, Node node, SourceSection section) {
insertWrapper(node, section);
}
}
private final class DisposeWrappersWithBindingVisitor extends AbstractBindingsVisitor {
DisposeWrappersWithBindingVisitor(Collection> bindings) {
super(bindings, false);
}
@Override
protected void visitInstrumented(EventBinding> binding, Node node, SourceSection section) {
invalidateWrapper(node);
}
}
private final class NotifyLoadedWithBindingVisitor extends AbstractBindingVisitor {
NotifyLoadedWithBindingVisitor(EventBinding> binding) {
super(binding);
}
@Override
protected void visitInstrumented(Node node, SourceSection section) {
notifySourceSectionLoaded(binding, node, section);
}
}
private final class NotifyLoadedListenerVisitor extends AbstractBindingsVisitor {
NotifyLoadedListenerVisitor(Collection> bindings) {
super(bindings, true);
}
@Override
protected void visitInstrumented(EventBinding> binding, Node node, SourceSection section) {
notifySourceSectionLoaded(binding, node, section);
}
}
/**
* Provider of instrumentation services for {@linkplain TruffleInstrument external clients} of
* instrumentation.
*/
final class InstrumentClientInstrumenter extends AbstractInstrumenter {
private final Class> instrumentClass;
private Object[] services;
private TruffleInstrument instrument;
private final Env env;
InstrumentClientInstrumenter(Class> instrumentClass, OutputStream out, OutputStream err, InputStream in) {
this.instrumentClass = instrumentClass;
this.env = new Env(this, out, err, in);
}
@Override
boolean isInstrumentableRoot(RootNode rootNode) {
return true;
}
@Override
public Set> queryTags(Node node) {
return queryTagsImpl(node, null);
}
@Override
void verifyFilter(SourceSectionFilter filter) {
}
Class> getInstrumentClass() {
return instrumentClass;
}
Env getEnv() {
return env;
}
@Override
void initialize() {
if (TRACE) {
trace("Initialize instrument %s class %s %n", instrument, instrumentClass);
}
assert instrument == null;
try {
this.instrument = (TruffleInstrument) instrumentClass.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
failInstrumentInitialization(String.format("Failed to create new instrumenter class %s", instrumentClass.getName()), e);
return;
}
try {
services = env.onCreate(instrument);
} catch (Throwable e) {
failInstrumentInitialization(String.format("Failed calling onCreate of instrument class %s", instrumentClass.getName()), e);
return;
}
if (TRACE) {
trace("Initialized instrument %s class %s %n", instrument, instrumentClass);
}
}
private void failInstrumentInitialization(String message, Throwable t) {
Exception exception = new Exception(message, t);
PrintStream stream = new PrintStream(env.err());
exception.printStackTrace(stream);
}
boolean isInitialized() {
return instrument != null;
}
TruffleInstrument getInstrument() {
return instrument;
}
@Override
void dispose() {
instrument.onDispose(env);
}
@Override
T lookup(InstrumentationHandler handler, Class type) {
if (services != null) {
for (Object service : services) {
if (type.isInstance(service)) {
return type.cast(service);
}
}
}
return null;
}
}
/**
* Provider of instrumentation services for {@linkplain TruffleLanguage language
* implementations}.
*/
final class LanguageClientInstrumenter extends AbstractInstrumenter {
@SuppressWarnings("unused") private final TruffleLanguage.Env env;
private final TruffleLanguage language;
LanguageClientInstrumenter(TruffleLanguage language, TruffleLanguage.Env env) {
this.language = language;
this.env = env;
}
@Override
boolean isInstrumentableRoot(RootNode node) {
if (AccessorInstrumentHandler.nodesAccess().findLanguage(node.getRootNode()) != language.getClass()) {
return false;
}
// TODO (chumer) check for the context instance
return true;
}
@Override
public Set> queryTags(Node node) {
return queryTagsImpl(node, language.getClass());
}
@Override
void verifyFilter(SourceSectionFilter filter) {
Set> providedTags = getProvidedTags(language.getClass());
// filters must not reference tags not declared in @RequiredTags
Set> referencedTags = filter.getReferencedTags();
if (!providedTags.containsAll(referencedTags)) {
Set> missingTags = new HashSet<>(referencedTags);
missingTags.removeAll(providedTags);
Set> allTags = new LinkedHashSet<>(providedTags);
allTags.addAll(missingTags);
StringBuilder builder = new StringBuilder("{");
String sep = "";
for (Class> tag : allTags) {
builder.append(sep);
builder.append(tag.getSimpleName());
sep = ", ";
}
builder.append("}");
throw new IllegalArgumentException(String.format("The attached filter %s references the following tags %s which are not declared as provided by the language. " +
"To fix this annotate the language class %s with @%s(%s).",
filter, missingTags, language.getClass().getName(), ProvidedTags.class.getSimpleName(), builder));
}
}
@Override
void initialize() {
// nothing to do
}
@Override
void dispose() {
// nothing to do
}
@Override
S lookup(InstrumentationHandler handler, Class type) {
return null;
}
}
/**
* Shared implementation of instrumentation services for clients whose requirements and
* privileges may vary.
*/
abstract class AbstractInstrumenter extends Instrumenter {
abstract void initialize();
abstract void dispose();
abstract T lookup(InstrumentationHandler handler, Class type);
void disposeBinding(EventBinding> binding) {
InstrumentationHandler.this.disposeBinding(binding);
}
abstract boolean isInstrumentableRoot(RootNode rootNode);
final Set> queryTagsImpl(Node node, Class> onlyLanguage) {
SourceSection sourceSection = node.getSourceSection();
if (!InstrumentationHandler.isInstrumentableNode(node, sourceSection)) {
return Collections.emptySet();
}
RootNode root = node.getRootNode();
if (root == null) {
return Collections.emptySet();
}
Class> language = AccessorInstrumentHandler.nodesAccess().findLanguage(root);
if (onlyLanguage != null && language != onlyLanguage) {
throw new IllegalArgumentException("The language instrumenter cannot query tags of nodes of other languages.");
}
Set> providedTags = getProvidedTags(root);
if (providedTags.isEmpty()) {
return Collections.emptySet();
}
Set> tags = new HashSet<>();
for (Class> providedTag : providedTags) {
if (hasTagImpl(providedTags, node, providedTag)) {
tags.add(providedTag);
}
}
return Collections.unmodifiableSet(tags);
}
@Override
public final EventBinding attachFactory(SourceSectionFilter filter, T factory) {
verifyFilter(filter);
return InstrumentationHandler.this.attachFactory(this, filter, factory);
}
@Override
public final EventBinding attachListener(SourceSectionFilter filter, T listener) {
verifyFilter(filter);
return InstrumentationHandler.this.attachListener(this, filter, listener);
}
@Override
public EventBinding attachLoadSourceListener(SourceSectionFilter filter, T listener, boolean notifyLoaded) {
verifySourceOnly(filter);
verifyFilter(filter);
return InstrumentationHandler.this.attachSourceListener(this, filter, listener, notifyLoaded);
}
@Override
public EventBinding attachLoadSourceSectionListener(SourceSectionFilter filter, T listener, boolean notifyLoaded) {
verifyFilter(filter);
return InstrumentationHandler.this.attachSourceSectionListener(this, filter, listener, notifyLoaded);
}
private void verifySourceOnly(SourceSectionFilter filter) {
if (!filter.isSourceOnly()) {
throw new IllegalArgumentException(String.format("The attached filter %s uses filters that require source sections to verifiy. " +
"Source listeners can only use filter critera based on Source objects like mimeTypeIs or sourceIs.", filter));
}
}
abstract void verifyFilter(SourceSectionFilter filter);
}
/**
* A list collection data structure that is optimized for fast non-blocking traversals. There is
* adds and no explicit removal. Removals are based on a side effect of the element, by
* returning null
in {@link AbstractAsyncCollection#unwrap(Object)}. It is not
* possible to reliably query the {@link AbstractAsyncCollection#size()} of the collection,
* therefore it throws an {@link UnsupportedOperationException}.
*/
private abstract static class AbstractAsyncCollection extends AbstractCollection {
/*
* We use an atomic reference list as we don't want to see holes in the array when appending
* to it. This allows us to use null as a safe terminator for the array.
*/
private volatile AtomicReferenceArray values;
/*
* Size can be non volatile as it is not exposed or used for traversal.
*/
private int nextInsertionIndex;
AbstractAsyncCollection(int initialCapacity) {
if (initialCapacity <= 0) {
throw new IllegalArgumentException("Invalid initial capacity " + initialCapacity);
}
this.values = new AtomicReferenceArray<>(initialCapacity);
}
@Override
public final synchronized boolean add(R reference) {
T wrappedElement = wrap(reference);
if (wrappedElement == null) {
// fail early
throw new NullPointerException();
}
if (nextInsertionIndex >= values.length()) {
compact();
}
values.set(nextInsertionIndex++, wrappedElement);
return true;
}
@Override
public int size() {
// size cannot be supported reliably
throw new UnsupportedOperationException();
}
@Override
public final boolean isEmpty() {
return values.get(0) == null;
}
protected abstract T wrap(R element);
protected abstract R unwrap(T element);
private void compact() {
AtomicReferenceArray localValues = values;
int liveElements = 0;
/*
* We count the still alive elements.
*/
for (int i = 0; i < localValues.length(); i++) {
T ref = localValues.get(i);
if (ref == null) {
break;
}
if (unwrap(ref) != null) {
liveElements++;
}
}
/*
* We ensure that the capacity after compaction is always twice as big as the number of
* live elements. This can make the array grow or shrink as needed.
*/
AtomicReferenceArray newValues = new AtomicReferenceArray<>(Math.max(liveElements * 2, 8));
int index = 0;
for (int i = 0; i < localValues.length(); i++) {
T ref = localValues.get(i);
if (ref == null) {
break;
}
if (unwrap(ref) != null) {
newValues.set(index++, ref);
}
}
this.nextInsertionIndex = index;
this.values = newValues;
}
/**
* Returns an iterator which can be traversed without a lock. The iterator that is
* constructed is not sequentially consistent. In other words, the user of the iterator may
* observe values that were added after the iterator was created.
*/
@Override
public Iterator iterator() {
return new Iterator() {
/*
* We need to capture the values field in the iterator to have a consistent view on
* the data while iterating.
*/
private final AtomicReferenceArray values = AbstractAsyncCollection.this.values;
private int index;
private R queuedNext;
public boolean hasNext() {
R next = queuedNext;
if (next == null) {
next = queueNext();
queuedNext = next;
}
return next != null;
}
private R queueNext() {
int localIndex = index;
AtomicReferenceArray array = values;
while (true) {
if (localIndex >= array.length()) {
return null;
}
T localValue = array.get(localIndex);
if (localValue == null) {
return null;
}
localIndex++;
R alive = unwrap(localValue);
if (alive == null) {
continue;
}
index = localIndex;
return alive;
}
}
public R next() {
R next = queuedNext;
if (next == null) {
next = queueNext();
if (next == null) {
throw new NoSuchElementException();
}
}
queuedNext = null;
return next;
}
public void remove() {
throw new UnsupportedOperationException();
}
};
}
}
/**
* An async list implementation that removes elements whenever a binding was disposed.
*/
private static final class EventBindingList extends AbstractAsyncCollection, EventBinding>> {
EventBindingList(int initialCapacity) {
super(initialCapacity);
}
@Override
protected EventBinding> wrap(EventBinding> element) {
return element;
}
@Override
protected EventBinding> unwrap(EventBinding> element) {
if (element.isDisposed()) {
return null;
}
return element;
}
}
/**
* An async list using weak references.
*/
private static final class WeakAsyncList extends AbstractAsyncCollection, T> {
WeakAsyncList(int initialCapacity) {
super(initialCapacity);
}
@Override
protected WeakReference wrap(T element) {
return new WeakReference<>(element);
}
@Override
protected T unwrap(WeakReference element) {
return element.get();
}
}
static final AccessorInstrumentHandler ACCESSOR = new AccessorInstrumentHandler();
static final class AccessorInstrumentHandler extends Accessor {
static Accessor.Nodes nodesAccess() {
return ACCESSOR.nodes();
}
static Accessor.LanguageSupport langAccess() {
return ACCESSOR.languageSupport();
}
static Accessor.EngineSupport engineAccess() {
return ACCESSOR.engineSupport();
}
@SuppressWarnings("rawtypes")
protected CallTarget parse(Class extends TruffleLanguage> languageClass, Source code, Node context, String... argumentNames) throws IOException {
final TruffleLanguage> truffleLanguage = engineSupport().findLanguageImpl(null, languageClass, code.getMimeType());
return langAccess().parse(truffleLanguage, code, context, argumentNames);
}
@Override
protected InstrumentSupport instrumentSupport() {
return new InstrumentImpl();
}
static final class InstrumentImpl extends InstrumentSupport {
@Override
public Object createInstrumentationHandler(Object vm, OutputStream out, OutputStream err, InputStream in) {
return new InstrumentationHandler(out, err, in);
}
@Override
public void addInstrument(Object instrumentationHandler, Object key, Class> instrumentClass) {
((InstrumentationHandler) instrumentationHandler).addInstrument(key, instrumentClass);
}
@Override
public void disposeInstrument(Object instrumentationHandler, Object key, boolean cleanupRequired) {
((InstrumentationHandler) instrumentationHandler).disposeInstrumenter(key, cleanupRequired);
}
@Override
public void collectEnvServices(Set collectTo, Object vm, TruffleLanguage> impl, TruffleLanguage.Env env) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(vm);
Instrumenter instrumenter = instrumentationHandler.forLanguage(env, impl);
collectTo.add(instrumenter);
}
@Override
public T getInstrumentationHandlerService(Object vm, Object key, Class type) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) vm;
return instrumentationHandler.lookup(key, type);
}
@Override
public void detachLanguageFromInstrumentation(Object vm, com.oracle.truffle.api.TruffleLanguage.Env env) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(vm);
instrumentationHandler.disposeInstrumenter(langAccess().findContext(env), false);
}
@Override
public void onFirstExecution(RootNode rootNode) {
Object instrumentationHandler = engineAccess().getInstrumentationHandler(null);
/*
* we want to still support cases where call targets are executed without an
* enclosing engine.
*/
if (instrumentationHandler != null) {
((InstrumentationHandler) instrumentationHandler).onFirstExecution(rootNode);
}
}
@Override
public void onLoad(RootNode rootNode) {
Object instrumentationHandler = engineAccess().getInstrumentationHandler(null);
/*
* we want to still support cases where call targets are executed without an
* enclosing engine.
*/
if (instrumentationHandler != null) {
((InstrumentationHandler) instrumentationHandler).onLoad(rootNode);
}
}
}
}
}