1 package com.uber.nullaway.dataflow.cfg; 2 3 import com.google.common.base.Preconditions; 4 import com.sun.source.tree.ExpressionTree; 5 import com.sun.source.tree.MethodInvocationTree; 6 import com.sun.source.tree.ThrowTree; 7 import com.sun.source.tree.Tree; 8 import com.sun.source.tree.TreeVisitor; 9 import com.sun.source.util.TreePath; 10 import com.uber.nullaway.handlers.Handler; 11 import javax.annotation.processing.ProcessingEnvironment; 12 import javax.lang.model.type.TypeMirror; 13 import org.checkerframework.nullaway.dataflow.cfg.ControlFlowGraph; 14 import org.checkerframework.nullaway.dataflow.cfg.UnderlyingAST; 15 import org.checkerframework.nullaway.dataflow.cfg.builder.CFGBuilder; 16 import org.checkerframework.nullaway.dataflow.cfg.builder.CFGTranslationPhaseOne; 17 import org.checkerframework.nullaway.dataflow.cfg.builder.CFGTranslationPhaseThree; 18 import org.checkerframework.nullaway.dataflow.cfg.builder.CFGTranslationPhaseTwo; 19 import org.checkerframework.nullaway.dataflow.cfg.builder.ConditionalJump; 20 import org.checkerframework.nullaway.dataflow.cfg.builder.ExtendedNode; 21 import org.checkerframework.nullaway.dataflow.cfg.builder.Label; 22 import org.checkerframework.nullaway.dataflow.cfg.builder.PhaseOneResult; 23 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode; 24 import org.checkerframework.nullaway.dataflow.cfg.node.Node; 25 import org.checkerframework.nullaway.dataflow.cfg.node.ThrowNode; 26 import org.checkerframework.nullaway.javacutil.AnnotationProvider; 27 import org.checkerframework.nullaway.javacutil.BasicAnnotationProvider; 28 import org.checkerframework.nullaway.javacutil.trees.TreeBuilder; 29 30 /** 31 * A NullAway specific CFGBuilder subclass, which allows to more directly control the AST to CFG 32 * translation performed by the checker framework. 33 * 34 * <p>This holds the static method {@link #build(TreePath, UnderlyingAST, boolean, boolean, 35 * ProcessingEnvironment, Handler)}, called to perform the CFG translation, and the class {@link 36 * NullAwayCFGTranslationPhaseOne}, which extends {@link CFGTranslationPhaseOne} and adds hooks for 37 * the NullAway handlers mechanism and some utility methods. 38 */ 39 public final class NullAwayCFGBuilder extends CFGBuilder { 40 41 /** This class should never be instantiated. */ NullAwayCFGBuilder()42 private NullAwayCFGBuilder() {} 43 44 /** 45 * This static method produces a new CFG representation given a method's (or lambda/initializer) 46 * body. 47 * 48 * <p>It is analogous to {@link CFGBuilder#build(TreePath, UnderlyingAST, boolean, boolean, 49 * ProcessingEnvironment)}, but it also takes a handler to be called at specific extention points 50 * during the CFG translation. 51 * 52 * @param bodyPath the TreePath to the body of the method, lambda, or initializer. 53 * @param underlyingAST the AST that underlies the control frow graph 54 * @param assumeAssertionsEnabled can assertions be assumed to be disabled? 55 * @param assumeAssertionsDisabled can assertions be assumed to be enabled? 56 * @param env annotation processing environment containing type utilities 57 * @param handler a NullAway handler or chain of handlers (through {@link 58 * com.uber.nullaway.handlers.CompositeHandler} 59 * @return a control flow graph 60 */ build( TreePath bodyPath, UnderlyingAST underlyingAST, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled, ProcessingEnvironment env, Handler handler)61 public static ControlFlowGraph build( 62 TreePath bodyPath, 63 UnderlyingAST underlyingAST, 64 boolean assumeAssertionsEnabled, 65 boolean assumeAssertionsDisabled, 66 ProcessingEnvironment env, 67 Handler handler) { 68 TreeBuilder builder = new TreeBuilder(env); 69 AnnotationProvider annotationProvider = new BasicAnnotationProvider(); 70 CFGTranslationPhaseOne phase1translator = 71 new NullAwayCFGTranslationPhaseOne( 72 builder, 73 annotationProvider, 74 assumeAssertionsEnabled, 75 assumeAssertionsDisabled, 76 env, 77 handler); 78 PhaseOneResult phase1result = phase1translator.process(bodyPath, underlyingAST); 79 ControlFlowGraph phase2result = CFGTranslationPhaseTwo.process(phase1result); 80 ControlFlowGraph phase3result = CFGTranslationPhaseThree.process(phase2result); 81 return phase3result; 82 } 83 84 /** 85 * A NullAway specific subclass of the Checker Framework's {@link CFGTranslationPhaseOne}, 86 * augmented with handler extension points and some utility methods meant to be called from 87 * handlers to customize the AST to CFG translation. 88 */ 89 public static class NullAwayCFGTranslationPhaseOne extends CFGTranslationPhaseOne { 90 91 private final Handler handler; 92 93 /** 94 * Create a new NullAway phase one translation visitor. 95 * 96 * @param builder a TreeBuilder object (used to create synthetic AST nodes to feed to the 97 * translation process) 98 * @param annotationProvider an {@link AnnotationProvider}. 99 * @param assumeAssertionsEnabled can assertions be assumed to be disabled? 100 * @param assumeAssertionsDisabled can assertions be assumed to be enabled? 101 * @param env annotation processing environment containing type utilities 102 * @param handler a NullAway handler or chain of handlers (through {@link 103 * com.uber.nullaway.handlers.CompositeHandler} 104 */ NullAwayCFGTranslationPhaseOne( TreeBuilder builder, AnnotationProvider annotationProvider, boolean assumeAssertionsEnabled, boolean assumeAssertionsDisabled, ProcessingEnvironment env, Handler handler)105 public NullAwayCFGTranslationPhaseOne( 106 TreeBuilder builder, 107 AnnotationProvider annotationProvider, 108 boolean assumeAssertionsEnabled, 109 boolean assumeAssertionsDisabled, 110 ProcessingEnvironment env, 111 Handler handler) { 112 super(builder, annotationProvider, assumeAssertionsEnabled, assumeAssertionsDisabled, env); 113 this.handler = handler; 114 } 115 116 /** 117 * Obtain the type mirror for a given class, used for exception throwing. 118 * 119 * <p>We use this method to expose the otherwise protected method {@link #getTypeMirror(Class)} 120 * to handlers. 121 * 122 * @param klass a Java class 123 * @return the corresponding type mirror 124 */ classToErrorType(Class<?> klass)125 public TypeMirror classToErrorType(Class<?> klass) { 126 return this.getTypeMirror(klass); 127 } 128 129 /** 130 * Extend the CFG to throw an exception if the passed expression node evaluates to {@code 131 * false}. 132 * 133 * @param booleanExpressionNode a CFG Node representing a boolean expression. 134 * @param errorType the type of the exception to throw if booleanExpressionNode evaluates to 135 * {@code false}. 136 */ insertThrowOnFalse(Node booleanExpressionNode, TypeMirror errorType)137 public void insertThrowOnFalse(Node booleanExpressionNode, TypeMirror errorType) { 138 insertThrowOn(false, booleanExpressionNode, errorType); 139 } 140 141 /** 142 * Extend the CFG to throw an exception if the passed expression node evaluates to {@code true}. 143 * 144 * @param booleanExpressionNode a CFG Node representing a boolean expression. 145 * @param errorType the type of the exception to throw if booleanExpressionNode evaluates to 146 * {@code true}. 147 */ insertThrowOnTrue(Node booleanExpressionNode, TypeMirror errorType)148 public void insertThrowOnTrue(Node booleanExpressionNode, TypeMirror errorType) { 149 insertThrowOn(true, booleanExpressionNode, errorType); 150 } 151 insertThrowOn(boolean throwOn, Node booleanExpressionNode, TypeMirror errorType)152 private void insertThrowOn(boolean throwOn, Node booleanExpressionNode, TypeMirror errorType) { 153 Tree tree = booleanExpressionNode.getTree(); 154 Preconditions.checkArgument( 155 tree instanceof ExpressionTree, 156 "Argument booleanExpressionNode must represent a boolean expression"); 157 ExpressionTree booleanExpressionTree = (ExpressionTree) booleanExpressionNode.getTree(); 158 Preconditions.checkNotNull(booleanExpressionTree); 159 Label preconditionEntry = new Label(); 160 Label endPrecondition = new Label(); 161 this.scan(booleanExpressionTree, (Void) null); 162 ConditionalJump cjump = 163 new ConditionalJump( 164 throwOn ? preconditionEntry : endPrecondition, 165 throwOn ? endPrecondition : preconditionEntry); 166 this.extendWithExtendedNode(cjump); 167 this.addLabelForNextNode(preconditionEntry); 168 ExtendedNode exNode = 169 this.extendWithNodeWithException( 170 new ThrowNode( 171 new ThrowTree() { 172 // Dummy throw tree, unused. We could use null here, but that violates nullness 173 // typing. 174 @Override 175 public ExpressionTree getExpression() { 176 return booleanExpressionTree; 177 } 178 179 @Override 180 public Kind getKind() { 181 return Kind.THROW; 182 } 183 184 @Override 185 public <R, D> R accept(TreeVisitor<R, D> visitor, D data) { 186 return visitor.visitThrow(this, data); 187 } 188 }, 189 booleanExpressionNode, 190 this.env.getTypeUtils()), 191 errorType); 192 exNode.setTerminatesExecution(true); 193 this.addLabelForNextNode(endPrecondition); 194 } 195 196 @Override visitMethodInvocation(MethodInvocationTree tree, Void p)197 public MethodInvocationNode visitMethodInvocation(MethodInvocationTree tree, Void p) { 198 MethodInvocationNode originalNode = super.visitMethodInvocation(tree, p); 199 return handler.onCFGBuildPhase1AfterVisitMethodInvocation(this, tree, originalNode); 200 } 201 } 202 } 203