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