• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2021 Uber Technologies, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a copy
5  * of this software and associated documentation files (the "Software"), to deal
6  * in the Software without restriction, including without limitation the rights
7  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8  * copies of the Software, and to permit persons to whom the Software is
9  * furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20  * THE SOFTWARE.
21  */
22 package com.uber.nullaway.handlers;
23 
24 import static com.uber.nullaway.ASTHelpersBackports.getEnclosedElements;
25 import static com.uber.nullaway.NullabilityUtil.castToNonNull;
26 
27 import com.google.common.collect.ImmutableSet;
28 import com.google.errorprone.VisitorState;
29 import com.google.errorprone.suppliers.Supplier;
30 import com.google.errorprone.suppliers.Suppliers;
31 import com.google.errorprone.util.ASTHelpers;
32 import com.sun.source.tree.ClassTree;
33 import com.sun.source.tree.MethodInvocationTree;
34 import com.sun.tools.javac.code.Symbol;
35 import com.sun.tools.javac.code.Type;
36 import com.sun.tools.javac.code.Types;
37 import com.uber.nullaway.NullAway;
38 import com.uber.nullaway.Nullness;
39 import com.uber.nullaway.annotations.Initializer;
40 import com.uber.nullaway.dataflow.AccessPath;
41 import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
42 import java.util.ArrayList;
43 import java.util.List;
44 import java.util.Optional;
45 import javax.annotation.Nullable;
46 import javax.lang.model.element.Element;
47 import javax.lang.model.element.ElementKind;
48 import javax.lang.model.type.TypeKind;
49 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
50 import org.checkerframework.nullaway.dataflow.cfg.node.Node;
51 
52 public class GrpcHandler extends BaseNoOpHandler {
53   private static final String GRPC_METADATA_TNAME = "io.grpc.Metadata";
54   private static final String GRPC_METADATA_KEY_TNAME = "io.grpc.Metadata.Key";
55   private static final String GRPC_CONTAINSKEY_MNAME = "containsKey";
56   private static final String GRPC_GETTER_MNAME = "get";
57 
58   private static final Supplier<Type> GRPC_METADATA_TYPE_SUPPLIER =
59       Suppliers.typeFromString(GRPC_METADATA_TNAME);
60 
61   private static final Supplier<Type> GRPC_METADATA_KEY_TYPE_SUPPLIER =
62       Suppliers.typeFromString(GRPC_METADATA_KEY_TNAME);
63 
64   private Optional<Type> grpcMetadataType;
65   private Optional<Type> grpcKeyType;
66 
67   /**
68    * This method is annotated {@code @Initializer} since it will be invoked when the first class is
69    * processed, before any other handler methods
70    */
71   @Initializer
72   @Override
onMatchTopLevelClass( NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol)73   public void onMatchTopLevelClass(
74       NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol) {
75     if (grpcMetadataType == null || grpcKeyType == null) {
76       grpcMetadataType =
77           Optional.ofNullable(GRPC_METADATA_TYPE_SUPPLIER.get(state))
78               .map(state.getTypes()::erasure);
79       grpcKeyType =
80           Optional.ofNullable(GRPC_METADATA_KEY_TYPE_SUPPLIER.get(state))
81               .map(state.getTypes()::erasure);
82     }
83   }
84 
85   @Override
onDataflowVisitMethodInvocation( MethodInvocationNode node, Symbol.MethodSymbol symbol, VisitorState state, AccessPath.AccessPathContext apContext, AccessPathNullnessPropagation.SubNodeValues inputs, AccessPathNullnessPropagation.Updates thenUpdates, AccessPathNullnessPropagation.Updates elseUpdates, AccessPathNullnessPropagation.Updates bothUpdates)86   public NullnessHint onDataflowVisitMethodInvocation(
87       MethodInvocationNode node,
88       Symbol.MethodSymbol symbol,
89       VisitorState state,
90       AccessPath.AccessPathContext apContext,
91       AccessPathNullnessPropagation.SubNodeValues inputs,
92       AccessPathNullnessPropagation.Updates thenUpdates,
93       AccessPathNullnessPropagation.Updates elseUpdates,
94       AccessPathNullnessPropagation.Updates bothUpdates) {
95     MethodInvocationTree tree = castToNonNull(node.getTree());
96     Types types = state.getTypes();
97     if (grpcIsMetadataContainsKeyCall(symbol, types)) {
98       // On seeing o.containsKey(k), set AP for o.get(k) to @NonNull
99       Element getter = getGetterForMetadataSubtype(symbol.enclClass(), types);
100       Node base = node.getTarget().getReceiver();
101       // Argument list and types should be already checked by grpcIsMetadataContainsKeyCall
102       Symbol keyArgSymbol = ASTHelpers.getSymbol(tree.getArguments().get(0));
103       if (getter != null
104           && keyArgSymbol instanceof Symbol.VarSymbol
105           && keyArgSymbol.getKind().equals(ElementKind.FIELD)) {
106         Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) keyArgSymbol;
107         String immutableFieldFQN =
108             varSymbol.enclClass().flatName().toString() + "." + varSymbol.flatName().toString();
109         String keyStr = AccessPath.immutableFieldNameAsConstantArgument(immutableFieldFQN);
110         List<String> constantArgs = new ArrayList<>(1);
111         constantArgs.add(keyStr);
112         AccessPath ap =
113             AccessPath.fromBaseMethodAndConstantArgs(base, getter, constantArgs, apContext);
114         if (ap != null) {
115           thenUpdates.set(ap, Nullness.NONNULL);
116         }
117       }
118     }
119     return NullnessHint.UNKNOWN;
120   }
121 
122   @Override
onRegisterImmutableTypes()123   public ImmutableSet<String> onRegisterImmutableTypes() {
124     return ImmutableSet.of(GRPC_METADATA_KEY_TNAME);
125   }
126 
127   @Nullable
getGetterForMetadataSubtype( Symbol.ClassSymbol classSymbol, Types types)128   private Symbol.MethodSymbol getGetterForMetadataSubtype(
129       Symbol.ClassSymbol classSymbol, Types types) {
130     // Is there a better way than iteration?
131     for (Symbol elem : getEnclosedElements(classSymbol)) {
132       if (elem.getKind().equals(ElementKind.METHOD)) {
133         Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) elem;
134         if (grpcIsMetadataGetCall(methodSymbol, types)) {
135           return methodSymbol;
136         }
137       }
138     }
139     return null;
140   }
141 
grpcIsMetadataContainsKeyCall(Symbol.MethodSymbol symbol, Types types)142   private boolean grpcIsMetadataContainsKeyCall(Symbol.MethodSymbol symbol, Types types) {
143     // noinspection ConstantConditions
144     return grpcMetadataType.isPresent()
145         && grpcKeyType.isPresent()
146         // Check declaring class type first, as that will short-circuit 99% of cases
147         && types.isSubtype(symbol.owner.type, grpcMetadataType.get())
148         && symbol.getSimpleName().toString().startsWith(GRPC_CONTAINSKEY_MNAME)
149         && symbol.getParameters().length() == 1
150         && types.isSubtype(symbol.getParameters().get(0).type, grpcKeyType.get())
151         && symbol.getReturnType().getKind() == TypeKind.BOOLEAN;
152   }
153 
grpcIsMetadataGetCall(Symbol.MethodSymbol symbol, Types types)154   private boolean grpcIsMetadataGetCall(Symbol.MethodSymbol symbol, Types types) {
155     // noinspection ConstantConditions
156     return grpcMetadataType.isPresent()
157         && grpcKeyType.isPresent()
158         && types.isSubtype(symbol.owner.type, grpcMetadataType.get())
159         && symbol.getSimpleName().toString().startsWith(GRPC_GETTER_MNAME)
160         && symbol.getParameters().length() == 1
161         && types.isSubtype(symbol.getParameters().get(0).type, grpcKeyType.get());
162   }
163 }
164