• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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