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