• 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 com.google.common.base.Preconditions;
25 import com.google.common.collect.ImmutableSet;
26 import com.google.errorprone.VisitorState;
27 import com.google.errorprone.suppliers.Supplier;
28 import com.google.errorprone.suppliers.Suppliers;
29 import com.google.errorprone.util.ASTHelpers;
30 import com.sun.source.tree.ClassTree;
31 import com.sun.source.tree.MethodInvocationTree;
32 import com.sun.tools.javac.code.Symbol;
33 import com.sun.tools.javac.code.Type;
34 import com.sun.tools.javac.code.Types;
35 import com.sun.tools.javac.util.Context;
36 import com.uber.nullaway.NullAway;
37 import com.uber.nullaway.Nullness;
38 import com.uber.nullaway.dataflow.AccessPath;
39 import com.uber.nullaway.dataflow.AccessPathNullnessPropagation;
40 import java.util.ArrayList;
41 import java.util.List;
42 import java.util.Optional;
43 import javax.annotation.Nullable;
44 import javax.lang.model.element.Element;
45 import javax.lang.model.element.ElementKind;
46 import javax.lang.model.type.TypeKind;
47 import org.checkerframework.nullaway.dataflow.cfg.node.MethodInvocationNode;
48 import org.checkerframework.nullaway.dataflow.cfg.node.Node;
49 
50 public class GrpcHandler extends BaseNoOpHandler {
51   private static final String GRPC_METADATA_TNAME = "io.grpc.Metadata";
52   private static final String GRPC_METADATA_KEY_TNAME = "io.grpc.Metadata.Key";
53   private static final String GRPC_CONTAINSKEY_MNAME = "containsKey";
54   private static final String GRPC_GETTER_MNAME = "get";
55 
56   private static final Supplier<Type> GRPC_METADATA_TYPE_SUPPLIER =
57       Suppliers.typeFromString(GRPC_METADATA_TNAME);
58 
59   private static final Supplier<Type> GRPC_METADATA_KEY_TYPE_SUPPLIER =
60       Suppliers.typeFromString(GRPC_METADATA_KEY_TNAME);
61 
62   @Nullable private Optional<Type> grpcMetadataType;
63   @Nullable private Optional<Type> grpcKeyType;
64 
65   @Override
onMatchTopLevelClass( NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol)66   public void onMatchTopLevelClass(
67       NullAway analysis, ClassTree tree, VisitorState state, Symbol.ClassSymbol classSymbol) {
68     if (grpcMetadataType == null || grpcKeyType == null) {
69       grpcMetadataType =
70           Optional.ofNullable(GRPC_METADATA_TYPE_SUPPLIER.get(state))
71               .map(state.getTypes()::erasure);
72       grpcKeyType =
73           Optional.ofNullable(GRPC_METADATA_KEY_TYPE_SUPPLIER.get(state))
74               .map(state.getTypes()::erasure);
75     }
76   }
77 
78   @Override
onDataflowVisitMethodInvocation( MethodInvocationNode node, Types types, Context context, AccessPath.AccessPathContext apContext, AccessPathNullnessPropagation.SubNodeValues inputs, AccessPathNullnessPropagation.Updates thenUpdates, AccessPathNullnessPropagation.Updates elseUpdates, AccessPathNullnessPropagation.Updates bothUpdates)79   public NullnessHint onDataflowVisitMethodInvocation(
80       MethodInvocationNode node,
81       Types types,
82       Context context,
83       AccessPath.AccessPathContext apContext,
84       AccessPathNullnessPropagation.SubNodeValues inputs,
85       AccessPathNullnessPropagation.Updates thenUpdates,
86       AccessPathNullnessPropagation.Updates elseUpdates,
87       AccessPathNullnessPropagation.Updates bothUpdates) {
88     MethodInvocationTree tree = node.getTree();
89     Symbol.MethodSymbol symbol = ASTHelpers.getSymbol(tree);
90     if (grpcIsMetadataContainsKeyCall(symbol, types)) {
91       // On seeing o.containsKey(k), set AP for o.get(k) to @NonNull
92       Element getter = getGetterForMetadataSubtype(symbol.enclClass(), types);
93       Node base = node.getTarget().getReceiver();
94       // Argument list and types should be already checked by grpcIsMetadataContainsKeyCall
95       Symbol keyArgSymbol = ASTHelpers.getSymbol(tree.getArguments().get(0));
96       if (getter != null && keyArgSymbol.getKind().equals(ElementKind.FIELD)) {
97         Symbol.VarSymbol varSymbol = (Symbol.VarSymbol) keyArgSymbol;
98         String immutableFieldFQN =
99             varSymbol.enclClass().flatName().toString() + "." + varSymbol.flatName().toString();
100         String keyStr = AccessPath.immutableFieldNameAsConstantArgument(immutableFieldFQN);
101         List<String> constantArgs = new ArrayList<>(1);
102         constantArgs.add(keyStr);
103         AccessPath ap =
104             AccessPath.fromBaseMethodAndConstantArgs(base, getter, constantArgs, apContext);
105         if (ap != null) {
106           thenUpdates.set(ap, Nullness.NONNULL);
107         }
108       }
109     }
110     return NullnessHint.UNKNOWN;
111   }
112 
113   @Override
onRegisterImmutableTypes()114   public ImmutableSet<String> onRegisterImmutableTypes() {
115     return ImmutableSet.of(GRPC_METADATA_KEY_TNAME);
116   }
117 
118   @Nullable
getGetterForMetadataSubtype( Symbol.ClassSymbol classSymbol, Types types)119   private Symbol.MethodSymbol getGetterForMetadataSubtype(
120       Symbol.ClassSymbol classSymbol, Types types) {
121     // Is there a better way than iteration?
122     for (Symbol elem : classSymbol.getEnclosedElements()) {
123       if (elem.getKind().equals(ElementKind.METHOD)) {
124         Symbol.MethodSymbol methodSymbol = (Symbol.MethodSymbol) elem;
125         if (grpcIsMetadataGetCall(methodSymbol, types)) {
126           return methodSymbol;
127         }
128       }
129     }
130     return null;
131   }
132 
grpcIsMetadataContainsKeyCall(Symbol.MethodSymbol symbol, Types types)133   private boolean grpcIsMetadataContainsKeyCall(Symbol.MethodSymbol symbol, Types types) {
134     Preconditions.checkNotNull(grpcMetadataType);
135     Preconditions.checkNotNull(grpcKeyType);
136     // noinspection ConstantConditions
137     return grpcMetadataType.isPresent()
138         && grpcKeyType.isPresent()
139         // Check declaring class type first, as that will short-circuit 99% of cases
140         && types.isSubtype(symbol.owner.type, grpcMetadataType.get())
141         && symbol.getSimpleName().toString().startsWith(GRPC_CONTAINSKEY_MNAME)
142         && symbol.getParameters().length() == 1
143         && types.isSubtype(symbol.getParameters().get(0).type, grpcKeyType.get())
144         && symbol.getReturnType().getKind() == TypeKind.BOOLEAN;
145   }
146 
grpcIsMetadataGetCall(Symbol.MethodSymbol symbol, Types types)147   private boolean grpcIsMetadataGetCall(Symbol.MethodSymbol symbol, Types types) {
148     Preconditions.checkNotNull(grpcMetadataType);
149     Preconditions.checkNotNull(grpcKeyType);
150     // noinspection ConstantConditions
151     return grpcMetadataType.isPresent()
152         && grpcKeyType.isPresent()
153         && types.isSubtype(symbol.owner.type, grpcMetadataType.get())
154         && symbol.getSimpleName().toString().startsWith(GRPC_GETTER_MNAME)
155         && symbol.getParameters().length() == 1
156         && types.isSubtype(symbol.getParameters().get(0).type, grpcKeyType.get());
157   }
158 }
159