/*
* 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.
*
* 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.dsl.processor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.element.VariableElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeKind;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.ElementFilter;
import javax.tools.Diagnostic.Kind;
import com.oracle.truffle.api.dsl.GeneratedBy;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.instrumentation.Instrumentable;
import com.oracle.truffle.api.instrumentation.InstrumentableFactory;
import com.oracle.truffle.api.instrumentation.InstrumentableFactory.WrapperNode;
import com.oracle.truffle.api.instrumentation.ProbeNode;
import com.oracle.truffle.api.nodes.Node.Child;
import com.oracle.truffle.api.nodes.NodeCost;
import com.oracle.truffle.api.source.SourceSection;
import com.oracle.truffle.dsl.processor.generator.GeneratorUtils;
import com.oracle.truffle.dsl.processor.java.ElementUtils;
import com.oracle.truffle.dsl.processor.java.model.CodeAnnotationMirror;
import com.oracle.truffle.dsl.processor.java.model.CodeAnnotationValue;
import com.oracle.truffle.dsl.processor.java.model.CodeExecutableElement;
import com.oracle.truffle.dsl.processor.java.model.CodeTreeBuilder;
import com.oracle.truffle.dsl.processor.java.model.CodeTypeElement;
import com.oracle.truffle.dsl.processor.java.model.CodeTypeMirror;
import com.oracle.truffle.dsl.processor.java.model.CodeVariableElement;
import com.oracle.truffle.dsl.processor.java.transform.FixWarningsVisitor;
import com.oracle.truffle.dsl.processor.java.transform.GenerateOverrideVisitor;
@SupportedAnnotationTypes("com.oracle.truffle.api.instrumentation.Instrumentable")
public final class InstrumentableProcessor extends AbstractProcessor {
// configuration
private static final String CLASS_SUFFIX = "Wrapper";
private static final String EXECUTE_METHOD_PREFIX = "execute";
// API name assumptions
private static final String METHOD_GET_NODE_COST = "getCost";
private static final String METHOD_ON_RETURN_EXCEPTIONAL = "onReturnExceptional";
private static final String METHOD_ON_RETURN_VALUE = "onReturnValue";
private static final String METHOD_ON_ENTER = "onEnter";
private static final String FIELD_DELEGATE = "delegateNode";
private static final String FIELD_PROBE = "probeNode";
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latest();
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (roundEnv.processingOver()) {
return false;
}
try {
ProcessorContext context = new ProcessorContext(processingEnv, null);
ProcessorContext.setThreadLocalInstance(context);
for (Element element : roundEnv.getElementsAnnotatedWith(Instrumentable.class)) {
if (!element.getKind().isClass() && !element.getKind().isInterface()) {
continue;
}
try {
if (element.getKind() != ElementKind.CLASS) {
emitError(element, String.format("Only classes can be annotated with %s.", Instrumentable.class.getSimpleName()));
continue;
}
TypeMirror instrumentableType = context.getType(Instrumentable.class);
AnnotationMirror instrumentable = ElementUtils.findAnnotationMirror(element.getAnnotationMirrors(), instrumentableType);
if (instrumentable == null) {
continue;
}
TypeMirror factoryType = ElementUtils.getAnnotationValue(TypeMirror.class, instrumentable, "factory");
final boolean generateWrapper;
if (factoryType == null || factoryType.getKind() == TypeKind.ERROR) {
// factory type is erroneous or null (can mean error in javac)
// generate it
generateWrapper = true;
} else {
TypeElement type = context.getEnvironment().getElementUtils().getTypeElement("com.oracle.truffle.api.instrumentation.test.TestErrorFactory");
if (type != null && ElementUtils.typeEquals(factoryType, type.asType())) {
generateWrapper = true;
} else {
// factory is user defined or already generated
generateWrapper = false;
}
}
if (!generateWrapper) {
continue;
}
CodeTypeElement unit = generateWrapperAndFactory(context, element);
if (unit == null) {
continue;
}
DeclaredType overrideType = (DeclaredType) context.getType(Override.class);
DeclaredType unusedType = (DeclaredType) context.getType(SuppressWarnings.class);
unit.accept(new GenerateOverrideVisitor(overrideType), null);
unit.accept(new FixWarningsVisitor(context.getEnvironment(), unusedType, overrideType), null);
unit.accept(new CodeWriter(context.getEnvironment(), element), null);
} catch (Throwable e) {
// never throw annotation processor exceptions to the compiler
// it might screw up its state.
handleThrowable(e, element);
}
}
return true;
} finally {
ProcessorContext.setThreadLocalInstance(null);
}
}
private void handleThrowable(Throwable t, Element e) {
String message = "Uncaught error in " + getClass().getSimpleName() + " while processing " + e + " ";
ProcessorContext.getInstance().getEnvironment().getMessager().printMessage(Kind.ERROR, message + ": " + ElementUtils.printException(t), e);
}
private CodeTypeElement generateWrapperAndFactory(ProcessorContext context, Element e) {
CodeTypeElement wrapper = generateWrapper(context, e);
if (wrapper == null) {
return null;
}
CodeTypeElement factory = generateFactory(context, e, wrapper);
wrapper.getModifiers().add(Modifier.STATIC);
factory.add(wrapper);
assertNoErrorExpected(e);
return factory;
}
private static CodeTypeElement generateFactory(ProcessorContext context, Element e, CodeTypeElement wrapper) {
TypeElement sourceType = (TypeElement) e;
PackageElement pack = context.getEnvironment().getElementUtils().getPackageOf(sourceType);
Set<Modifier> typeModifiers = ElementUtils.modifiers(Modifier.PUBLIC, Modifier.FINAL);
CodeTypeElement factory = new CodeTypeElement(typeModifiers, ElementKind.CLASS, pack, createWrapperClassName(sourceType));
TypeMirror factoryType = context.reloadType(context.getType(InstrumentableFactory.class));
factory.getImplements().add(new CodeTypeMirror.DeclaredCodeTypeMirror(ElementUtils.fromTypeMirror(factoryType), Arrays.asList(sourceType.asType())));
addGeneratedBy(context, factory, sourceType);
TypeMirror returnType = context.getType(WrapperNode.class);
CodeExecutableElement createMethod = new CodeExecutableElement(ElementUtils.modifiers(Modifier.PUBLIC), returnType, "createWrapper");
createMethod.addParameter(new CodeVariableElement(sourceType.asType(), FIELD_DELEGATE));
createMethod.addParameter(new CodeVariableElement(context.getType(ProbeNode.class), FIELD_PROBE));
CodeTreeBuilder builder = createMethod.createBuilder();
ExecutableElement constructor = ElementFilter.constructorsIn(wrapper.getEnclosedElements()).iterator().next();
String firstParameterReference = null;
if (constructor.getParameters().size() > 2) {
TypeMirror firstParameter = constructor.getParameters().get(0).asType();
if (ElementUtils.typeEquals(firstParameter, sourceType.asType())) {
firstParameterReference = FIELD_DELEGATE;
} else if (ElementUtils.typeEquals(firstParameter, context.getType(SourceSection.class))) {
firstParameterReference = FIELD_DELEGATE + ".getSourceSection()";
}
}
builder.startReturn().startNew(wrapper.asType());
if (firstParameterReference != null) {
builder.string(firstParameterReference);
}
builder.string(FIELD_DELEGATE).string(FIELD_PROBE);
builder.end().end();
factory.add(createMethod);
return factory;
}
private static String createWrapperClassName(TypeElement sourceType) {
return sourceType.getSimpleName().toString() + CLASS_SUFFIX;
}
private CodeTypeElement generateWrapper(ProcessorContext context, Element e) {
if (!e.getKind().isClass()) {
return null;
}
if (!e.getModifiers().contains(Modifier.PUBLIC)) {
emitError(e, "Class must be public to generate a wrapper.");
return null;
}
if (e.getModifiers().contains(Modifier.FINAL)) {
emitError(e, "Class must not be final to generate a wrapper.");
return null;
}
if (e.getEnclosingElement().getKind() != ElementKind.PACKAGE && !e.getModifiers().contains(Modifier.STATIC)) {
emitError(e, "Inner class must be static to generate a wrapper.");
return null;
}
TypeElement sourceType = (TypeElement) e;
ExecutableElement constructor = null;
List<ExecutableElement> constructors = ElementFilter.constructorsIn(e.getEnclosedElements());
if (constructors.isEmpty()) {
// add default constructor
constructors.add(new CodeExecutableElement(ElementUtils.modifiers(Modifier.PUBLIC), null, e.getSimpleName().toString()));
}
// try visible default constructor
for (ListIterator<ExecutableElement> iterator = constructors.listIterator(); iterator.hasNext();) {
ExecutableElement c = iterator.next();
Modifier modifier = ElementUtils.getVisibility(c.getModifiers());
if (modifier == null || modifier == Modifier.PRIVATE) {
iterator.remove();
continue;
}
if (c.getParameters().isEmpty()) {
constructor = c;
break;
}
}
// try copy constructor
if (constructor == null) {
for (ExecutableElement c : constructors) {
VariableElement firstParameter = c.getParameters().iterator().next();
if (ElementUtils.typeEquals(firstParameter.asType(), sourceType.asType())) {
constructor = c;
break;
}
}
}
// try source section constructor
if (constructor == null) {
for (ExecutableElement c : constructors) {
VariableElement firstParameter = c.getParameters().iterator().next();
if (ElementUtils.typeEquals(firstParameter.asType(), context.getType(SourceSection.class))) {
constructor = c;
break;
}
}
}
if (constructor == null) {
emitError(sourceType, "No suiteable constructor found for wrapper factory generation. At least one default or copy constructor must be visible.");
return null;
}
PackageElement pack = context.getEnvironment().getElementUtils().getPackageOf(sourceType);
Set<Modifier> typeModifiers = ElementUtils.modifiers(Modifier.PRIVATE, Modifier.FINAL);
CodeTypeElement wrapperType = new CodeTypeElement(typeModifiers, ElementKind.CLASS, pack, sourceType.getSimpleName().toString() + CLASS_SUFFIX + "0");
TypeMirror resolvedSuperType = sourceType.asType();
wrapperType.setSuperClass(resolvedSuperType);
wrapperType.getImplements().add(context.getType(WrapperNode.class));
addGeneratedBy(context, wrapperType, sourceType);
wrapperType.add(createNodeChild(context, sourceType.asType(), FIELD_DELEGATE));
wrapperType.add(createNodeChild(context, context.getType(ProbeNode.class), FIELD_PROBE));
CodeExecutableElement wrappedConstructor = GeneratorUtils.createConstructorUsingFields(ElementUtils.modifiers(Modifier.PRIVATE), wrapperType, constructor);
wrapperType.add(wrappedConstructor);
// generate getters
for (VariableElement field : wrapperType.getFields()) {
CodeExecutableElement getter = new CodeExecutableElement(ElementUtils.modifiers(Modifier.PUBLIC), field.asType(), "get" +
ElementUtils.firstLetterUpperCase(field.getSimpleName().toString()));
getter.createBuilder().startReturn().string(field.getSimpleName().toString()).end();
wrapperType.add(getter);
}
if (isOverrideableOrUndeclared(sourceType, METHOD_GET_NODE_COST)) {
TypeMirror returnType = context.getType(NodeCost.class);
CodeExecutableElement getInstrumentationTags = new CodeExecutableElement(ElementUtils.modifiers(Modifier.PUBLIC), returnType, METHOD_GET_NODE_COST);
getInstrumentationTags.createBuilder().startReturn().staticReference(returnType, "NONE").end();
wrapperType.add(getInstrumentationTags);
}
List<ExecutableElement> wrappedExecuteMethods = new ArrayList<>();
List<? extends Element> elementList = context.getEnvironment().getElementUtils().getAllMembers(sourceType);
for (ExecutableElement method : ElementFilter.methodsIn(elementList)) {
Set<Modifier> modifiers = method.getModifiers();
if (modifiers.contains(Modifier.FINAL)) {
continue;
}
Modifier visibility = ElementUtils.getVisibility(modifiers);
if (visibility == Modifier.PRIVATE || visibility == null) {
continue;
}
String methodName = method.getSimpleName().toString();
if (methodName.startsWith(EXECUTE_METHOD_PREFIX)) {
wrappedExecuteMethods.add(method);
} else {
if (modifiers.contains(Modifier.ABSTRACT) && !methodName.equals("getSourceSection") //
&& !methodName.equals(METHOD_GET_NODE_COST)) {
emitError(sourceType, String.format("Unable to implement unknown abstract method %s in generated wrapper node.", ElementUtils.createReferenceName(method)));
return null;
}
}
}
if (wrappedExecuteMethods.isEmpty()) {
emitError(sourceType, String.format("No methods starting with name execute found to wrap."));
return null;
}
Collections.sort(wrappedExecuteMethods, new Comparator<ExecutableElement>() {
public int compare(ExecutableElement o1, ExecutableElement o2) {
return ElementUtils.compareMethod(o1, o2);
}
});
for (ExecutableElement executeMethod : wrappedExecuteMethods) {
CodeExecutableElement wrappedExecute = CodeExecutableElement.clone(processingEnv, executeMethod);
wrappedExecute.getModifiers().remove(Modifier.ABSTRACT);
wrappedExecute.getAnnotationMirrors().clear();
String frameParameterName = "null";
for (VariableElement parameter : wrappedExecute.getParameters()) {
if (ElementUtils.typeEquals(context.getType(VirtualFrame.class), parameter.asType())) {
frameParameterName = parameter.getSimpleName().toString();
break;
}
}
CodeTreeBuilder builder = wrappedExecute.createBuilder();
builder.startTryBlock();
builder.startStatement().startCall(FIELD_PROBE, METHOD_ON_ENTER).string(frameParameterName).end().end();
CodeTreeBuilder callDelegate = builder.create();
callDelegate.startCall(FIELD_DELEGATE, executeMethod.getSimpleName().toString());
for (VariableElement parameter : wrappedExecute.getParameters()) {
callDelegate.string(parameter.getSimpleName().toString());
}
callDelegate.end();
String returnName;
if (ElementUtils.isVoid(executeMethod.getReturnType())) {
returnName = "null";
builder.statement(callDelegate.build());
} else {
returnName = "returnValue";
builder.declaration(executeMethod.getReturnType(), returnName, callDelegate.build());
}
builder.startStatement().startCall(FIELD_PROBE, METHOD_ON_RETURN_VALUE).string(frameParameterName).string(returnName).end().end();
if (!ElementUtils.isVoid(executeMethod.getReturnType())) {
builder.startReturn().string(returnName).end();
}
builder.end().startCatchBlock(context.getType(Throwable.class), "t");
builder.startStatement().startCall(FIELD_PROBE, METHOD_ON_RETURN_EXCEPTIONAL).string(frameParameterName).string("t").end().end();
builder.startThrow().string("t").end();
builder.end();
wrapperType.add(wrappedExecute);
}
return wrapperType;
}
private static void addGeneratedBy(ProcessorContext context, CodeTypeElement generatedType, TypeElement generatedByType) {
DeclaredType generatedBy = (DeclaredType) context.getType(GeneratedBy.class);
// only do this if generatedBy is on the classpath.
if (generatedBy != null) {
CodeAnnotationMirror generatedByAnnotation = new CodeAnnotationMirror(generatedBy);
generatedByAnnotation.setElementValue(generatedByAnnotation.findExecutableElement("value"), new CodeAnnotationValue(generatedByType.asType()));
generatedType.addAnnotationMirror(generatedByAnnotation);
}
}
private static boolean isOverrideableOrUndeclared(TypeElement sourceType, String methodName) {
List<ExecutableElement> elements = ElementUtils.getDeclaredMethodsInSuperTypes(sourceType, methodName);
return elements.isEmpty() || !elements.iterator().next().getModifiers().contains(Modifier.FINAL);
}
private static CodeVariableElement createNodeChild(ProcessorContext context, TypeMirror type, String name) {
CodeVariableElement var = new CodeVariableElement(ElementUtils.modifiers(Modifier.PRIVATE), type, name);
var.addAnnotationMirror(new CodeAnnotationMirror((DeclaredType) context.getType(Child.class)));
return var;
}
void assertNoErrorExpected(Element e) {
ExpectError.assertNoErrorExpected(processingEnv, e);
}
void emitError(Element e, String msg) {
if (ExpectError.isExpectedError(processingEnv, e, msg)) {
return;
}
processingEnv.getMessager().printMessage(Kind.ERROR, msg, e);
}
void emitError(Element e, AnnotationMirror annotation, String msg) {
if (ExpectError.isExpectedError(processingEnv, e, msg)) {
return;
}
processingEnv.getMessager().printMessage(Kind.ERROR, msg, e, annotation);
}
}