/*
* Copyright (c) 2008, 2015, 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.sun.btrace.compiler;
import java.util.List;
import java.util.ArrayList;
import java.util.Locale;
import java.util.Set;
import javax.lang.model.element.Element;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.lang.model.util.Types;
import com.sun.btrace.annotations.BTrace;
import com.sun.btrace.util.Messages;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import com.sun.source.util.Trees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.JCAssign;
import com.sun.tools.javac.tree.JCTree.JCClassDecl;
import com.sun.tools.javac.tree.JCTree.JCIdent;
import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.tools.Diagnostic.Kind;
import com.sun.tools.javac.util.Context;
/**
* An annotation processor that validates a BTrace program.
* Safety rules (such as no loops, no new/throw etc.) are
* enforced. This uses javac's Tree API in addition to JSR 269.
*
* @author A. Sundararajan
*/
@SupportedAnnotationTypes("*")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
public class Verifier extends AbstractProcessor
implements TaskListener {
private final List<String> classNames = new ArrayList<>();
private Trees treeUtils;
private final List<CompilationUnitTree> compUnits = new ArrayList<>();
private ClassTree currentClass;
private final AttributionTaskListener listener = new AttributionTaskListener();
@Override
public synchronized void init(ProcessingEnvironment pe) {
super.init(pe);
treeUtils = Trees.instance(pe);
prepareContext(((JavacProcessingEnvironment)pe).getContext());
}
@Override
public boolean process(Set<? extends TypeElement> annotations,
RoundEnvironment roundEnv) {
return true;
}
@Override
public void started(TaskEvent e) {
if (e.getKind() == TaskEvent.Kind.ENTER) {
CompilationUnitTree ct = e.getCompilationUnit();
if (ct != null) {
compUnits.add(ct);
}
}
}
@Override
public void finished(TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ANALYZE) return;
if (processingEnv == null) {
return;
}
TypeElement elem = e.getTypeElement();
for(Tree t : e.getCompilationUnit().getTypeDecls()) {
if (t.getKind() == Tree.Kind.CLASS) {
if (((JCClassDecl)t).sym.equals(elem)) {
currentClass = (ClassTree)t;
break;
}
}
}
if (currentClass != null) {
verify(currentClass, elem);
}
}
List<String> getClassNames() {
return classNames;
}
CompilationUnitTree getCompilationUnit() {
for (CompilationUnitTree ct : compUnits) {
for (Tree clazz : ct.getTypeDecls()) {
if (clazz.equals(currentClass)) {
return ct;
}
}
}
return null;
}
Trees getTreeUtils() {
return treeUtils;
}
SourcePositions getSourcePositions() {
return treeUtils.getSourcePositions();
}
ProcessingEnvironment getProcessingEnvironment() {
return processingEnv;
}
Messager getMessager() {
return processingEnv.getMessager();
}
Elements getElementUtils() {
return processingEnv.getElementUtils();
}
Types getTypeUtils() {
return processingEnv.getTypeUtils();
}
Locale getLocale() {
return processingEnv.getLocale();
}
// verify each BTrace class
private boolean verify(ClassTree ct, Element topElement) {
currentClass = ct;
CompilationUnitTree cut = getCompilationUnit();
String className = ct.getSimpleName().toString();
ExpressionTree pkgName = cut.getPackageName();
if (pkgName != null) {
className = pkgName + "." + className;
}
classNames.add(className);
if (hasTrustedAnnotation(ct, topElement)) {
return true;
}
Boolean value = ct.accept(new VerifierVisitor(this, topElement), null);
return value == null? true : value;
}
/** Detects if the class is annotated as @BTrace(trusted=true). */
private boolean hasTrustedAnnotation(ClassTree ct, Element topElement) {
for (AnnotationTree at : ct.getModifiers().getAnnotations()) {
String annFqn = ((JCTree)at.getAnnotationType()).type.tsym.getQualifiedName().toString();
if (!annFqn.equals(BTrace.class.getName())) {
continue;
}
// now we have @BTrace, look for unsafe = xxx or trusted = xxx
for (ExpressionTree ext : at.getArguments()) {
if (!(ext instanceof JCAssign)) {
continue;
}
JCAssign assign = (JCAssign) ext;
String name = ((JCIdent)assign.lhs).name.toString();
if (!"unsafe".equals(name) && !"trusted".equals(name)) {
continue;
}
// now rhs is the value of @BTrace.unsafe.
// The value can be complex (!!true, 1 == 2, etc.) - we support only booleans
String val = assign.rhs.toString();
if ("true".equals(val)) {
return true; // bingo!
} else if (!"false".equals(val)) {
processingEnv.getMessager().printMessage(Kind.WARNING,
Messages.get("no.complex.unsafe.value"), topElement);
}
}
}
return false;
}
/**
* adds a listener for attribution.
*/
private void prepareContext(Context context) {
TaskListener otherListener = context.get(TaskListener.class);
if (otherListener == null) {
context.put(TaskListener.class, listener);
} else {
// handle cases of multiple listeners
context.put(TaskListener.class, (TaskListener)null);
TaskListeners listeners = new TaskListeners();
listeners.add(otherListener);
listeners.add(listener);
context.put(TaskListener.class, listeners);
}
}
/**
* A task listener that invokes the processor whenever a class is fully
* analyzed.
*/
private final class AttributionTaskListener implements TaskListener {
@Override
public void finished(TaskEvent e) {
if (e.getKind() != TaskEvent.Kind.ANALYZE) return;
TypeElement elem = e.getTypeElement();
for(Tree t : e.getCompilationUnit().getTypeDecls()) {
if (t.getKind() == Tree.Kind.CLASS) {
if (((JCClassDecl)t).sym.equals(elem)) {
currentClass = (ClassTree)t;
break;
}
}
}
if (currentClass != null) {
verify(currentClass, elem);
}
}
@Override
public void started(TaskEvent e) { }
}
/**
* A task listener multiplexer.
*/
private static class TaskListeners implements TaskListener {
private final List<TaskListener> listeners = new ArrayList<>();
public void add(TaskListener listener) {
listeners.add(listener);
}
public void remove(TaskListener listener) {
listeners.remove(listener);
}
@Override
public void finished(TaskEvent e) {
for (TaskListener listener : listeners)
listener.finished(e);
}
@Override
public void started(TaskEvent e) {
for (TaskListener listener : listeners)
listener.started(e);
}
}
}