From 5d3c189c68aedcbf0c2fa96cff915a0ea09961e9 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Sun, 8 Sep 2024 23:39:28 -0400 Subject: [PATCH 1/7] Invoke viewpoint adapter at correct place --- .../framework/type/AnnotatedTypeFactory.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index 530b178c177d..d570512e67c7 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -2958,13 +2958,12 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { // AnnotatedTypes.asMemberOf handles vararg type properly, so we do not need to compute // vararg type again. con.computeVarargType(); + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); + } con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con); } - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); - } - Map typeParamToTypeArg = AnnotatedTypes.findTypeArguments(processingEnv, this, tree, ctor, con); List typeargs; From 8228faadfaf7b792bde84a6582833e849e317453 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Mon, 30 Sep 2024 16:41:06 -0400 Subject: [PATCH 2/7] Invoke viewpointAdaptConstructor also for super constructor --- .../framework/type/AnnotatedTypeFactory.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index d570512e67c7..048f5fedc104 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -2925,9 +2925,12 @@ public ParameterizedExecutableType constructorFromUse(NewClassTree tree) { AnnotatedExecutableType superCon = getAnnotatedType(TreeUtils.getSuperConstructor(tree)); constructorFromUsePreSubstitution(tree, superCon); - // no viewpoint adaptation needed for super invocation superCon = AnnotatedTypes.asMemberOf(types, this, type, superCon.getElement(), superCon); + // Adapt the result from super constructor as it will be used in anonymous constructor. + if (viewpointAdapter != null) { + viewpointAdapter.viewpointAdaptConstructor(type, ctor, superCon); + } con.computeVarargType(superCon); if (superCon.getParameterTypes().size() == con.getParameterTypes().size()) { con.setParameterTypes(superCon.getParameterTypes()); From c2b1bf103fbaa6bcc2e6312142979d050c238a3d Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Sat, 20 Jun 2026 10:04:13 -0400 Subject: [PATCH 3/7] Fix viewpoint-adapted varargs type --- docs/CHANGELOG.md | 2 +- .../type/AbstractViewpointAdapter.java | 2 ++ .../framework/type/AnnotatedTypeFactory.java | 3 -- framework/tests/viewpointtest/Issue386.java | 29 +++++++++++++++++++ .../viewpointtest/VarargsConstructor.java | 8 +++-- 5 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 framework/tests/viewpointtest/Issue386.java diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2e0e8f0991e3..51c03037fd2e 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -231,7 +231,7 @@ median of four warm-daemon reps per side). **Closed issues:** -eisop#433, eisop#792, eisop#863, eisop#1801. +eisop#386, eisop#433, eisop#792, eisop#863, eisop#1801. Version 3.49.5-eisop1 (April 26, 2026) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index 253f6d1ce7a0..19afae46990a 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -135,6 +135,7 @@ public void viewpointAdaptConstructor( constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes()); constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables()); constructorType.setReturnType(unsubstitutedConstructorType.getReturnType()); + constructorType.computeVarargType(); } @Override @@ -189,6 +190,7 @@ public void viewpointAdaptMethod( methodType.setReceiverType(unsubstitutedMethodType.getReceiverType()); methodType.setParameterTypes(unsubstitutedMethodType.getParameterTypes()); methodType.setTypeVariables(unsubstitutedMethodType.getTypeVariables()); + methodType.computeVarargType(); } /** diff --git a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java index bd6ae9d64004..0609a387345d 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AnnotatedTypeFactory.java @@ -3494,9 +3494,6 @@ protected ParameterizedExecutableType constructorFromUse( // AnnotatedTypes.asMemberOf handles vararg type properly, so we do not need to compute // vararg type again. con.computeVarargType(); - if (viewpointAdapter != null) { - viewpointAdapter.viewpointAdaptConstructor(type, ctor, con); - } con = AnnotatedTypes.asMemberOf(types, this, type, ctor, con); } diff --git a/framework/tests/viewpointtest/Issue386.java b/framework/tests/viewpointtest/Issue386.java new file mode 100644 index 000000000000..b8be2a78b343 --- /dev/null +++ b/framework/tests/viewpointtest/Issue386.java @@ -0,0 +1,29 @@ +// Test case for EISOP issue #386: +// https://github.com/eisop/checker-framework/issues/386 +import viewpointtest.quals.*; + +public class Issue386 { + public class Inner { + Inner() {} + + Inner(@ReceiverDependentQual Object... args) {} + } + + public class MethodReceiver { + void method(@ReceiverDependentQual Object... args) {} + } + + @SuppressWarnings("cast.unsafe.constructor.invocation") + public void constructorTest(@A Object aObj, @A Object otherAObj, @B Object bObj) { + this.new @A Inner(aObj, otherAObj); + // :: error: (argument.type.incompatible) + this.new @A Inner(aObj, bObj); + } + + public void methodTest( + @A MethodReceiver receiver, @A Object aObj, @A Object otherAObj, @B Object bObj) { + receiver.method(aObj, otherAObj); + // :: error: (argument.type.incompatible) + receiver.method(aObj, bObj); + } +} diff --git a/framework/tests/viewpointtest/VarargsConstructor.java b/framework/tests/viewpointtest/VarargsConstructor.java index 5382f7559495..40de6469bf98 100644 --- a/framework/tests/viewpointtest/VarargsConstructor.java +++ b/framework/tests/viewpointtest/VarargsConstructor.java @@ -17,7 +17,8 @@ void foo() { void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { @A Object a = new @A VarargsConstructor(aObj); @B Object b = new @B VarargsConstructor(bObj); - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) :: error: + // (varargs.type.incompatible) @Top Object top = new @Top VarargsConstructor(topObj); // :: error: (argument.type.incompatible) new @A VarargsConstructor(bObj); @@ -30,7 +31,7 @@ class Inner { @ReceiverDependentQual Inner(@ReceiverDependentQual Object... args) {} void foo() { - // :: error: (new.class.type.invalid) + // :: error: (new.class.type.invalid) :: error: (varargs.type.incompatible) Inner a = new Inner(); // :: warning: (cast.unsafe.constructor.invocation) Inner b = new @A Inner(new @A Object()); @@ -42,7 +43,8 @@ void foo() { void invokeConstructor(@A Object aObj, @B Object bObj, @Top Object topObj) { @A Object a = new @A Inner(aObj); @B Object b = new @B Inner(bObj); - // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) + // :: error: (argument.type.incompatible) :: error: (new.class.type.invalid) :: error: + // (varargs.type.incompatible) @Top Object top = new @Top Inner(topObj); // :: error: (argument.type.incompatible) new @A Inner(bObj); From bffeb0d4f34637775760133d687271a873181f6a Mon Sep 17 00:00:00 2001 From: Werner Dietl Date: Thu, 16 Jul 2026 21:55:37 -0400 Subject: [PATCH 4/7] Wording tweaks --- .../framework/type/AbstractViewpointAdapter.java | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java index 19afae46990a..4c71258b62f9 100644 --- a/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java +++ b/framework/src/main/java/org/checkerframework/framework/type/AbstractViewpointAdapter.java @@ -106,10 +106,11 @@ public void viewpointAdaptConstructor( AnnotatedTypeMirror receiverType, ExecutableElement constructorElt, AnnotatedExecutableType constructorType) { - // constructorType's typevar are not substituted when calling viewpointAdaptConstructor + // constructorType's type variables are not substituted when calling + // viewpointAdaptConstructor. AnnotatedExecutableType unsubstitutedConstructorType = constructorType.deepCopy(); - // For constructors, we adapt parameter types, return type and type parameters + // For constructors, we adapt parameter types, return type, and type parameters. List parameterTypes = unsubstitutedConstructorType.getParameterTypes(); List typeVariables = unsubstitutedConstructorType.getTypeVariables(); AnnotatedTypeMirror constructorReturn = unsubstitutedConstructorType.getReturnType(); @@ -135,6 +136,7 @@ public void viewpointAdaptConstructor( constructorType.setParameterTypes(unsubstitutedConstructorType.getParameterTypes()); constructorType.setTypeVariables(unsubstitutedConstructorType.getTypeVariables()); constructorType.setReturnType(unsubstitutedConstructorType.getReturnType()); + // Recompute the vararg type to ensure it points to the newly updated parameter list. constructorType.computeVarargType(); } @@ -147,10 +149,10 @@ public void viewpointAdaptMethod( return; } - // methodType's typevar are not substituted when calling viewpointAdaptMethod + // methodType's type variables are not substituted when calling viewpointAdaptMethod. AnnotatedExecutableType unsubstitutedMethodType = methodType.deepCopy(); - // For methods, we additionally adapt method receiver compared to constructors + // For methods, we additionally adapt the method receiver (compared to constructors). List parameterTypes = unsubstitutedMethodType.getParameterTypes(); List typeVariables = unsubstitutedMethodType.getTypeVariables(); AnnotatedTypeMirror returnType = unsubstitutedMethodType.getReturnType(); @@ -184,12 +186,14 @@ public void viewpointAdaptMethod( AnnotatedTypeCopierWithReplacement.replace( unsubstitutedMethodType, mappings); - // Because we can't viewpoint adapt asMemberOf result, we adapt the declared method first, - // and sets the corresponding parts to asMemberOf result + // Because we cannot viewpoint-adapt the result of asMemberOf, we adapt the declared method + // first, and set the corresponding parts on the method type before passing it to + // asMemberOf. methodType.setReturnType(unsubstitutedMethodType.getReturnType()); methodType.setReceiverType(unsubstitutedMethodType.getReceiverType()); methodType.setParameterTypes(unsubstitutedMethodType.getParameterTypes()); methodType.setTypeVariables(unsubstitutedMethodType.getTypeVariables()); + // Recompute the vararg type to ensure it points to the newly updated parameter list. methodType.computeVarargType(); } From 465269bb5679ca1e0d1bbbb12d596be64d9e9581 Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 24 Jul 2026 12:28:27 -0400 Subject: [PATCH 5/7] Allow empty varargs calls without issuing varargs.type.incompatible --- .../common/basetype/BaseTypeVisitor.java | 5 +++ .../viewpointtest/VarargsConstructor.java | 2 +- .../checkerframework/javacutil/TreeUtils.java | 40 +++++++++++++++++-- 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index 3d060f2e9061..b1e738500c76 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -2555,6 +2555,11 @@ protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) { return; } + if (TreeUtils.isCallToVarargsMethodWithZeroVarargsActuals(tree)) { + // An empty varargs call creates an empty array, so no elements are passed. + return; + } + // This is the varags type, an array. AnnotatedArrayType lastParamAnnotatedType = invokedMethod.getVarargType(); diff --git a/framework/tests/viewpointtest/VarargsConstructor.java b/framework/tests/viewpointtest/VarargsConstructor.java index 40de6469bf98..0fecd9969e76 100644 --- a/framework/tests/viewpointtest/VarargsConstructor.java +++ b/framework/tests/viewpointtest/VarargsConstructor.java @@ -31,7 +31,7 @@ class Inner { @ReceiverDependentQual Inner(@ReceiverDependentQual Object... args) {} void foo() { - // :: error: (new.class.type.invalid) :: error: (varargs.type.incompatible) + // :: error: (new.class.type.invalid) Inner a = new Inner(); // :: warning: (cast.unsafe.constructor.invocation) Inner b = new @A Inner(new @A Object()); diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index 7553e2df1aab..c311c8848502 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -3018,24 +3018,56 @@ public static boolean isSignaturePolymorphic(final MethodInvocationTree invok) { return (symbol.flags() & Flags.SIGNATURE_POLYMORPHIC) != 0; } + /** + * Returns true if the given invocation tree is an invocation of a method or constructor with a + * vararg parameter, and the invocation has zero vararg actuals. + * + * @param tree a method invocation or constructor invocation tree + * @return true if the given invocation has zero vararg actuals + */ + public static boolean isCallToVarargsMethodWithZeroVarargsActuals(Tree tree) { + switch (tree.getKind()) { + case METHOD_INVOCATION: + return isCallToVarargsMethodWithZeroVarargsActuals((MethodInvocationTree) tree); + case NEW_CLASS: + return isCallToVarargsMethodWithZeroVarargsActuals((NewClassTree) tree); + default: + return false; + } + } + /** * Returns true if the given method invocation is an invocation of a method with a vararg * parameter, and the invocation has zero vararg actuals. * * @param invok the method invocation * @return true if the given method invocation is an invocation of a method with a vararg - * parameter, and the invocation has with zero vararg actuals + * parameter, and the invocation has zero vararg actuals */ public static boolean isCallToVarargsMethodWithZeroVarargsActuals(MethodInvocationTree invok) { - if (!TreeUtils.isVarArgs(invok)) { + if (!isVarargsCall(invok)) { return false; } int numParams = elementFromUse(invok).getParameters().size(); - // The comparison of the number of arguments to the number of formals (minus one) checks - // whether there are no varargs actuals. return invok.getArguments().size() == numParams - 1; } + /** + * Returns true if the given constructor invocation is an invocation of a constructor with a + * vararg parameter, and the invocation has zero vararg actuals. + * + * @param newClassTree the constructor invocation + * @return true if the given constructor invocation is an invocation of a constructor with a + * vararg parameter, and the invocation has zero vararg actuals + */ + public static boolean isCallToVarargsMethodWithZeroVarargsActuals(NewClassTree newClassTree) { + if (!isVarargsCall(newClassTree)) { + return false; + } + int numParams = elementFromUse(newClassTree).getParameters().size(); + return newClassTree.getArguments().size() == numParams - 1; + } + /** * Returns true if the given constructor invocation is a varargs invocation. * From be684d3a1c761514136f83983dbbee0c19cf792c Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 24 Jul 2026 15:06:10 -0400 Subject: [PATCH 6/7] Extract overridable shouldCheckVarargs from checkVarargs and override in ValueVisitor --- .../common/basetype/BaseTypeVisitor.java | 20 +++++++++++++++++-- .../common/value/ValueVisitor.java | 8 ++++++++ 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java index b1e738500c76..9b1f2872d17f 100644 --- a/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.java @@ -2555,8 +2555,7 @@ protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) { return; } - if (TreeUtils.isCallToVarargsMethodWithZeroVarargsActuals(tree)) { - // An empty varargs call creates an empty array, so no elements are passed. + if (!shouldCheckVarargs(tree)) { return; } @@ -2585,6 +2584,23 @@ protected void checkVarargs(AnnotatedExecutableType invokedMethod, Tree tree) { lastParamAnnotatedType, wrappedVarargsType, tree, "varargs.type.incompatible"); } + /** + * Returns true if the varargs array created for the given varargs invocation should be checked + * against the formal varargs parameter type. + * + *

The default implementation returns false if zero varargs actual arguments were passed at + * the call site, because no user arguments were passed to populate the varargs array. Checkers + * that enforce container array annotations on implicit empty arrays (such as the Value Checker + * enforcing {@code @MinLen}) may override this method to return true even when zero varargs + * actual arguments were passed. + * + * @param tree the invocation tree + * @return true if the varargs array should be checked + */ + protected boolean shouldCheckVarargs(Tree tree) { + return !TreeUtils.isCallToVarargsMethodWithZeroVarargsActuals(tree); + } + /** * Checks that all the given {@code preconditions} hold true immediately prior to the method * invocation or variable access at {@code tree}. diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java index 17ef2f6359a1..ea6ec47814a8 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java @@ -48,6 +48,14 @@ public ValueVisitor(BaseTypeChecker checker) { super(checker); } + @Override + protected boolean shouldCheckVarargs(Tree tree) { + // The Value Checker enforces container array annotations (such as @MinLen) on implicit + // empty + // arrays created for zero-argument varargs calls. + return true; + } + /** * ValueVisitor overrides this method so that it does not have to check variables annotated with * the {@link IntRangeFromPositive} annotation, the {@link IntRangeFromNonNegative} annotation, From f0732431475b8436aa98aaa606f69f0a59897cdd Mon Sep 17 00:00:00 2001 From: Aosen Xiong Date: Fri, 24 Jul 2026 22:44:42 -0400 Subject: [PATCH 7/7] Clean up comment line formatting in ValueVisitor.java --- .../java/org/checkerframework/common/value/ValueVisitor.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java index ea6ec47814a8..69b20ffd9710 100644 --- a/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java +++ b/framework/src/main/java/org/checkerframework/common/value/ValueVisitor.java @@ -50,9 +50,8 @@ public ValueVisitor(BaseTypeChecker checker) { @Override protected boolean shouldCheckVarargs(Tree tree) { - // The Value Checker enforces container array annotations (such as @MinLen) on implicit - // empty - // arrays created for zero-argument varargs calls. + // The Value Checker enforces container array annotations (such as @MinLen) on + // implicit empty arrays created for zero-argument varargs calls. return true; }