Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
93 commits
Select commit Hold shift + click to select a range
b3eba82
fix and test
msridhar Nov 5, 2024
bc9e5fb
another fix and test
msridhar Nov 5, 2024
174b262
add TODO
msridhar Nov 5, 2024
c94dc28
check explicit type arguments when comparing generic type param nulla…
haewiful Nov 11, 2024
32b87ab
formatting
haewiful Nov 11, 2024
e1dfa8a
add test case for explicit type arguments
haewiful Nov 11, 2024
be9c657
Merge branch 'master' into check-type-param
haewiful Nov 11, 2024
24ca5a1
apply substitution to method type
msridhar Nov 11, 2024
cc45e43
Merge branch 'master' into check-type-param
msridhar Nov 12, 2024
3f3ebf2
make function that substitutes explicit type arguments
haewiful Nov 12, 2024
7004926
test case
haewiful Nov 18, 2024
aaba869
add test for multiple generics
haewiful Dec 7, 2024
37ef4d1
temp structure of inferred_types cache - need to change map key type
haewiful Dec 7, 2024
c6a6244
infer on assignments draft
haewiful Jan 13, 2025
277e2b6
new test case for inference on assignments
haewiful Jan 13, 2025
e7346d3
update test case
haewiful Jan 13, 2025
bca1d7d
simple cleanup
haewiful Jan 15, 2025
453623a
formatting
haewiful Jan 15, 2025
9375170
Merge remote-tracking branch 'upstream/master' into infer-generics
haewiful Jan 15, 2025
db0b6ce
minor changes
haewiful Jan 15, 2025
35b18cf
move cache into GenericsChecks and make related methods nonstatic
haewiful Jan 15, 2025
0308a5b
Merge branch 'master' into infer-generics
msridhar Jan 26, 2025
f5ece94
formatting
msridhar Jan 26, 2025
f89fef0
fix CI Job failure
haewiful Jan 28, 2025
0c577ae
remove unused code
haewiful Jan 28, 2025
61e4f3e
debugged for testJdk23
haewiful Feb 3, 2025
9f6c91b
fetch and merge
haewiful Feb 3, 2025
8a30816
Merge branch 'master' into infer-generics
haewiful Feb 3, 2025
4f4ca89
recreated :caffeine:compileTestJava error
haewiful Feb 4, 2025
d96769a
update test case
haewiful Feb 4, 2025
b3dda68
add scenarios to test cases
haewiful Feb 5, 2025
5f1e08f
add expected errors for testcase
haewiful Feb 6, 2025
1e9699a
add upper bound checks
haewiful Feb 6, 2025
a2489ea
update NullAway.java
haewiful Feb 6, 2025
ddf2440
formatting
msridhar Feb 7, 2025
24241b5
remove unnecessary parameter
msridhar Feb 7, 2025
fbedd9c
Merge branch 'master' into infer-generics
msridhar Feb 7, 2025
e07c020
update test cases
haewiful Feb 10, 2025
e15754a
version passing all tests
haewiful Feb 10, 2025
d5c4e5f
change according to comments
haewiful Feb 10, 2025
6c4430e
mid status
haewiful Feb 10, 2025
e4051cc
make code more simple
haewiful Feb 10, 2025
82628ca
make pass ./gradlew :nullaway:buildWithNullAway
haewiful Feb 10, 2025
430e855
Merge branch 'master' into infer-generics
haewiful Feb 10, 2025
f8c8d07
remove always true condition
haewiful Feb 10, 2025
6bc1d0c
make method for clearing inferredTypes cache
haewiful Feb 10, 2025
099fae7
Merge branch 'master' into infer-generics
msridhar Feb 13, 2025
29255bb
change based on comments
haewiful Feb 18, 2025
575006e
add visitor for infering types on assignments using method calls
haewiful Feb 21, 2025
2ab7a31
javadoc
haewiful Feb 21, 2025
61d6f95
update according to changed method definition
haewiful Feb 21, 2025
d984de2
Merge branch 'master' into infer-generics
haewiful Feb 21, 2025
f7c4a3f
add test case for return types with arrays
haewiful Feb 21, 2025
461c309
add array type inference to visitor
haewiful Feb 21, 2025
48bb542
clean up
haewiful Feb 22, 2025
5573800
change key of inferredTypes to MethodInvokationTree
haewiful Feb 22, 2025
bbae811
change inferredTypes value type to Map<TypeVariable, Type>
haewiful Feb 22, 2025
3c77d70
format
haewiful Feb 22, 2025
ea785ba
remove filter call
msridhar Feb 22, 2025
2aec29b
simplify
msridhar Feb 22, 2025
014fc93
add failing test
msridhar Feb 22, 2025
b45bbad
make it work for assignments that are not declarations
haewiful Feb 22, 2025
50f73de
simplify
msridhar Feb 22, 2025
cd2ec93
another test
msridhar Feb 22, 2025
7e7290f
use inferred types immediately after inference
haewiful Feb 23, 2025
87ed351
changes based on comments
haewiful Feb 25, 2025
3d6c606
wrote expected error message for the error case
haewiful Feb 25, 2025
b10a877
remove some comments
haewiful Feb 28, 2025
ad44d92
javadoc
haewiful Feb 28, 2025
e996302
formatting
haewiful Mar 3, 2025
174606b
replace uninferred type variables
haewiful Mar 4, 2025
9a910b6
Merge branch 'master' into infer-generics
msridhar Mar 4, 2025
930917a
Merge branch 'master' into infer-generics
msridhar Mar 4, 2025
e083f41
add inferred type substitution to substituteTypeArgsInGenericMethodTy…
haewiful Mar 6, 2025
c36d198
add inference using parameter types and make tests pass
haewiful Mar 6, 2025
3053e4d
remove extra blank line
msridhar Mar 8, 2025
5a0b7aa
remove inference via parameter types
haewiful Mar 10, 2025
bfccfad
Pass around NullAway object to get the right GenericsChecks object in…
msridhar Mar 11, 2025
c2743da
whoops
msridhar Mar 11, 2025
b995c4d
cleanup
msridhar Mar 11, 2025
2053035
more cleanup
msridhar Mar 11, 2025
4b5b76c
remove unnecessary code
msridhar Mar 11, 2025
2e41b53
comments, still need to be addressed
msridhar Mar 11, 2025
433ac92
cleanup and simplification
msridhar Mar 11, 2025
bbc57c4
remove unnecessary bailout code
msridhar Mar 11, 2025
2343dd7
failing test
msridhar Mar 11, 2025
a8c4c33
fix bug
msridhar Mar 11, 2025
bba61a7
move method location
msridhar Mar 11, 2025
e218dd0
rename test case
haewiful Mar 11, 2025
1b57a94
more cleanup
msridhar Mar 11, 2025
3d928a6
Trigger CLA check
msridhar Mar 11, 2025
72dc550
Trigger CLA check
msridhar Mar 11, 2025
fe8702d
Trigger CLA check
msridhar Mar 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions nullaway/src/main/java/com/uber/nullaway/NullAway.java
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,11 @@ public Config getConfig() {
*/
private final Handler handler;

/** Returns the handler being used for this analysis */
public Handler getHandler() {
return handler;
}

/**
* entities relevant to field initialization per class. cached for performance. nulled out in
* {@link #matchClass(ClassTree, VisitorState)}
Expand Down Expand Up @@ -277,6 +282,14 @@ public Config getConfig() {
*/
private final Map<ExpressionTree, Nullness> computedNullnessMap = new LinkedHashMap<>();

/** Logic and state for generics checking */
private final GenericsChecks genericsChecks = new GenericsChecks();

/** Returns the GenericsChecks object for this analysis, used for generics-related checking */
public GenericsChecks getGenericsChecks() {
return genericsChecks;
}

/**
* Error Prone requires us to have an empty constructor for each Plugin, in addition to the
* constructor taking an ErrorProneFlags object. This constructor should not be used anywhere
Expand Down Expand Up @@ -500,7 +513,7 @@ public Description matchAssignment(AssignmentTree tree, VisitorState state) {
}
// generics check
if (lhsType != null && config.isJSpecifyMode()) {
GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state);
genericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state);
}

if (config.isJSpecifyMode() && tree.getVariable() instanceof ArrayAccessTree) {
Expand Down Expand Up @@ -1494,7 +1507,7 @@ public Description matchVariable(VariableTree tree, VisitorState state) {
}
VarSymbol symbol = ASTHelpers.getSymbol(tree);
if (tree.getInitializer() != null && config.isJSpecifyMode()) {
GenericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state);
genericsChecks.checkTypeParameterNullnessForAssignability(tree, this, state);
}
if (!config.isLegacyAnnotationLocation()) {
checkNullableAnnotationPositionInType(
Expand Down Expand Up @@ -1662,6 +1675,7 @@ public Description matchClass(ClassTree tree, VisitorState state) {
class2Entities.clear();
class2ConstructorUninit.clear();
computedNullnessMap.clear();
genericsChecks.clearCache();
EnclosingEnvironmentNullness.instance(state.context).clear();
} else if (classAnnotationIntroducesPartialMarking(classSymbol)) {
// Handle the case where the top-class is unannotated, but there is a @NullMarked annotation
Expand Down Expand Up @@ -1880,13 +1894,13 @@ private Description handleInvocation(
Nullness.paramHasNullableAnnotation(methodSymbol, i, config)
? Nullness.NULLABLE
: ((config.isJSpecifyMode() && tree instanceof MethodInvocationTree)
? GenericsChecks.getGenericParameterNullnessAtInvocation(
? genericsChecks.getGenericParameterNullnessAtInvocation(
i, methodSymbol, (MethodInvocationTree) tree, state, config)
: Nullness.NONNULL);
}
}
if (config.isJSpecifyMode()) {
GenericsChecks.compareGenericTypeParameterNullabilityForCall(
genericsChecks.compareGenericTypeParameterNullabilityForCall(
methodSymbol, tree, actualParams, varArgsMethod, this, state);
if (!methodSymbol.getTypeParameters().isEmpty()) {
GenericsChecks.checkGenericMethodCallTypeArguments(tree, state, this, config, handler);
Expand Down Expand Up @@ -2632,8 +2646,8 @@ private boolean mayBeNullMethodCall(
return true;
}
if (config.isJSpecifyMode()
&& GenericsChecks.getGenericReturnNullnessAtInvocation(
exprSymbol, invocationTree, state, config)
&& genericsChecks
.getGenericReturnNullnessAtInvocation(exprSymbol, invocationTree, state, config)
.equals(Nullness.NULLABLE)) {
return true;
}
Expand All @@ -2652,7 +2666,7 @@ public boolean nullnessFromDataflow(VisitorState state, ExpressionTree expr) {
}

public AccessPathNullnessAnalysis getNullnessAnalysis(VisitorState state) {
return AccessPathNullnessAnalysis.instance(state, nonAnnotatedMethod, config, this.handler);
return AccessPathNullnessAnalysis.instance(state, nonAnnotatedMethod, this);
}

private Description matchDereference(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.sun.source.util.TreePath;
import com.sun.tools.javac.util.Context;
import com.uber.nullaway.Config;
import com.uber.nullaway.NullAway;
import com.uber.nullaway.Nullness;
import com.uber.nullaway.handlers.Handler;
import com.uber.nullaway.handlers.contract.ContractNullnessStoreInitializer;
Expand Down Expand Up @@ -65,10 +66,9 @@ public final class AccessPathNullnessAnalysis {

// Use #instance to instantiate
private AccessPathNullnessAnalysis(
Predicate<MethodInvocationNode> methodReturnsNonNull,
VisitorState state,
Config config,
Handler handler) {
Predicate<MethodInvocationNode> methodReturnsNonNull, VisitorState state, NullAway analysis) {
Config config = analysis.getConfig();
Handler handler = analysis.getHandler();
apContext =
AccessPath.AccessPathContext.builder()
.setImmutableTypes(handler.onRegisterImmutableTypes())
Expand All @@ -79,8 +79,7 @@ private AccessPathNullnessAnalysis(
methodReturnsNonNull,
state,
apContext,
config,
handler,
analysis,
new CoreNullnessStoreInitializer());
this.dataFlow = new DataFlow(config.assertsEnabled(), handler);

Expand All @@ -91,8 +90,7 @@ private AccessPathNullnessAnalysis(
methodReturnsNonNull,
state,
apContext,
config,
handler,
analysis,
new ContractNullnessStoreInitializer());
}
}
Expand All @@ -103,18 +101,15 @@ private AccessPathNullnessAnalysis(
* @param state visitor state for the compilation
* @param methodReturnsNonNull predicate determining whether a method is assumed to return NonNull
* value
* @param config analysis config
* @param analysis instance of NullAway analysis
* @return instance of the analysis
*/
public static AccessPathNullnessAnalysis instance(
VisitorState state,
Predicate<MethodInvocationNode> methodReturnsNonNull,
Config config,
Handler handler) {
VisitorState state, Predicate<MethodInvocationNode> methodReturnsNonNull, NullAway analysis) {
Context context = state.context;
AccessPathNullnessAnalysis instance = context.get(FIELD_NULLNESS_ANALYSIS_KEY);
if (instance == null) {
instance = new AccessPathNullnessAnalysis(methodReturnsNonNull, state, config, handler);
instance = new AccessPathNullnessAnalysis(methodReturnsNonNull, state, analysis);
context.put(FIELD_NULLNESS_ANALYSIS_KEY, instance);
}
return instance;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.sun.tools.javac.code.TypeTag;
import com.uber.nullaway.CodeAnnotationInfo;
import com.uber.nullaway.Config;
import com.uber.nullaway.NullAway;
import com.uber.nullaway.NullabilityUtil;
import com.uber.nullaway.Nullness;
import com.uber.nullaway.generics.GenericsChecks;
Expand Down Expand Up @@ -160,22 +161,24 @@ public class AccessPathNullnessPropagation

private final Handler handler;

private final GenericsChecks genericsChecks;

private final NullnessStoreInitializer nullnessStoreInitializer;

public AccessPathNullnessPropagation(
Nullness defaultAssumption,
Predicate<MethodInvocationNode> methodReturnsNonNull,
VisitorState state,
AccessPath.AccessPathContext apContext,
Config config,
Handler handler,
NullAway analysis,
NullnessStoreInitializer nullnessStoreInitializer) {
this.defaultAssumption = defaultAssumption;
this.methodReturnsNonNull = methodReturnsNonNull;
this.state = state;
this.apContext = apContext;
this.config = config;
this.handler = handler;
this.config = analysis.getConfig();
this.handler = analysis.getHandler();
this.genericsChecks = analysis.getGenericsChecks();
this.nullnessStoreInitializer = nullnessStoreInitializer;
}

Expand Down Expand Up @@ -1086,7 +1089,7 @@ private boolean genericReturnIsNullable(MethodInvocationNode node) {
MethodInvocationTree tree = node.getTree();
if (tree != null) {
Nullness nullness =
GenericsChecks.getGenericReturnNullnessAtInvocation(
genericsChecks.getGenericReturnNullnessAtInvocation(
ASTHelpers.getSymbol(tree), tree, state, config);
return nullness.equals(NULLABLE);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.sun.tools.javac.code.TargetType;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.util.ListBuffer;
import com.uber.nullaway.CodeAnnotationInfo;
import com.uber.nullaway.Config;
import com.uber.nullaway.ErrorBuilder;
Expand All @@ -35,6 +36,7 @@
import com.uber.nullaway.handlers.Handler;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand All @@ -46,8 +48,14 @@
/** Methods for performing checks related to generic types and nullability. */
public final class GenericsChecks {

/** Do not instantiate; all methods should be static */
private GenericsChecks() {}
/**
* Maps a MethodInvocationTree representing a call to a generic method to a substitution for its
* type arguments. The call must not have any explicit type arguments. The substitution is a map
* from type variables for the method to their inferred type arguments (most importantly with
* inferred nullability information).
*/
private final Map<MethodInvocationTree, Map<TypeVariable, Type>>
inferredSubstitutionsForGenericMethodCalls = new LinkedHashMap<>();

/**
* Checks that for an instantiated generic type, {@code @Nullable} types are only used for type
Expand Down Expand Up @@ -413,13 +421,16 @@ private static void reportInvalidOverridingMethodParamTypeError(
* @param analysis the analysis object
* @param state the visitor state
*/
public static void checkTypeParameterNullnessForAssignability(
public void checkTypeParameterNullnessForAssignability(
Tree tree, NullAway analysis, VisitorState state) {
Config config = analysis.getConfig();
if (!config.isJSpecifyMode()) {
return;
}
Type lhsType = getTreeType(tree, config);
if (lhsType == null) {
return;
}
Tree rhsTree;
if (tree instanceof VariableTree) {
VariableTree varTree = (VariableTree) tree;
Expand All @@ -435,14 +446,58 @@ public static void checkTypeParameterNullnessForAssignability(
}
Type rhsType = getTreeType(rhsTree, config);

if (lhsType != null && rhsType != null) {
if (rhsTree instanceof MethodInvocationTree) {
MethodInvocationTree methodInvocationTree = (MethodInvocationTree) rhsTree;
Symbol.MethodSymbol methodSymbol = ASTHelpers.getSymbol(methodInvocationTree);
if (methodSymbol.type instanceof Type.ForAll
&& methodInvocationTree.getTypeArguments().isEmpty()) {
// generic method call with no explicit generic arguments
// update inferred type arguments based on the assignment context
InferSubstitutionViaAssignmentContextVisitor inferVisitor =
new InferSubstitutionViaAssignmentContextVisitor(config);
Type returnType = methodSymbol.getReturnType();
returnType.accept(inferVisitor, lhsType);

Map<TypeVariable, Type> substitution = inferVisitor.getInferredSubstitution();
inferredSubstitutionsForGenericMethodCalls.put(methodInvocationTree, substitution);
if (rhsType != null) {
// update rhsType with inferred substitution
rhsType =
substituteInferredTypesForTypeVariables(
state, methodSymbol.getReturnType(), substitution, config);
}
}
}

if (rhsType != null) {
boolean isAssignmentValid = subtypeParameterNullability(lhsType, rhsType, state, config);
if (!isAssignmentValid) {
reportInvalidAssignmentInstantiationError(tree, lhsType, rhsType, state, analysis);
}
}
}

/**
* Substitutes inferred types for type variables within a type.
*
* @param state The visitor state
* @param targetType The type with type variables on which substitutions will be applied
* @param substitution The cache that maps type variables to its inferred types
* @param config Configuration for the analysis
* @return {@code targetType} with the substitutions applied
*/
private Type substituteInferredTypesForTypeVariables(
VisitorState state, Type targetType, Map<TypeVariable, Type> substitution, Config config) {
ListBuffer<Type> typeVars = new ListBuffer<>();
ListBuffer<Type> inferredTypes = new ListBuffer<>();
for (Map.Entry<TypeVariable, Type> entry : substitution.entrySet()) {
typeVars.append((Type) entry.getKey());
inferredTypes.append(entry.getValue());
}
return TypeSubstitutionUtils.subst(
state.getTypes(), targetType, typeVars.toList(), inferredTypes.toList(), config);
}

/**
* Checks that the nullability of type parameters for a returned expression matches that of the
* type parameters of the enclosing method's return type.
Expand Down Expand Up @@ -613,7 +668,7 @@ public static void checkTypeParameterNullnessForConditionalExpression(
* @param analysis the analysis object
* @param state the visitor state
*/
public static void compareGenericTypeParameterNullabilityForCall(
public void compareGenericTypeParameterNullabilityForCall(
Symbol.MethodSymbol methodSymbol,
Tree tree,
List<? extends ExpressionTree> actualParams,
Expand All @@ -640,7 +695,7 @@ public static void compareGenericTypeParameterNullabilityForCall(
TypeSubstitutionUtils.memberType(state.getTypes(), enclosingType, methodSymbol, config);
}
}
// substitute type arguments for generic methods
// substitute type arguments for generic methods with explicit type arguments
if (tree instanceof MethodInvocationTree && methodSymbol.type instanceof Type.ForAll) {
invokedMethodType =
substituteTypeArgsInGenericMethodType(
Expand Down Expand Up @@ -830,7 +885,7 @@ public static Nullness getGenericMethodReturnTypeNullness(
* @return Nullness of invocation's return type, or {@code NONNULL} if the call does not invoke an
* instance method
*/
public static Nullness getGenericReturnNullnessAtInvocation(
public Nullness getGenericReturnNullnessAtInvocation(
Symbol.MethodSymbol invokedMethodSymbol,
MethodInvocationTree tree,
VisitorState state,
Expand Down Expand Up @@ -883,7 +938,7 @@ private static com.sun.tools.javac.util.List<Type> convertTreesToTypes(
* @param config the NullAway config
* @return the substituted method type for the generic method
*/
private static Type substituteTypeArgsInGenericMethodType(
private Type substituteTypeArgsInGenericMethodType(
MethodInvocationTree methodInvocationTree,
Symbol.MethodSymbol methodSymbol,
VisitorState state,
Expand All @@ -894,6 +949,17 @@ private static Type substituteTypeArgsInGenericMethodType(

Type.ForAll forAllType = (Type.ForAll) methodSymbol.type;
Type.MethodType underlyingMethodType = (Type.MethodType) forAllType.qtype;

// There are no explicit type arguments, so use the inferred types
if (explicitTypeArgs.isEmpty()) {
if (inferredSubstitutionsForGenericMethodCalls.containsKey(methodInvocationTree)) {
return substituteInferredTypesForTypeVariables(
state,
underlyingMethodType,
inferredSubstitutionsForGenericMethodCalls.get(methodInvocationTree),
config);
}
}
return TypeSubstitutionUtils.subst(
state.getTypes(), underlyingMethodType, forAllType.tvars, explicitTypeArgs, config);
}
Expand Down Expand Up @@ -932,7 +998,7 @@ private static Type substituteTypeArgsInGenericMethodType(
* @return Nullness of parameter at {@code paramIndex}, or {@code NONNULL} if the call does not
* invoke an instance method
*/
public static Nullness getGenericParameterNullnessAtInvocation(
public Nullness getGenericParameterNullnessAtInvocation(
int paramIndex,
Symbol.MethodSymbol invokedMethodSymbol,
MethodInvocationTree tree,
Expand All @@ -941,7 +1007,6 @@ public static Nullness getGenericParameterNullnessAtInvocation(
// If generic method invocation
if (!invokedMethodSymbol.getTypeParameters().isEmpty()) {
// Substitute the argument types within the MethodType
// NOTE: if explicitTypeArgs is empty, this is a noop
List<Type> substitutedParamTypes =
substituteTypeArgsInGenericMethodType(tree, invokedMethodSymbol, state, config)
.getParameterTypes();
Expand Down Expand Up @@ -1160,6 +1225,14 @@ public static boolean passingLambdaOrMethodRefWithGenericReturnToUnmarkedCode(
return callingUnannotated;
}

/**
* Clears the cache of inferred substitutions for generic method calls. This should be invoked
* after each CompilationUnit to avoid memory leaks.
*/
public void clearCache() {
inferredSubstitutionsForGenericMethodCalls.clear();
}

public static boolean isNullableAnnotated(Type type, Config config) {
return Nullness.hasNullableAnnotation(type.getAnnotationMirrors().stream(), config);
}
Expand Down
Loading
Loading