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.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.Objects;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.concurrent.locks.Lock;
import org.graalvm.options.OptionDescriptor;
import org.graalvm.options.OptionDescriptors;
import org.graalvm.options.OptionValues;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Scope;
import com.oracle.truffle.api.TruffleContext;
import com.oracle.truffle.api.TruffleLanguage;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.impl.Accessor;
import com.oracle.truffle.api.impl.DispatchOutputStream;
import com.oracle.truffle.api.instrumentation.ProbeNode.EventChainNode;
import com.oracle.truffle.api.instrumentation.TruffleInstrument.Env;
import com.oracle.truffle.api.nodes.LanguageInfo;
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 org.graalvm.polyglot.Engine engine}.
*/
final class InstrumentationHandler {
/* Enable trace output to stdout. */
static final boolean TRACE = Boolean.getBoolean("truffle.instrumentation.trace");
private final Object sourceVM;
/*
* The contract is the following: "sources" and "sourcesList" can only be accessed while
* synchronized on "sources". both will only be lazily initialized from "loadedRoots" when the
* first sourceBindings is added, by calling lazyInitializeSourcesList(). "sourcesList" will be
* null as long as the sources haven't been initialized.
*/
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 AtomicReference> sourcesListRef = new AtomicReference<>();
private volatile boolean hasSourceBindings;
/*
* The contract is the following: "sourcesExecuted" and "sourcesExecutedList" can only be
* accessed while synchronized on "sourcesExecuted". Both will only be lazily initialized from
* "onFirstExecution" when the first sourceExecutedBindings is added, by calling
* lazyInitializeSourcesExecutedList(). "sourcesExecutedList" will be null as long as the
* sources haven't been executed.
*/
private final Map sourcesExecuted = Collections.synchronizedMap(new WeakHashMap());
/* Load order needs to be preserved for sources, thats why we store sources again in a list. */
private final AtomicReference> sourcesExecutedListRef = new AtomicReference<>();
private volatile boolean hasSourceExecutedBindings;
private final Collection loadedRoots = new WeakAsyncList<>(256);
private final Collection executedRoots = new WeakAsyncList<>(64);
private final Collection allocationReporters = new WeakAsyncList<>(16);
private final Collection> executionBindings = new EventBindingList<>(8);
private final Collection> sourceSectionBindings = new EventBindingList<>(8);
private final Collection> sourceBindings = new EventBindingList<>(8);
private final FindSourcesVisitor findSourcesVisitor = new FindSourcesVisitor(sources, sourcesListRef);
private final Collection> sourceExecutedBindings = new EventBindingList<>(8);
private final FindSourcesVisitor findSourcesExecutedVisitor = new FindSourcesVisitor(sourcesExecuted, sourcesExecutedListRef);
private final Collection> outputStdBindings = new EventBindingList<>(1);
private final Collection> outputErrBindings = new EventBindingList<>(1);
private final Collection> allocationBindings = new EventBindingList<>(2);
private final Collection> contextsBindings = new EventBindingList<>(8);
private final Collection> threadsBindings = new EventBindingList<>(8);
/*
* Fast lookup of instrumenter instances based on a key provided by the accessor.
*/
private final ConcurrentHashMap instrumenterMap = new ConcurrentHashMap<>();
private DispatchOutputStream out; // effectively final
private DispatchOutputStream err; // effectively final
private InputStream in; // effectively final
private final Map, Set>> cachedProvidedTags = new ConcurrentHashMap<>();
private final EngineInstrumenter engineInstrumenter;
private InstrumentationHandler(Object sourceVM, DispatchOutputStream out, DispatchOutputStream err, InputStream in) {
this.sourceVM = sourceVM;
this.out = out;
this.err = err;
this.in = in;
this.engineInstrumenter = new EngineInstrumenter();
}
Object getSourceVM() {
return sourceVM;
}
void onLoad(RootNode root) {
if (!AccessorInstrumentHandler.nodesAccess().isInstrumentable(root)) {
return;
}
assert root.getLanguageInfo() != null;
if (hasSourceBindings) {
final Source[] rootSources;
synchronized (sources) {
if (!sourceBindings.isEmpty()) {
// we'll add to the sourcesList, so it needs to be initialized
lazyInitializeSourcesList();
SourceSection sourceSection = root.getSourceSection();
if (sourceSection != null) {
findSourcesVisitor.adoptSource(sourceSection.getSource());
}
visitRoot(root, root, findSourcesVisitor, false);
rootSources = findSourcesVisitor.getSources();
} else {
hasSourceBindings = false;
sources.clear();
sourcesListRef.set(null);
rootSources = null;
}
}
loadedRoots.add(root);
// Do not invoke foreign code while holding a lock to avoid deadlocks.
if (rootSources != null) {
for (Source src : rootSources) {
notifySourceBindingsLoaded(sourceBindings, src);
}
}
} else {
loadedRoots.add(root);
}
// fast path no bindings attached
if (!sourceSectionBindings.isEmpty()) {
visitRoot(root, root, new NotifyLoadedListenerVisitor(sourceSectionBindings), false);
}
}
private static class FindSourcesVisitor extends AbstractNodeVisitor {
private final Map sources;
private final AtomicReference> sourcesListRef;
private final List rootSources = new ArrayList<>(5);
FindSourcesVisitor(Map sources, AtomicReference> sourcesListRef) {
this.sources = sources;
this.sourcesListRef = sourcesListRef;
}
@Override
boolean shouldVisit() {
return true;
}
@Override
protected void visitInstrumentable(Node parentInstrumentable, SourceSection parentSourceSection, Node instrumentableNode, SourceSection sourceSection) {
if (sourceSection != null) {
adoptSource(sourceSection.getSource());
}
}
void adoptSource(Source source) {
assert Thread.holdsLock(sources);
if (!sources.containsKey(source)) {
sources.put(source, null);
sourcesListRef.get().add(source);
rootSources.add(source);
}
}
Source[] getSources() {
if (rootSources.isEmpty()) {
return null;
}
Source[] sourcesArray = rootSources.toArray(new Source[rootSources.size()]);
rootSources.clear();
return sourcesArray;
}
}
void onFirstExecution(RootNode root) {
if (!AccessorInstrumentHandler.nodesAccess().isInstrumentable(root)) {
return;
}
assert root.getLanguageInfo() != null;
if (hasSourceExecutedBindings) {
final Source[] rootSources;
synchronized (sourcesExecuted) {
if (!sourceExecutedBindings.isEmpty()) {
// we'll add to the sourcesExecutedList, so it needs to be initialized
lazyInitializeSourcesExecutedList();
int rootBits = RootNodeBits.get(root);
if (RootNodeBits.isNoSourceSection(rootBits)) {
rootSources = null;
} else {
SourceSection sourceSection = root.getSourceSection();
if (RootNodeBits.isSameSource(rootBits) && sourceSection != null) {
Source source = sourceSection.getSource();
findSourcesExecutedVisitor.adoptSource(source);
rootSources = new Source[]{source};
} else {
if (sourceSection != null) {
findSourcesExecutedVisitor.adoptSource(sourceSection.getSource());
}
visitRoot(root, root, findSourcesExecutedVisitor, false);
rootSources = findSourcesExecutedVisitor.getSources();
}
}
} else {
hasSourceExecutedBindings = false;
sourcesExecuted.clear();
sourcesExecutedListRef.set(null);
rootSources = null;
}
}
executedRoots.add(root);
// Do not invoke foreign code while holding a lock to avoid deadlocks.
if (rootSources != null) {
for (Source src : rootSources) {
notifySourceExecutedBindings(sourceExecutedBindings, src);
}
}
} else {
executedRoots.add(root);
}
// fast path no bindings attached
if (!executionBindings.isEmpty()) {
visitRoot(root, root, new InsertWrappersVisitor(executionBindings), false);
}
}
void initializeInstrument(Object vmObject, Class> instrumentClass) {
Env env = new Env(vmObject, out, err, in);
env.instrumenter = new InstrumentClientInstrumenter(env, instrumentClass);
if (TRACE) {
trace("Initialize instrument class %s %n", instrumentClass);
}
try {
env.instrumenter.instrument = (TruffleInstrument) instrumentClass.getDeclaredConstructor().newInstance();
} catch (Exception e) {
failInstrumentInitialization(env, String.format("Failed to create new instrumenter class %s", instrumentClass.getName()), e);
return;
}
if (TRACE) {
trace("Initialized instrument %s class %s %n", env.instrumenter.instrument, instrumentClass);
}
addInstrumenter(vmObject, env.instrumenter);
}
void createInstrument(Object vmObject, String[] expectedServices, OptionValues optionValues) {
InstrumentClientInstrumenter instrumenter = ((InstrumentClientInstrumenter) instrumenterMap.get(vmObject));
instrumenter.env.options = optionValues;
instrumenter.create(expectedServices);
}
void finalizeInstrumenter(Object key) {
AbstractInstrumenter finalisingInstrumenter = instrumenterMap.get(key);
if (finalisingInstrumenter == null) {
throw new AssertionError("Instrumenter already disposed.");
}
finalisingInstrumenter.doFinalize();
}
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));
disposeOutputBindingsBulk(out, outputStdBindings);
disposeOutputBindingsBulk(err, outputErrBindings);
}
if (TRACE) {
trace("END: Disposed instrumenter %n", key);
}
}
private static void disposeBindingsBulk(Collection> list) {
for (EventBinding> binding : list) {
binding.disposeBulk();
}
}
private static void disposeOutputBindingsBulk(DispatchOutputStream dos, Collection> list) {
for (EventBinding extends OutputStream> binding : list) {
AccessorInstrumentHandler.engineAccess().detachOutputConsumer(dos, binding.getElement());
binding.disposeBulk();
}
}
Instrumenter forLanguage(TruffleLanguage> language) {
return new LanguageClientInstrumenter<>(language);
}
EventBinding addExecutionBinding(EventBinding.Source 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.Source 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;
}
private void visitLoadedSourceSections(EventBinding.Source> binding) {
if (TRACE) {
trace("BEGIN: Visiting loaded source sections %s, %s%n", binding.getFilter(), binding.getElement());
}
if (!loadedRoots.isEmpty()) {
visitRoots(loadedRoots, new NotifyLoadedWithBindingVisitor(binding));
}
if (TRACE) {
trace("END: Visited loaded source sections %s, %s%n", binding.getFilter(), binding.getElement());
}
}
EventBinding addSourceBinding(EventBinding.Source binding, boolean notifyLoaded) {
if (TRACE) {
trace("BEGIN: Adding source binding %s, %s%n", binding.getFilter(), binding.getElement());
}
this.sourceBindings.add(binding);
this.hasSourceBindings = true;
if (notifyLoaded) {
synchronized (sources) {
lazyInitializeSourcesList();
}
for (Source source : sourcesListRef.get()) {
notifySourceBindingLoaded(binding, source);
}
}
if (TRACE) {
trace("END: Added source binding %s, %s%n", binding.getFilter(), binding.getElement());
}
return binding;
}
EventBinding addSourceExecutionBinding(EventBinding.Source binding, boolean notifyLoaded) {
if (TRACE) {
trace("BEGIN: Adding source execution binding %s, %s%n", binding.getFilter(), binding.getElement());
}
this.sourceExecutedBindings.add(binding);
this.hasSourceExecutedBindings = true;
if (notifyLoaded) {
synchronized (sourcesExecuted) {
lazyInitializeSourcesExecutedList();
}
for (Source source : sourcesExecutedListRef.get()) {
notifySourceExecutedBinding(binding, source);
}
}
if (TRACE) {
trace("END: Added source execution binding %s, %s%n", binding.getFilter(), binding.getElement());
}
return binding;
}
EventBinding addOutputBinding(EventBinding binding, boolean errorOutput) {
if (TRACE) {
String kind = (errorOutput) ? "error" : "standard";
trace("BEGIN: Adding " + kind + " output binding %s%n", binding.getElement());
}
if (errorOutput) {
this.outputErrBindings.add(binding);
AccessorInstrumentHandler.engineAccess().attachOutputConsumer(this.err, binding.getElement());
} else {
this.outputStdBindings.add(binding);
AccessorInstrumentHandler.engineAccess().attachOutputConsumer(this.out, binding.getElement());
}
if (TRACE) {
String kind = (errorOutput) ? "error" : "standard";
trace("END: Added " + kind + " output binding %s%n", binding.getElement());
}
return binding;
}
private EventBinding addAllocationBinding(EventBinding.Allocation binding) {
if (TRACE) {
trace("BEGIN: Adding allocation binding %s%n", binding.getElement());
}
this.allocationBindings.add(binding);
for (AllocationReporter allocationReporter : allocationReporters) {
if (binding.getAllocationFilter().contains(allocationReporter.language)) {
allocationReporter.addListener(binding.getElement());
}
}
if (TRACE) {
trace("END: Added allocation binding %s%n", binding.getElement());
}
return binding;
}
private EventBinding addContextsBinding(EventBinding binding, boolean includeActiveContexts) {
if (TRACE) {
trace("BEGIN: Adding contexts binding %s%n", binding.getElement());
}
contextsBindings.add(binding);
if (includeActiveContexts) {
Accessor.EngineSupport engineAccess = InstrumentationHandler.AccessorInstrumentHandler.engineAccess();
engineAccess.reportAllLanguageContexts(sourceVM, binding.getElement());
}
if (TRACE) {
trace("END: Added contexts binding %s%n", binding.getElement());
}
return binding;
}
private EventBinding addThreadsBinding(EventBinding binding, boolean includeStartedThreads) {
if (TRACE) {
trace("BEGIN: Adding threads binding %s%n", binding.getElement());
}
threadsBindings.add(binding);
if (includeStartedThreads) {
Accessor.EngineSupport engineAccess = InstrumentationHandler.AccessorInstrumentHandler.engineAccess();
engineAccess.reportAllContextThreads(sourceVM, binding.getElement());
}
if (TRACE) {
trace("END: Added threads binding %s%n", binding.getElement());
}
return binding;
}
/**
* Initializes sources and sourcesList by populating them from loadedRoots.
*/
private void lazyInitializeSourcesList() {
assert Thread.holdsLock(sources);
if (sourcesListRef.get() == null) {
// build the sourcesList, we need it now
Collection sourcesList = new WeakAsyncList<>(16);
sourcesListRef.set(sourcesList);
for (RootNode root : loadedRoots) {
int rootBits = RootNodeBits.get(root);
if (RootNodeBits.isNoSourceSection(rootBits)) {
continue;
} else {
SourceSection sourceSection = root.getSourceSection();
if (RootNodeBits.isSameSource(rootBits) && sourceSection != null) {
Source source = sourceSection.getSource();
if (!sources.containsKey(source)) {
sources.put(source, null);
sourcesList.add(source);
}
} else {
if (sourceSection != null) {
findSourcesVisitor.adoptSource(sourceSection.getSource());
}
visitRoot(root, root, findSourcesVisitor, false);
for (Source source : findSourcesVisitor.rootSources) {
if (!sources.containsKey(source)) {
sources.put(source, null);
sourcesList.add(source);
}
}
findSourcesVisitor.rootSources.clear();
}
}
}
}
}
/**
* Initializes sourcesExecuted and sourcesExecutedList by populating them from executedRoots.
*/
private void lazyInitializeSourcesExecutedList() {
assert Thread.holdsLock(sourcesExecuted);
if (sourcesExecutedListRef.get() == null) {
// build the sourcesExecutedList, we need it now
Collection sourcesExecutedList = new WeakAsyncList<>(16);
sourcesExecutedListRef.set(sourcesExecutedList);
for (RootNode root : executedRoots) {
int rootBits = RootNodeBits.get(root);
if (RootNodeBits.isNoSourceSection(rootBits)) {
continue;
} else {
SourceSection sourceSection = root.getSourceSection();
if (RootNodeBits.isSameSource(rootBits) && sourceSection != null) {
Source source = sourceSection.getSource();
if (!sourcesExecuted.containsKey(source)) {
sourcesExecuted.put(source, null);
sourcesExecutedList.add(source);
}
} else {
if (sourceSection != null) {
findSourcesExecutedVisitor.adoptSource(sourceSection.getSource());
}
visitRoot(root, root, findSourcesExecutedVisitor, false);
for (Source source : findSourcesExecutedVisitor.rootSources) {
if (!sourcesExecuted.containsKey(source)) {
sourcesExecuted.put(source, null);
sourcesExecutedList.add(source);
}
}
findSourcesExecutedVisitor.rootSources.clear();
}
}
}
}
}
private void visitRoots(Collection roots, AbstractNodeVisitor addBindingsVisitor) {
for (RootNode root : roots) {
visitRoot(root, root, addBindingsVisitor, false);
}
}
@SuppressWarnings("deprecation")
void disposeBinding(EventBinding> binding) {
if (TRACE) {
trace("BEGIN: Dispose binding %s, %s%n", binding.getFilter(), binding.getElement());
}
if (binding instanceof EventBinding.Source) {
EventBinding.Source> sourceBinding = (EventBinding.Source>) binding;
if (sourceBinding.isExecutionEvent()) {
visitRoots(executedRoots, new DisposeWrappersVisitor(sourceBinding));
}
} else if (binding instanceof EventBinding.Allocation) {
EventBinding.Allocation> allocationBinding = (EventBinding.Allocation>) binding;
AllocationListener l = (AllocationListener) binding.getElement();
for (AllocationReporter allocationReporter : allocationReporters) {
if (allocationBinding.getAllocationFilter().contains(allocationReporter.language)) {
allocationReporter.removeListener(l);
}
}
} else {
Object elm = binding.getElement();
if (elm instanceof OutputStream) {
if (outputErrBindings.contains(binding)) {
AccessorInstrumentHandler.engineAccess().detachOutputConsumer(err, (OutputStream) elm);
} else if (outputStdBindings.contains(binding)) {
AccessorInstrumentHandler.engineAccess().detachOutputConsumer(out, (OutputStream) elm);
}
} else if (elm instanceof ContextsListener) {
// binding disposed
} else if (elm instanceof ThreadsListener) {
// binding disposed
} else {
assert false : "Unexpected binding " + binding + " with element " + elm;
}
}
if (TRACE) {
trace("END: Disposed binding %s, %s%n", binding.getFilter(), binding.getElement());
}
}
EventChainNode createBindings(VirtualFrame frame, ProbeNode probeNodeImpl) {
EventContext context = probeNodeImpl.getContext();
SourceSection sourceSection = context.getInstrumentedSourceSection();
if (TRACE) {
trace("BEGIN: Lazy update for %s%n", sourceSection);
}
RootNode rootNode;
Node parentInstrumentable = null;
SourceSection parentInstrumentableSourceSection = null;
Node parentNode = probeNodeImpl.getParent();
while (parentNode != null && parentNode.getParent() != null) {
if (parentInstrumentable == null) {
SourceSection parentSourceSection = parentNode.getSourceSection();
if (isInstrumentableNode(parentNode, parentSourceSection)) {
parentInstrumentable = parentNode;
parentInstrumentableSourceSection = parentSourceSection;
}
}
parentNode = parentNode.getParent();
}
if (parentNode instanceof RootNode) {
rootNode = (RootNode) parentNode;
} else {
throw new AssertionError();
}
Node instrumentedNode = probeNodeImpl.getContext().getInstrumentedNode();
Set> providedTags = getProvidedTags(rootNode);
EventChainNode root = null;
EventChainNode parent = null;
for (EventBinding.Source> binding : executionBindings) {
if (binding.isChildInstrumentedFull(providedTags, rootNode, parentInstrumentable, parentInstrumentableSourceSection, instrumentedNode, sourceSection)) {
if (TRACE) {
trace(" Found input value binding %s, %s%n", binding.getInputFilter(), System.identityHashCode(binding));
}
EventChainNode next = probeNodeImpl.createParentEventChainCallback(frame, binding, rootNode, providedTags);
if (next == null) {
// inconsistent AST
continue;
}
if (root == null) {
root = next;
} else {
assert parent != null;
parent.setNext(next);
}
parent = next;
}
if (binding.isInstrumentedFull(providedTags, rootNode, instrumentedNode, sourceSection)) {
if (TRACE) {
trace(" Found binding %s, %s%n", binding.getFilter(), binding.getElement());
}
EventChainNode next = probeNodeImpl.createEventChainCallback(frame, binding, rootNode, providedTags, instrumentedNode, sourceSection);
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;
}
public void onNodeInserted(RootNode rootNode, Node tree) {
// for input filters to be updated correctly we need to
// start traversing with the parent instrumentable node.
Node parentInstrumentable = tree;
while (parentInstrumentable != null && parentInstrumentable.getParent() != null) {
parentInstrumentable = parentInstrumentable.getParent();
if (InstrumentationHandler.isInstrumentableNode(parentInstrumentable, parentInstrumentable.getSourceSection())) {
break;
}
}
assert parentInstrumentable != null;
if (!sourceSectionBindings.isEmpty()) {
visitRoot(rootNode, parentInstrumentable, new NotifyLoadedListenerVisitor(sourceSectionBindings), true);
}
if (!executionBindings.isEmpty()) {
visitRoot(rootNode, parentInstrumentable, new InsertWrappersVisitor(executionBindings), true);
}
}
private static void notifySourceBindingsLoaded(Collection> bindings, Source source) {
for (EventBinding.Source> binding : bindings) {
notifySourceBindingLoaded(binding, source);
}
}
private static void notifySourceBindingLoaded(EventBinding.Source> 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);
}
}
}
}
private static void notifySourceExecutedBindings(Collection> bindings, Source source) {
for (EventBinding.Source> binding : bindings) {
notifySourceExecutedBinding(binding, source);
}
}
private static void notifySourceExecutedBinding(EventBinding.Source> binding, Source source) {
if (!binding.isDisposed() && binding.isInstrumentedSource(source)) {
try {
((ExecuteSourceListener) binding.getElement()).onExecute(new ExecuteSourceEvent(source));
} catch (Throwable t) {
if (binding.isLanguageBinding()) {
throw t;
} else {
ProbeNode.exceptionEventForClientInstrument(binding, "onExecute", t);
}
}
}
}
static void notifySourceSectionLoaded(EventBinding.Source> binding, Node node, SourceSection section) {
if (section == null) {
// Do not report null source sections to keep compatibility with the past behavior.
return;
}
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.");
}
}
private static Collection> filterBindingsForInstrumenter(Collection> bindings, AbstractInstrumenter instrumenter) {
if (bindings.isEmpty()) {
return Collections.emptyList();
}
Collection> newBindings = new ArrayList<>();
for (EventBinding.Source> binding : bindings) {
if (binding.getInstrumenter() == instrumenter) {
newBindings.add(binding);
}
}
return newBindings;
}
private void insertWrapper(Node instrumentableNode, SourceSection sourceSection) {
Lock lock = AccessorInstrumentHandler.nodesAccess().getLock(instrumentableNode);
try {
lock.lock();
insertWrapperImpl(instrumentableNode, sourceSection);
} finally {
lock.unlock();
}
}
@SuppressWarnings({"unchecked", "deprecation"})
private void insertWrapperImpl(Node node, SourceSection sourceSection) {
Node parent = node.getParent();
if (parent instanceof com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode) {
// already wrapped, need to invalidate the wrapper something changed
invalidateWrapperImpl((com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode) parent, node);
return;
}
ProbeNode probe = new ProbeNode(InstrumentationHandler.this, sourceSection);
com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode wrapper;
try {
if (node instanceof InstrumentableNode) {
wrapper = ((InstrumentableNode) node).createWrapper(probe);
if (wrapper == null) {
throw new IllegalStateException("No wrapper returned for " + node + " of class " + node.getClass().getName());
}
} else {
Class> factory = null;
Class> currentClass = node.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.getDeclaredConstructor().newInstance()).createWrapper(node, probe);
}
} catch (Exception e) {
throw new IllegalStateException("Failed to create wrapper of " + node, e);
}
if (!(wrapper instanceof Node)) {
throw new IllegalStateException(String.format("Implementation of %s must be a subclass of %s.",
wrapper.getClass().getName(), Node.class.getSimpleName()));
}
final Node wrapperNode = (Node) wrapper;
if (wrapperNode.getParent() != null) {
throw new IllegalStateException(String.format("Instance of provided wrapper %s is already adopted by another parent: %s",
wrapper.getClass().getName(), wrapperNode.getParent().getClass().getName()));
}
if (parent == null) {
throw new IllegalStateException(String.format("Instance of instrumentable node %s is not adopted by a parent.", node.getClass().getName()));
}
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.");
assert probe.getContext().validEventContext();
}
private EventBinding attachFactory(AbstractInstrumenter instrumenter, SourceSectionFilter filter, SourceSectionFilter inputFilter, T factory) {
return addExecutionBinding(new EventBinding.Source<>(instrumenter, filter, inputFilter, factory, true));
}
private EventBinding attachListener(AbstractInstrumenter instrumenter, SourceSectionFilter filter, SourceSectionFilter inputFilter, T listener) {
return addExecutionBinding(new EventBinding.Source<>(instrumenter, filter, inputFilter, listener, true));
}
private EventBinding attachSourceListener(AbstractInstrumenter abstractInstrumenter, SourceSectionFilter filter, T listener, boolean notifyLoaded) {
return addSourceBinding(new EventBinding.Source<>(abstractInstrumenter, filter, null, listener, false), notifyLoaded);
}
private EventBinding attachSourceSectionListener(AbstractInstrumenter abstractInstrumenter, SourceSectionFilter filter, T listener, boolean notifyLoaded) {
return addSourceSectionBinding(new EventBinding.Source<>(abstractInstrumenter, filter, null, listener, false), notifyLoaded);
}
private void visitLoadedSourceSections(AbstractInstrumenter abstractInstrumenter, SourceSectionFilter filter, LoadSourceSectionListener listener) {
visitLoadedSourceSections(new EventBinding.Source<>(abstractInstrumenter, filter, null, listener, false));
}
private EventBinding attachExecuteSourceListener(AbstractInstrumenter abstractInstrumenter, SourceSectionFilter filter, T listener, boolean notifyLoaded) {
return addSourceExecutionBinding(new EventBinding.Source<>(abstractInstrumenter, filter, null, listener, false), notifyLoaded);
}
private EventBinding attachOutputConsumer(AbstractInstrumenter instrumenter, T stream, boolean errorOutput) {
return addOutputBinding(new EventBinding<>(instrumenter, stream), errorOutput);
}
private EventBinding attachAllocationListener(AbstractInstrumenter instrumenter, AllocationEventFilter filter, T listener) {
return addAllocationBinding(new EventBinding.Allocation<>(instrumenter, filter, listener));
}
private EventBinding attachContextsListener(AbstractInstrumenter instrumenter, T listener, boolean includeActiveContexts) {
assert listener != null;
return addContextsBinding(new EventBinding<>(instrumenter, listener), includeActiveContexts);
}
private EventBinding attachThreadsListener(AbstractInstrumenter instrumenter, T listener, boolean includeStartedThreads) {
assert listener != null;
return addThreadsBinding(new EventBinding<>(instrumenter, listener), includeStartedThreads);
}
private void notifyContextCreated(TruffleContext context) {
for (EventBinding extends ContextsListener> binding : contextsBindings) {
binding.getElement().onContextCreated(context);
}
}
private void notifyContextClosed(TruffleContext context) {
for (EventBinding extends ContextsListener> binding : contextsBindings) {
binding.getElement().onContextClosed(context);
}
}
private void notifyLanguageContextCreated(TruffleContext context, LanguageInfo language) {
for (EventBinding extends ContextsListener> binding : contextsBindings) {
binding.getElement().onLanguageContextCreated(context, language);
}
}
private void notifyLanguageContextInitialized(TruffleContext context, LanguageInfo language) {
for (EventBinding extends ContextsListener> binding : contextsBindings) {
binding.getElement().onLanguageContextInitialized(context, language);
}
}
private void notifyLanguageContextFinalized(TruffleContext context, LanguageInfo language) {
for (EventBinding extends ContextsListener> binding : contextsBindings) {
binding.getElement().onLanguageContextFinalized(context, language);
}
}
private void notifyLanguageContextDisposed(TruffleContext context, LanguageInfo language) {
for (EventBinding extends ContextsListener> binding : contextsBindings) {
binding.getElement().onLanguageContextDisposed(context, language);
}
}
private void notifyThreadStarted(TruffleContext context, Thread thread) {
for (EventBinding extends ThreadsListener> binding : threadsBindings) {
binding.getElement().onThreadInitialized(context, thread);
}
}
private void notifyThreadFinished(TruffleContext context, Thread thread) {
for (EventBinding extends ThreadsListener> binding : threadsBindings) {
binding.getElement().onThreadDisposed(context, thread);
}
}
Set> getProvidedTags(TruffleLanguage> lang) {
if (lang == null) {
return Collections.emptySet();
}
Class> languageClass = lang.getClass();
Set> tags = cachedProvidedTags.get(languageClass);
if (tags == null) {
ProvidedTags languageTags = languageClass.getAnnotation(ProvidedTags.class);
List> languageTagsList = languageTags != null ? Arrays.asList(languageTags.value()) : Collections.> emptyList();
tags = Collections.unmodifiableSet(new HashSet<>(languageTagsList));
cachedProvidedTags.put(languageClass, tags);
}
return tags;
}
Set> getProvidedTags(Node root) {
return getProvidedTags(AccessorInstrumentHandler.nodesAccess().getLanguage(root.getRootNode()));
}
@SuppressWarnings("deprecation")
static boolean isInstrumentableNode(Node node, SourceSection sourceSection) {
if (node instanceof com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode) {
return false;
}
if (node instanceof InstrumentableNode) {
return ((InstrumentableNode) node).isInstrumentable();
} else {
return !(node instanceof RootNode) && sourceSection != null;
}
}
static void trace(String message, Object... args) {
PrintStream out = System.out;
out.printf(message, args);
}
private void visitRoot(RootNode root, final Node node, final AbstractNodeVisitor visitor, boolean forceRootBitComputation) {
if (TRACE) {
trace("BEGIN: Visit root %s for %s%n", root.toString(), visitor);
}
visitor.root = root;
visitor.providedTags = getProvidedTags(root);
visitor.rootSourceSection = root.getSourceSection();
visitor.rootBits = RootNodeBits.get(visitor.root);
if (visitor.shouldVisit() || forceRootBitComputation) {
if (forceRootBitComputation) {
visitor.computingRootNodeBits = RootNodeBits.isUninitialized(visitor.rootBits) ? RootNodeBits.getAll() : visitor.rootBits;
} else if (RootNodeBits.isUninitialized(visitor.rootBits)) {
visitor.computingRootNodeBits = RootNodeBits.getAll();
}
if (TRACE) {
trace("BEGIN: Traverse root %s for %s%n", root.toString(), visitor);
}
visitor.visit(node);
if (TRACE) {
trace("END: Traverse root %s for %s%n", root.toString(), visitor);
}
if (!RootNodeBits.isUninitialized(visitor.computingRootNodeBits)) {
RootNodeBits.set(visitor.root, visitor.computingRootNodeBits);
}
}
if (TRACE) {
trace("END: Visited root %s for %s%n", root.toString(), visitor);
}
}
@SuppressWarnings("deprecation")
static void removeWrapper(ProbeNode node) {
if (TRACE) {
trace("Remove wrapper for %s%n", node.getContext().getInstrumentedSourceSection());
}
com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode wrapperNode = node.findWrapper();
((Node) wrapperNode).replace(wrapperNode.getDelegateNode());
}
@SuppressWarnings("deprecation")
private static void invalidateWrapper(Node node) {
Node parent = node.getParent();
if (!(parent instanceof com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode)) {
// not yet wrapped
return;
}
invalidateWrapperImpl((com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode) parent, node);
}
@SuppressWarnings("deprecation")
private static void invalidateWrapperImpl(com.oracle.truffle.api.instrumentation.InstrumentableFactory.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();
}
}
@SuppressWarnings("unchecked")
static boolean hasTagImpl(Set> providedTags, Node node, Class> tag) {
if (providedTags.contains(tag)) {
if (node instanceof InstrumentableNode) {
return ((InstrumentableNode) node).hasTag((Class extends Tag>) tag);
} else {
return AccessorInstrumentHandler.nodesAccess().isTaggedWith(node, tag);
}
}
return false;
}
private T lookup(Object key, Class type) {
AbstractInstrumenter value = instrumenterMap.get(key);
return value == null ? null : value.lookup(this, type);
}
private AllocationReporter getAllocationReporter(LanguageInfo info) {
AllocationReporter allocationReporter = new AllocationReporter(info);
allocationReporters.add(allocationReporter);
for (EventBinding.Allocation extends AllocationListener> binding : allocationBindings) {
if (binding.getAllocationFilter().contains(info)) {
allocationReporter.addListener(binding.getElement());
}
}
return allocationReporter;
}
private void patch(DispatchOutputStream newOut, DispatchOutputStream newErr, InputStream newIn) {
this.out = newOut;
this.err = newErr;
this.in = newIn;
}
static void failInstrumentInitialization(Env env, String message, Throwable t) {
Exception exception = new Exception(message, t);
PrintStream stream = new PrintStream(env.err());
exception.printStackTrace(stream);
}
private abstract static class AbstractNodeVisitor implements NodeVisitor {
RootNode root;
SourceSection rootSourceSection;
Set> providedTags;
Set> materializeLimitedTags;
/* cached root bits read from the root node. value is reliable. */
int rootBits;
/* temporary field for currently computing root bits. value is not reliable. */
int computingRootNodeBits;
abstract boolean shouldVisit();
private void computeRootBits(SourceSection sourceSection) {
int bits = computingRootNodeBits;
if (RootNodeBits.isUninitialized(bits)) {
return;
}
if (sourceSection != null) {
if (RootNodeBits.isNoSourceSection(bits)) {
bits = RootNodeBits.setHasSourceSection(bits);
}
if (rootSourceSection != null) {
if (RootNodeBits.isSourceSectionsHierachical(bits)) {
if (sourceSection.getCharIndex() < rootSourceSection.getCharIndex() //
|| sourceSection.getCharEndIndex() > rootSourceSection.getCharEndIndex()) {
bits = RootNodeBits.setSourceSectionsUnstructured(bits);
}
}
if (RootNodeBits.isSameSource(bits) && rootSourceSection.getSource() != sourceSection.getSource()) {
bits = RootNodeBits.setHasDifferentSource(bits);
}
} else {
bits = RootNodeBits.setSourceSectionsUnstructured(bits);
bits = RootNodeBits.setHasDifferentSource(bits);
}
}
computingRootNodeBits = bits;
}
private Node savedParent;
private SourceSection savedParentSourceSection;
public final boolean visit(Node originalNode) {
Node node = originalNode;
SourceSection sourceSection = node.getSourceSection();
boolean instrumentable = InstrumentationHandler.isInstrumentableNode(node, sourceSection);
Node previousParent = null;
SourceSection previousParentSourceSection = null;
if (instrumentable) {
computeRootBits(sourceSection);
node = materializeSyntaxNodes(node, sourceSection);
visitInstrumentable(this.savedParent, this.savedParentSourceSection, node, sourceSection);
previousParent = this.savedParent;
previousParentSourceSection = this.savedParentSourceSection;
this.savedParent = node;
this.savedParentSourceSection = sourceSection;
}
try {
NodeUtil.forEachChild(node, this);
} finally {
if (instrumentable) {
this.savedParent = previousParent;
this.savedParentSourceSection = previousParentSourceSection;
}
}
return true;
}
@SuppressWarnings("unchecked")
private Node materializeSyntaxNodes(Node instrumentableNode, SourceSection sourceSection) {
if (instrumentableNode instanceof InstrumentableNode) {
InstrumentableNode currentNode = (InstrumentableNode) instrumentableNode;
assert currentNode.isInstrumentable();
Set> materializeTags = (Set>) (materializeLimitedTags == null ? providedTags : materializeLimitedTags);
InstrumentableNode materializedNode = currentNode.materializeInstrumentableNodes(materializeTags);
if (currentNode != materializedNode) {
if (!(materializedNode instanceof Node)) {
throw new IllegalStateException("The returned materialized syntax node is not a Truffle Node.");
}
if (((Node) materializedNode).getParent() != null) {
throw new IllegalStateException("The returned materialized syntax node is already adopted.");
}
SourceSection newSourceSection = ((Node) materializedNode).getSourceSection();
if (!Objects.equals(sourceSection, newSourceSection)) {
throw new IllegalStateException(String.format("The source section of the materialized syntax node must match the source section of the original node. %s != %s.", sourceSection,
newSourceSection));
}
return ((Node) currentNode).replace((Node) materializedNode);
}
}
return instrumentableNode;
}
protected abstract void visitInstrumentable(Node parentInstrumentable, SourceSection parentSourceSection, Node instrumentableNode, SourceSection sourceSection);
}
private abstract class AbstractBindingVisitor extends AbstractNodeVisitor {
protected final EventBinding.Source> binding;
AbstractBindingVisitor(EventBinding.Source> binding) {
this.binding = binding;
Set> limitedTags = binding.getLimitedTags();
this.materializeLimitedTags = limitedTags != null ? Collections.unmodifiableSet(limitedTags) : null;
}
@Override
boolean shouldVisit() {
RootNode localRoot = root;
SourceSection localRootSourceSection = rootSourceSection;
int localRootBits = rootBits;
return binding.isInstrumentedRoot(providedTags, localRoot, localRootSourceSection, localRootBits);
}
@Override
protected final void visitInstrumentable(Node parentInstrumentable, SourceSection parentSourceSection, Node instrumentableNode, SourceSection sourceSection) {
if (binding.isInstrumentedLeaf(providedTags, instrumentableNode, sourceSection) ||
binding.isChildInstrumentedLeaf(providedTags, root, parentInstrumentable, parentSourceSection, instrumentableNode, sourceSection)) {
if (TRACE) {
traceFilterCheck("hit", providedTags, binding, instrumentableNode, sourceSection);
}
visitInstrumented(instrumentableNode, sourceSection);
} else {
if (TRACE) {
traceFilterCheck("miss", providedTags, binding, instrumentableNode, sourceSection);
}
}
}
protected abstract void visitInstrumented(Node node, SourceSection section);
}
@SuppressWarnings("deprecation")
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;
Set> compoundTags = null; // null means all provided tags by the language
for (EventBinding.Source> sourceBinding : bindings) {
Set> limitedTags = sourceBinding.getLimitedTags();
if (limitedTags == null) {
compoundTags = null;
break;
} else {
if (compoundTags == null) {
compoundTags = new HashSet<>();
}
compoundTags.addAll(limitedTags);
}
}
this.materializeLimitedTags = compoundTags != null ? Collections.unmodifiableSet(compoundTags) : null;
}
@Override
boolean shouldVisit() {
if (bindings.isEmpty()) {
return false;
}
RootNode localRoot = root;
SourceSection localRootSourceSection = rootSourceSection;
int localRootBits = rootBits;
for (EventBinding.Source> binding : bindings) {
if (binding.isInstrumentedRoot(providedTags, localRoot, localRootSourceSection, localRootBits)) {
return true;
}
}
return false;
}
@Override
protected final void visitInstrumentable(Node parentInstrumentable, SourceSection parentSourceSection, Node instrumentableNode, SourceSection sourceSection) {
// no locking required for these atomic reference arrays
for (EventBinding.Source> binding : bindings) {
if (binding.isInstrumentedFull(providedTags, root, instrumentableNode, sourceSection) ||
binding.isChildInstrumentedFull(providedTags, root, parentInstrumentable, parentSourceSection, instrumentableNode, sourceSection)) {
if (TRACE) {
traceFilterCheck("hit", providedTags, binding, instrumentableNode, sourceSection);
}
visitInstrumented(binding, instrumentableNode, sourceSection);
if (!visitForEachBinding) {
break;
}
} else {
if (TRACE) {
traceFilterCheck("miss", providedTags, binding, instrumentableNode, sourceSection);
}
}
}
}
protected abstract void visitInstrumented(EventBinding.Source> binding, Node node, SourceSection section);
}
/* Insert wrappers for a single bindings. */
private final class InsertWrappersWithBindingVisitor extends AbstractBindingVisitor {
InsertWrappersWithBindingVisitor(EventBinding.Source> filter) {
super(filter);
}
@Override
protected void visitInstrumented(Node node, SourceSection section) {
insertWrapper(node, section);
}
}
private final class DisposeWrappersVisitor extends AbstractBindingVisitor {
DisposeWrappersVisitor(EventBinding.Source> 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.Source> 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.Source> binding, Node node, SourceSection section) {
invalidateWrapper(node);
}
}
private final class NotifyLoadedWithBindingVisitor extends AbstractBindingVisitor {
NotifyLoadedWithBindingVisitor(EventBinding.Source> 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.Source> 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(Env env, Class> instrumentClass) {
this.instrumentClass = instrumentClass;
this.env = env;
}
@Override
boolean isInstrumentableSource(Source source) {
return true;
}
@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;
}
void create(String[] expectedServices) {
if (TRACE) {
trace("Create instrument %s class %s %n", instrument, instrumentClass);
}
services = env.onCreate(instrument);
if (expectedServices != null && !TruffleOptions.AOT) {
checkServices(expectedServices);
}
if (TRACE) {
trace("Created instrument %s class %s %n", instrument, instrumentClass);
}
}
private boolean checkServices(String[] expectedServices) {
LOOP: for (String name : expectedServices) {
for (Object obj : services) {
if (findType(name, obj.getClass())) {
continue LOOP;
}
}
failInstrumentInitialization(env, String.format("%s declares service %s but doesn't register it", instrumentClass.getName(), name), null);
}
return true;
}
private boolean findType(String name, Class> type) {
if (type == null) {
return false;
}
if (type.getName().equals(name) || (type.getCanonicalName() != null && type.getCanonicalName().equals(name))) {
return true;
}
if (findType(name, type.getSuperclass())) {
return true;
}
for (Class> inter : type.getInterfaces()) {
if (findType(name, inter)) {
return true;
}
}
return false;
}
boolean isInitialized() {
return instrument != null;
}
TruffleInstrument getInstrument() {
return instrument;
}
@Override
public EventBinding attachContextsListener(T listener, boolean includeActiveContexts) {
return InstrumentationHandler.this.attachContextsListener(this, listener, includeActiveContexts);
}
@Override
public EventBinding attachThreadsListener(T listener, boolean includeStartedThreads) {
return InstrumentationHandler.this.attachThreadsListener(this, listener, includeStartedThreads);
}
@Override
void doFinalize() {
instrument.onFinalize(env);
}
@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 EngineInstrumenter extends AbstractInstrumenter {
@Override
void doFinalize() {
}
@Override
void dispose() {
}
@Override
T lookup(InstrumentationHandler handler, Class type) {
return null;
}
@Override
boolean isInstrumentableRoot(RootNode rootNode) {
return true;
}
@Override
boolean isInstrumentableSource(Source source) {
return true;
}
@Override
void verifyFilter(SourceSectionFilter filter) {
}
@Override
public Set> queryTags(Node node) {
return queryTagsImpl(node, null);
}
@Override
public EventBinding attachContextsListener(T listener, boolean includeActiveContexts) {
throw new UnsupportedOperationException("Not supported in engine instrumenter.");
}
@Override
public EventBinding attachThreadsListener(T listener, boolean includeStartedThreads) {
throw new UnsupportedOperationException("Not supported in engine instrumenter.");
}
}
/**
* Provider of instrumentation services for {@linkplain TruffleLanguage language
* implementations}.
*/
final class LanguageClientInstrumenter extends AbstractInstrumenter {
private final LanguageInfo languageInfo;
private final TruffleLanguage> language;
LanguageClientInstrumenter(TruffleLanguage> language) {
this.language = language;
this.languageInfo = AccessorInstrumentHandler.langAccess().getLanguageInfo(language);
}
@Override
boolean isInstrumentableSource(Source source) {
String mimeType = source.getMimeType();
if (mimeType == null) {
return false;
}
return languageInfo.getMimeTypes().contains(mimeType);
}
@Override
boolean isInstrumentableRoot(RootNode node) {
LanguageInfo langInfo = node.getLanguageInfo();
if (langInfo == null) {
return false;
}
if (langInfo != languageInfo) {
return false;
}
return true;
}
@Override
public Set> queryTags(Node node) {
return queryTagsImpl(node, languageInfo);
}
@Override
void verifyFilter(SourceSectionFilter filter) {
Set> providedTags = getProvidedTags(language);
// 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
public EventBinding attachContextsListener(S listener, boolean includeActiveContexts) {
throw new UnsupportedOperationException("Not supported in language instrumenter.");
}
@Override
public EventBinding attachThreadsListener(S listener, boolean includeStartedThreads) {
throw new UnsupportedOperationException("Not supported in language instrumenter.");
}
@Override
void doFinalize() {
// 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 doFinalize();
abstract void dispose();
abstract T lookup(InstrumentationHandler handler, Class type);
public void disposeBinding(EventBinding> binding) {
InstrumentationHandler.this.disposeBinding(binding);
}
abstract boolean isInstrumentableRoot(RootNode rootNode);
abstract boolean isInstrumentableSource(Source source);
final Set> queryTagsImpl(Node node, LanguageInfo onlyLanguage) {
SourceSection sourceSection = node.getSourceSection();
if (!InstrumentationHandler.isInstrumentableNode(node, sourceSection)) {
return Collections.emptySet();
}
RootNode root = node.getRootNode();
if (root == null) {
return Collections.emptySet();
}
if (onlyLanguage != null && root.getLanguageInfo() != 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 EventBinding attachExecutionEventFactory(SourceSectionFilter filter, SourceSectionFilter inputFilter, T factory) {
verifyFilter(filter);
return InstrumentationHandler.this.attachFactory(this, filter, inputFilter, factory);
}
@Override
public EventBinding attachExecutionEventListener(SourceSectionFilter filter, SourceSectionFilter inputFilter, T listener) {
verifyFilter(filter);
return InstrumentationHandler.this.attachListener(this, filter, inputFilter, listener);
}
@Override
@SuppressWarnings("deprecation")
public EventBinding attachLoadSourceListener(SourceSectionFilter filter, T listener, boolean includeExistingSources) {
verifySourceOnly(filter);
verifyFilter(filter);
return InstrumentationHandler.this.attachSourceListener(this, filter, listener, includeExistingSources);
}
@Override
public EventBinding attachLoadSourceListener(SourceFilter filter, T listener, boolean notifyLoaded) {
SourceSectionFilter sectionsFilter = SourceSectionFilter.newBuilder().sourceFilter(filter).build();
return attachLoadSourceListener(sectionsFilter, listener, notifyLoaded);
}
@Override
public EventBinding attachLoadSourceSectionListener(SourceSectionFilter filter, T listener, boolean notifyLoaded) {
verifyFilter(filter);
return InstrumentationHandler.this.attachSourceSectionListener(this, filter, listener, notifyLoaded);
}
@Override
public void visitLoadedSourceSections(SourceSectionFilter filter, LoadSourceSectionListener listener) {
verifyFilter(filter);
InstrumentationHandler.this.visitLoadedSourceSections(this, filter, listener);
}
@Override
public EventBinding attachExecuteSourceListener(SourceFilter filter, T listener, boolean notifyLoaded) {
SourceSectionFilter sectionsFilter = SourceSectionFilter.newBuilder().sourceFilter(filter).build();
return InstrumentationHandler.this.attachExecuteSourceListener(this, sectionsFilter, listener, notifyLoaded);
}
@Override
public EventBinding attachAllocationListener(AllocationEventFilter filter, T listener) {
return InstrumentationHandler.this.attachAllocationListener(this, filter, listener);
}
@Override
public EventBinding attachOutConsumer(T stream) {
return InstrumentationHandler.this.attachOutputConsumer(this, stream, false);
}
@Override
public EventBinding attachErrConsumer(T stream) {
return InstrumentationHandler.this.attachOutputConsumer(this, stream, true);
}
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 {
EventBindingList(int initialCapacity) {
super(initialCapacity);
}
@Override
protected EB wrap(EB element) {
return element;
}
@Override
protected EB unwrap(EB 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();
}
static Accessor.InteropSupport interopAccess() {
return ACCESSOR.interopSupport();
}
@Override
protected InstrumentSupport instrumentSupport() {
return new InstrumentImpl();
}
protected boolean isTruffleObject(Object value) {
return interopSupport().isTruffleObject(value);
}
static final class InstrumentImpl extends InstrumentSupport {
@Override
public Object createInstrumentationHandler(Object vm, DispatchOutputStream out, DispatchOutputStream err, InputStream in) {
return new InstrumentationHandler(vm, out, err, in);
}
@Override
public void initializeInstrument(Object instrumentationHandler, Object key, Class> instrumentClass) {
((InstrumentationHandler) instrumentationHandler).initializeInstrument(key, instrumentClass);
}
@Override
public void createInstrument(Object instrumentationHandler, Object key, String[] expectedServices, OptionValues options) {
((InstrumentationHandler) instrumentationHandler).createInstrument(key, expectedServices, options);
}
@Override
public Object getEngineInstrumenter(Object instrumentationHandler) {
return ((InstrumentationHandler) instrumentationHandler).engineInstrumenter;
}
@Override
public void onNodeInserted(RootNode rootNode, Node tree) {
InstrumentationHandler handler = getHandler(rootNode);
if (handler != null) {
handler.onNodeInserted(rootNode, tree);
}
}
@Override
public OptionDescriptors describeOptions(Object instrumentationHandler, Object key, String requiredGroup) {
InstrumentClientInstrumenter instrumenter = (InstrumentClientInstrumenter) ((InstrumentationHandler) instrumentationHandler).instrumenterMap.get(key);
OptionDescriptors descriptors = instrumenter.instrument.getOptionDescriptors();
if (descriptors == null) {
descriptors = OptionDescriptors.EMPTY;
}
String groupPlusDot = requiredGroup + ".";
for (OptionDescriptor descriptor : descriptors) {
if (!descriptor.getName().equals(requiredGroup) && !descriptor.getName().startsWith(groupPlusDot)) {
throw new IllegalArgumentException(String.format("Illegal option prefix in name '%s' specified for option described by instrument '%s'. " +
"The option prefix must match the id of the instrument '%s'.",
descriptor.getName(), instrumenter.instrument.getClass().getName(), requiredGroup));
}
}
return descriptors;
}
@Override
public void finalizeInstrument(Object instrumentationHandler, Object key) {
((InstrumentationHandler) instrumentationHandler).finalizeInstrumenter(key);
}
@Override
public void disposeInstrument(Object instrumentationHandler, Object key, boolean cleanupRequired) {
((InstrumentationHandler) instrumentationHandler).disposeInstrumenter(key, cleanupRequired);
}
@Override
public void collectEnvServices(Set collectTo, Object languageShared, TruffleLanguage> language) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(languageShared);
Instrumenter instrumenter = instrumentationHandler.forLanguage(language);
collectTo.add(instrumenter);
AllocationReporter allocationReporter = instrumentationHandler.getAllocationReporter(AccessorInstrumentHandler.langAccess().getLanguageInfo(language));
collectTo.add(allocationReporter);
}
@Override
public T getInstrumentationHandlerService(Object vm, Object key, Class type) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) vm;
return instrumentationHandler.lookup(key, type);
}
@Override
public void onFirstExecution(RootNode rootNode) {
InstrumentationHandler handler = getHandler(rootNode);
if (handler != null) {
handler.onFirstExecution(rootNode);
}
}
@Override
public void onLoad(RootNode rootNode) {
InstrumentationHandler handler = getHandler(rootNode);
if (handler != null) {
handler.onLoad(rootNode);
}
}
@Override
public Iterable findTopScopes(TruffleLanguage.Env env) {
return TruffleInstrument.Env.findTopScopes(env);
}
@Override
@CompilerDirectives.TruffleBoundary
public void notifyContextCreated(Object engine, TruffleContext context) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyContextCreated(context);
}
@Override
@CompilerDirectives.TruffleBoundary
public void notifyContextClosed(Object engine, TruffleContext context) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyContextClosed(context);
}
@Override
public void notifyLanguageContextCreated(Object engine, TruffleContext context, LanguageInfo info) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyLanguageContextCreated(context, info);
}
@Override
public void notifyLanguageContextInitialized(Object engine, TruffleContext context, LanguageInfo info) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyLanguageContextInitialized(context, info);
}
@Override
public void notifyLanguageContextFinalized(Object engine, TruffleContext context, LanguageInfo info) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyLanguageContextFinalized(context, info);
}
@Override
public void notifyLanguageContextDisposed(Object engine, TruffleContext context, LanguageInfo info) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyLanguageContextDisposed(context, info);
}
@Override
@CompilerDirectives.TruffleBoundary
public void notifyThreadStarted(Object engine, TruffleContext context, Thread thread) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyThreadStarted(context, thread);
}
@Override
@CompilerDirectives.TruffleBoundary
public void notifyThreadFinished(Object engine, TruffleContext context, Thread thread) {
InstrumentationHandler instrumentationHandler = (InstrumentationHandler) engineAccess().getInstrumentationHandler(engine);
instrumentationHandler.notifyThreadFinished(context, thread);
}
@Override
public org.graalvm.polyglot.SourceSection createSourceSection(Object instrumentEnv, org.graalvm.polyglot.Source source, com.oracle.truffle.api.source.SourceSection ss) {
TruffleInstrument.Env env = (TruffleInstrument.Env) instrumentEnv;
return engineAccess().createSourceSection(env.getVMObject(), source, ss);
}
@Override
public void patchInstrumentationHandler(Object vm, DispatchOutputStream out, DispatchOutputStream err, InputStream in) {
final InstrumentationHandler instrumentationHandler = (InstrumentationHandler) vm;
instrumentationHandler.patch(out, err, in);
}
@Override
public boolean isInputValueSlotIdentifier(Object identifier) {
return identifier instanceof ProbeNode.EventProviderWithInputChainNode.SavedInputValueID;
}
private static InstrumentationHandler getHandler(RootNode rootNode) {
LanguageInfo info = rootNode.getLanguageInfo();
if (info == null) {
return null;
}
Object languageShared = nodesAccess().getEngineObject(info);
if (languageShared == null) {
return null;
}
return (InstrumentationHandler) engineAccess().getInstrumentationHandler(languageShared);
}
}
}
}