/* * Copyright (c) 2008, 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.sun.btrace.compiler; import java.util.List; import java.util.Set; import javax.lang.model.element.Modifier; import javax.lang.model.element.Name; import javax.tools.Diagnostic; import com.sun.source.tree.*; import com.sun.source.util.SourcePositions; import com.sun.source.util.TreeScanner; import com.sun.btrace.annotations.BTrace; import com.sun.btrace.annotations.Injected; import com.sun.btrace.annotations.Kind; import com.sun.btrace.annotations.OnError; import com.sun.btrace.annotations.OnExit; import com.sun.btrace.annotations.OnMethod; import com.sun.btrace.annotations.Sampled; import com.sun.btrace.util.Messages; import com.sun.source.util.TreePath; import com.sun.tools.javac.tree.JCTree; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.ExecutableElement; import javax.lang.model.element.VariableElement; import javax.lang.model.type.TypeMirror; /** * This class tree visitor validates a BTrace program's ClassTree. * * @author A. Sundararajan */ public class VerifierVisitor extends TreeScanner<Boolean, Void> { private static final String ON_ERROR_TYPE = OnError.class.getName(); private static final String ON_EXIT_TYPE = OnExit.class.getName(); private static final String THROWABLE_TYPE = Throwable.class.getName(); private final Verifier verifier; private String className; private String fqn; private boolean insideMethod; private boolean shortSyntax = false; private TypeMirror btraceServiceTm = null; private TypeMirror runtimeServiceTm = null; private TypeMirror simpleServiceTm = null; private TypeMirror serviceInjectorTm = null; public VerifierVisitor(Verifier verifier, Element clzElement) { this.verifier = verifier; Collection<ExecutableElement> shared = new ArrayList<>(); for(Element e : clzElement.getEnclosedElements()) { if (e.getKind() == ElementKind.METHOD && e.getModifiers().containsAll(EnumSet.of(Modifier.STATIC, Modifier.PRIVATE))) { shared.add((ExecutableElement)e); } } btraceServiceTm = verifier.getElementUtils().getTypeElement("com.sun.btrace.services.spi.BTraceService").asType(); runtimeServiceTm = verifier.getElementUtils().getTypeElement("com.sun.btrace.services.spi.RuntimeService").asType(); simpleServiceTm = verifier.getElementUtils().getTypeElement("com.sun.btrace.services.spi.SimpleService").asType(); serviceInjectorTm = verifier.getElementUtils().getTypeElement("com.sun.btrace.services.api.Service").asType(); } @Override public Boolean visitMethodInvocation(MethodInvocationTree node, Void v) { Element e = getElement(node); if (e.getKind() == ElementKind.METHOD || e.getKind() == ElementKind.CONSTRUCTOR) { String name = ((ExecutableElement)e).getSimpleName().toString(); // allow constructor calls if (name.equals("<init>")) { return super.visitMethodInvocation(node, v); } Element parent = null; do { parent = e.getEnclosingElement(); } while (parent != null && (parent.getKind() != ElementKind.CLASS && parent.getKind() != ElementKind.INTERFACE)); if (parent != null) { TypeMirror tm = parent.asType(); String typeName = tm.toString(); if (isSameClass(typeName)) { return super.visitMethodInvocation(node, v); } if (isBTraceClass(typeName)) { return super.visitMethodInvocation(node, v); } // check service injection if (verifier.getTypeUtils().isSubtype(tm, btraceServiceTm)) { return super.visitMethodInvocation(node, v); } if (verifier.getTypeUtils().isSubtype(tm, serviceInjectorTm)) { if (!validateInjectionParams(node)) { reportError("service.injector.literals", node); } return super.visitMethodInvocation(node, v); } } } reportError("no.method.calls", node); return super.visitMethodInvocation(node, v); } private boolean isSameClass(String typeName) { return fqn.equals(typeName); } private boolean isBTraceClass(String typeName) { return typeName.equals("com.sun.btrace.BTraceUtils") || typeName.startsWith("com.sun.btrace.BTraceUtils."); } @Override public Boolean visitAssert(AssertTree node, Void v) { reportError("no.asserts", node); return super.visitAssert(node, v); } @Override public Boolean visitAssignment(AssignmentTree node, Void v) { checkLValue(node.getVariable()); return super.visitAssignment(node, v); } @Override public Boolean visitCompoundAssignment(CompoundAssignmentTree node, Void v) { checkLValue(node.getVariable()); return super.visitCompoundAssignment(node, v); } @Override public Boolean visitCatch(CatchTree node, Void v) { reportError("no.catch", node); return super.visitCatch(node, v); } @Override public Boolean visitClass(ClassTree node, Void v) { // check for local class if (insideMethod) { reportError("no.local.class", node); } // check for short BTrace syntax (inferring redundant access qualifiers) Set<Modifier> mods = node.getModifiers().getFlags(); if (!mods.contains(Modifier.PRIVATE) && !mods.contains(Modifier.PROTECTED) && !mods.contains(Modifier.PUBLIC)) { shortSyntax = true; } // check for inner and nested class List<? extends Tree> members = node.getMembers(); for (Tree m : members) { if (m.getKind() == Tree.Kind.CLASS) { reportError("no.nested.class", m); } if (m.getKind() == Tree.Kind.VARIABLE) { VariableTree vt = (VariableTree)m; boolean isStatic = isStatic(vt.getModifiers().getFlags()); if (shortSyntax) { if (isStatic) { reportError("no.static.variables", m); } } else { if (! isStatic) { reportError("no.instance.variables", m); } } } } // should extend java.lang.Object Tree superClass = node.getExtendsClause(); if (superClass != null) { String name = superClass.toString(); if (!name.equals("Object") && !name.equals("java.lang.Object")) { reportError("object.superclass.required", superClass); } } // should not implement interfaces List<? extends Tree> interfaces = node.getImplementsClause(); if (interfaces != null && interfaces.size() > 0) { reportError("no.interface.implementation", interfaces.get(0)); } ModifiersTree mt = node.getModifiers(); if (!shortSyntax && ! isPublic(mt.getFlags())) { reportError("class.should.be.public", node); } List<? extends AnnotationTree> anno = mt.getAnnotations(); if (anno != null && !anno.isEmpty()) { String btrace = BTrace.class.getName(); for (AnnotationTree at : anno) { String name = at.getAnnotationType().toString(); if (name.equals(btrace) || name.equals("BTrace")) { String oldClassName = className; try { className = node.getSimpleName().toString(); fqn = getElement(node).asType().toString(); return super.visitClass(node, v); } finally { className = oldClassName; } } } } return reportError("not.a.btrace.program", node); } @Override public Boolean visitDoWhileLoop(DoWhileLoopTree node, Void v) { reportError("no.do.while", node); return super.visitDoWhileLoop(node, v); } @Override public Boolean visitEnhancedForLoop(EnhancedForLoopTree node, Void v) { reportError("no.enhanced.for", node); return super.visitEnhancedForLoop(node, v); } @Override public Boolean visitForLoop(ForLoopTree node, Void v) { reportError("no.for.loop", node); return super.visitForLoop(node, v); } @Override public Boolean visitMethod(MethodTree node, Void v) { boolean oldInsideMethod = insideMethod; insideMethod = true; try { Name name = node.getName(); if (name.contentEquals("<init>")) { return super.visitMethod(node, v); } else { checkSampling(node); if (isExitHandler(node)) { if (node.getParameters().size() != 1 || ! "int".equals(node.getParameters().get(0).getType().toString())) { reportError("onexit.invalid", node); return super.visitMethod(node, v); } } if (isErrorHandler(node)) { Element thrElement = getElement(node.getParameters().get(0).getType()); if (node.getParameters().size() != 1 || ! THROWABLE_TYPE.equals(thrElement.toString())) { reportError("onerror.invalid", node); } } final Map<String, Integer> annotationHisto = new HashMap<>(); for(VariableTree vt : node.getParameters()) { vt.accept(new TreeScanner<Void, Void>() { @Override public Void visitAnnotation(AnnotationTree at, Void p) { String annType = at.getAnnotationType().toString(); Integer i = annotationHisto.get(annType); if (i == null) { annotationHisto.put(annType, 0); } else { annotationHisto.put(annType, i + 1); } return super.visitAnnotation(at, p); } }, null); } Set<Modifier> flags = node.getModifiers().getFlags(); if (shortSyntax) { if (isStatic(flags)) { reportError("no.static.method", node); } if (isSynchronized(flags)) { reportError("no.synchronized.methods", node); } } else { boolean isStatic = isStatic(flags); if (isStatic) { boolean isPublic = isPublic(node.getModifiers().getFlags()); if (isPublic) { if (isSynchronized(flags)) { reportError("no.synchronized.methods", node); } } else { // force the "public" modifier only on the annotated methods if (isAnnotated(node)) { reportError("method.should.be.public", node); } } } else { reportError("no.instance.method", node); } } return super.visitMethod(node, v); } } finally { insideMethod = oldInsideMethod; } } @Override public Boolean visitNewArray(NewArrayTree node, Void v) { reportError("no.array.creation", node); return super.visitNewArray(node, v); } @Override public Boolean visitNewClass(NewClassTree node, Void v) { reportError("no.new.object", node); return super.visitNewClass(node, v); } @Override public Boolean visitReturn(ReturnTree node, Void v) { if (node.getExpression() != null) { TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), node); while (tp != null) { tp = tp.getParentPath(); Tree leaf = tp.getLeaf(); if (leaf.getKind() == Tree.Kind.METHOD) { if (isAnnotated((MethodTree)leaf)) { return reportError("return.type.should.be.void", node); } else { return super.visitReturn(node, v); } } } } return super.visitReturn(node, v); } @Override public Boolean visitMemberSelect(MemberSelectTree node, Void v) { if (node.getIdentifier().contentEquals("class")) { TypeMirror tm = getType(node.getExpression()); if (!verifier.getTypeUtils().isSubtype(tm, btraceServiceTm)) { reportError("no.class.literals", node); } } return super.visitMemberSelect(node, v); } @Override public Boolean visitSynchronized(SynchronizedTree node, Void v) { reportError("no.synchronized.blocks", node); return super.visitSynchronized(node, v); } @Override public Boolean visitThrow(ThrowTree node, Void v) { reportError("no.throw", node); return super.visitThrow(node, v); } @Override public Boolean visitTry(TryTree node, Void v) { reportError("no.try", node); return super.visitTry(node, v); } @Override public Boolean visitVariable(VariableTree vt, Void p) { VariableElement ve = (VariableElement)getElement(vt); if (ve.getEnclosingElement().getKind() == ElementKind.CLASS) { // only applying to fields if (verifier.getTypeUtils().isSubtype(ve.asType(), btraceServiceTm)) { Injected i = ve.getAnnotation(Injected.class); if (i == null) { reportError("missing.injected", vt); } else { switch (i.value()) { case RUNTIME: { if (!verifier.getTypeUtils().isSubtype(ve.asType(), runtimeServiceTm)) { reportError("injected.no.runtime", vt); } break; } case SIMPLE: { if (!verifier.getTypeUtils().isSubtype(ve.asType(), simpleServiceTm)) { reportError("injected.no.simple", vt); } break; } } } if (vt.getInitializer() != null) { reportError("injected.no.initializer", vt.getInitializer()); } } } return super.visitVariable(vt, p); } @Override public Boolean visitWhileLoop(WhileLoopTree node, Void v) { reportError("no.while.loop", node); return super.visitWhileLoop(node, v); } @Override public Boolean visitOther(Tree node, Void v) { reportError("no.other", node); return super.visitOther(node, v); } private boolean isStatic(Set<Modifier> modifiers) { for (Modifier m : modifiers) { if (m == Modifier.STATIC) { return true; } } return false; } private boolean isSynchronized(Set<Modifier> modifiers) { for (Modifier m : modifiers) { if (m == Modifier.SYNCHRONIZED) { return true; } } return false; } private boolean isPublic(Set<Modifier> modifiers) { for (Modifier m : modifiers) { if (m == Modifier.PUBLIC) { return true; } } return false; } private boolean isErrorHandler(MethodTree node) { ModifiersTree mt = node.getModifiers(); List<? extends AnnotationTree> annos = mt.getAnnotations(); for(AnnotationTree at : annos) { String annFqn = ((JCTree)at.getAnnotationType()).type.tsym.getQualifiedName().toString(); if (annFqn.equals(ON_ERROR_TYPE)) { return true; } } return false; } private boolean isExitHandler(MethodTree node) { ModifiersTree mt = node.getModifiers(); List<? extends AnnotationTree> annos = mt.getAnnotations(); for(AnnotationTree at : annos) { String annFqn = ((JCTree)at.getAnnotationType()).type.tsym.getQualifiedName().toString(); if (annFqn.equals(ON_EXIT_TYPE)) { return true; } } return false; } private boolean isAnnotated(MethodTree node) { ModifiersTree mt = node.getModifiers(); List<? extends AnnotationTree> annos = mt.getAnnotations(); for(AnnotationTree at : annos) { String annFqn = ((JCTree)at.getAnnotationType()).type.tsym.getQualifiedName().toString(); if (annFqn.startsWith("com.sun.btrace.annotations")) { return true; } } return false; } private boolean checkSampling(MethodTree node) { TreePath mPath = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), node); ExecutableElement ee = (ExecutableElement)verifier.getTreeUtils().getElement(mPath); Sampled s = ee.getAnnotation(Sampled.class); OnMethod om = ee.getAnnotation(OnMethod.class); if (s != null && om != null) { Kind k = om.location().value(); switch (k) { case ENTRY: case RETURN: case ERROR: case CALL: { return true; } default: { // noop } } reportError("sampler.invalid.location", node); return false; } return true; } private boolean checkLValue(Tree variable) { if (variable.getKind() == Tree.Kind.ARRAY_ACCESS) { reportError("no.assignment", variable); return false; } if (variable.getKind() != Tree.Kind.IDENTIFIER) { if (className != null) { String name = variable.toString(); name = name.substring(0, name.lastIndexOf('.')); if (! className.equals(name)) { reportError("no.assignment", variable); return false; } } else { reportError("no.assignment", variable); return false; } } return true; } private Boolean reportError(String msg, Tree node) { SourcePositions srcPos = verifier.getSourcePositions(); CompilationUnitTree compUnit = verifier.getCompilationUnit(); if (compUnit != null) { long pos = srcPos.getStartPosition(compUnit, node); long line = compUnit.getLineMap().getLineNumber(pos); String name = compUnit.getSourceFile().getName(); msg = String.format("%s:%d:%s", name, line, Messages.get(msg)); verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, msg, getElement(node)); } else { verifier.getMessager().printMessage(Diagnostic.Kind.ERROR, msg); } return Boolean.FALSE; } private Element getElement(Tree t) { TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), t); Element e = verifier.getTreeUtils().getElement(tp); if (e == null) { // hack to make JDK 7 symbol resolution working if (t instanceof MethodInvocationTree) { JCTree.JCExpression jce = ((JCTree.JCMethodInvocation)t).meth; if (jce instanceof IdentifierTree) { e = ((JCTree.JCIdent)jce).sym; } else if (jce instanceof MemberSelectTree) { e = ((JCTree.JCFieldAccess)jce).sym; } } else if (t instanceof JCTree.JCIdent) { e = ((JCTree.JCIdent)t).sym; } else if (t instanceof JCTree.JCNewClass) { e = ((JCTree.JCIdent)((JCTree.JCNewClass)t).clazz).sym; } else if (t instanceof JCTree.JCThrow) { e = ((JCTree.JCNewClass)((JCTree.JCThrow)t).expr).type.tsym; } } return e; } private TypeMirror getType(Tree t) { TreePath tp = verifier.getTreeUtils().getPath(verifier.getCompilationUnit(), t); return verifier.getTreeUtils().getTypeMirror(tp); } private boolean validateInjectionParams(MethodInvocationTree node) { boolean allLiterals = true; outer: for(ExpressionTree arg : node.getArguments()) { switch (arg.getKind()) { case MEMBER_SELECT: { if (!arg.toString().endsWith(".class")) { allLiterals = false; break outer; } break; } case STRING_LITERAL: { // allowed parameters break; } default: { allLiterals = false; break outer; } } } return allLiterals; } }