diff --git a/SKIP-REQUIRE-JAVADOC b/SKIP-REQUIRE-JAVADOC new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/checker/jtreg/nullness/EISOPissue548/ConservativeClassLiteral.java b/checker/jtreg/nullness/EISOPissue548/ConservativeClassLiteral.java new file mode 100644 index 000000000000..246648a4e53c --- /dev/null +++ b/checker/jtreg/nullness/EISOPissue548/ConservativeClassLiteral.java @@ -0,0 +1,15 @@ +/* + * @test + * @summary Test class literals in CFGs and their type with conservative nullness. + * + * @compile MyEnum.java + * @compile -processor org.checkerframework.checker.nullness.NullnessChecker -AuseConservativeDefaultsForUncheckedCode=bytecode,-source ConservativeClassLiteral.java + */ + +import java.util.EnumSet; + +class ConservativeClassLiteral { + EnumSet none() { + return EnumSet.noneOf(MyEnum.class); + } +} diff --git a/checker/jtreg/nullness/EISOPissue548/MyEnum.java b/checker/jtreg/nullness/EISOPissue548/MyEnum.java new file mode 100644 index 000000000000..c4b60a93a39e --- /dev/null +++ b/checker/jtreg/nullness/EISOPissue548/MyEnum.java @@ -0,0 +1,3 @@ +enum MyEnum { + VALUE; +} diff --git a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java index 59f178a0b4cb..ec5e3169e7ba 100644 --- a/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/initialization/InitializationParentAnnotatedTypeFactory.java @@ -826,7 +826,7 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror type) { @Override public Void visitMemberSelect( MemberSelectTree tree, AnnotatedTypeMirror annotatedTypeMirror) { - if (TreeUtils.isArrayLengthAccess(tree)) { + if (TreeUtils.isArrayLengthAccess(tree) || TreeUtils.isClassLiteral(tree)) { annotatedTypeMirror.replaceAnnotation(INITIALIZED); } return super.visitMemberSelect(tree, annotatedTypeMirror); diff --git a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java index ce8c9764449d..4f02ed657d7c 100644 --- a/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/nullness/NullnessNoInitAnnotatedTypeFactory.java @@ -667,9 +667,10 @@ public Void visitMemberSelect(MemberSelectTree tree, AnnotatedTypeMirror type) { Element elt = TreeUtils.elementFromUse(tree); assert elt != null; - // Make primitive variable @NonNull in case the Initialization Checker + // Make primitive variable and class literal @NonNull in case the Initialization Checker // considers it uninitialized. - if (TypesUtils.isPrimitive(type.getUnderlyingType())) { + if (TypesUtils.isPrimitive(type.getUnderlyingType()) + || TreeUtils.isClassLiteral(tree)) { type.replaceAnnotation(NONNULL); } diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java index a1be67c12675..805f0e4258b5 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/builder/CFGTranslationPhaseOne.java @@ -78,6 +78,7 @@ import org.checkerframework.dataflow.cfg.node.CatchMarkerNode; import org.checkerframework.dataflow.cfg.node.CharacterLiteralNode; import org.checkerframework.dataflow.cfg.node.ClassDeclarationNode; +import org.checkerframework.dataflow.cfg.node.ClassLiteralNode; import org.checkerframework.dataflow.cfg.node.ClassNameNode; import org.checkerframework.dataflow.cfg.node.ConditionalAndNode; import org.checkerframework.dataflow.cfg.node.ConditionalNotNode; @@ -3714,6 +3715,12 @@ public Node visitReturn(ReturnTree tree, Void p) { public Node visitMemberSelect(MemberSelectTree tree, Void p) { Node expr = scan(tree.getExpression(), p); if (!TreeUtils.isFieldAccess(tree)) { + if (TreeUtils.isClassLiteral(tree)) { + Node result = new ClassLiteralNode(tree, expr); + extendWithNode(result); + return result; + } + // Could be a selector of a class or package Element element = TreeUtils.elementFromUse(tree); if (ElementUtils.isTypeElement(element)) { diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java index 59964bb862e9..5210d4209e65 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/AbstractNodeVisitor.java @@ -463,6 +463,11 @@ public R visitPrimitiveType(PrimitiveTypeNode n, P p) { return visitNode(n, p); } + @Override + public R visitClassLiteral(ClassLiteralNode n, P p) { + return visitNode(n, p); + } + @Override public R visitClassName(ClassNameNode n, P p) { return visitNode(n, p); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassLiteralNode.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassLiteralNode.java new file mode 100644 index 000000000000..0a938f364b0b --- /dev/null +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/ClassLiteralNode.java @@ -0,0 +1,80 @@ +package org.checkerframework.dataflow.cfg.node; + +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; + +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.dataflow.qual.SideEffectFree; +import org.checkerframework.javacutil.TreeUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.Objects; + +/** A node for a class literal. For example: {@code String.class}. */ +public class ClassLiteralNode extends Node { + /** The tree for the class literal. */ + protected final MemberSelectTree tree; + + /** The class name of the class literal. */ + protected final Node className; + + /** + * Create a new ClassLiteralNode. + * + * @param tree the class literal + * @param className the class name for the class literal + */ + public ClassLiteralNode(MemberSelectTree tree, Node className) { + super(TreeUtils.typeOf(tree)); + this.tree = tree; + this.className = className; + } + + @Override + public @Nullable Tree getTree() { + return tree; + } + + /** + * Get the class name of the class literal. + * + * @return the class name of the class literal + */ + public Node getClassName() { + return className; + } + + @Override + public R accept(NodeVisitor visitor, P p) { + return visitor.visitClassLiteral(this, p); + } + + @Override + public String toString() { + return tree.toString(); + } + + @Override + public boolean equals(@Nullable Object obj) { + if (this == obj) { + return true; + } + if (!(obj instanceof ClassLiteralNode)) { + return false; + } + ClassLiteralNode other = (ClassLiteralNode) obj; + return getClassName().equals(other.getClassName()); + } + + @Override + public int hashCode() { + return Objects.hash(getClassName()); + } + + @Override + @SideEffectFree + public Collection getOperands() { + return Collections.singleton(className); + } +} diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java index 239b18bb6416..33f4bb29450d 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/cfg/node/NodeVisitor.java @@ -148,6 +148,8 @@ public interface NodeVisitor { R visitPrimitiveType(PrimitiveTypeNode n, P p); + R visitClassLiteral(ClassLiteralNode n, P p); + R visitClassName(ClassNameNode n, P p); R visitPackageName(PackageNameNode n, P p); diff --git a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java index 5fa21a3c5371..926d904be9db 100644 --- a/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java +++ b/dataflow/src/main/java/org/checkerframework/dataflow/expression/JavaExpression.java @@ -22,6 +22,7 @@ import org.checkerframework.dataflow.cfg.node.ArrayAccessNode; import org.checkerframework.dataflow.cfg.node.ArrayCreationNode; import org.checkerframework.dataflow.cfg.node.BinaryOperationNode; +import org.checkerframework.dataflow.cfg.node.ClassLiteralNode; import org.checkerframework.dataflow.cfg.node.ClassNameNode; import org.checkerframework.dataflow.cfg.node.FieldAccessNode; import org.checkerframework.dataflow.cfg.node.LocalVariableNode; @@ -361,7 +362,7 @@ public static JavaExpression fromNodeFieldAccess(FieldAccessNode node) { // access. return new ThisReference(receiverNode.getType()); } else if (fieldName.equals("class")) { - // The CFG represents "className.class" as a FieldAccessNode; bit it is a class literal. + // The CFG represents "className.class" as a FieldAccessNode; but it is a class literal. return new ClassName(receiverNode.getType()); } JavaExpression receiver; @@ -398,6 +399,9 @@ public static JavaExpression fromNode(Node receiverNode) { JavaExpression result = null; if (receiverNode instanceof FieldAccessNode) { result = fromNodeFieldAccess((FieldAccessNode) receiverNode); + } else if (receiverNode instanceof ClassLiteralNode) { + ClassLiteralNode cl = (ClassLiteralNode) receiverNode; + result = new ClassName(cl.getClassName().getType()); } else if (receiverNode instanceof ThisNode) { result = new ThisReference(receiverNode.getType()); } else if (receiverNode instanceof SuperNode) { @@ -680,7 +684,7 @@ private static JavaExpression fromMemberSelect(MemberSelectTree memberSelectTree return new ClassName(expressionType); } if (TreeUtils.isExplicitThisDereference(memberSelectTree)) { - // the identifier is "class" + // the identifier is "this" return new ThisReference(expressionType); } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 2e0e8f0991e3..21eb057e1eff 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#433, eisop#548, eisop#792, eisop#863, eisop#1801. Version 3.49.5-eisop1 (April 26, 2026) diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java index b568a940e6a5..25d39a807413 100644 --- a/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java +++ b/javacutil/src/main/java/org/checkerframework/javacutil/TreeUtils.java @@ -1533,7 +1533,7 @@ public static boolean isClassLiteral(Tree tree) { * obj . f * * - * This method currently also returns true for class literals and qualified this. + * This method currently also returns true for qualified this. * * @param tree a tree that might be a field access * @return true iff if tree is a field access expression (implicit or explicit) @@ -1550,16 +1550,19 @@ public static boolean isFieldAccess(Tree tree) { * obj . f * * - * This method currently also returns a non-null value for class literals and qualified this. + * This method currently also returns a non-null value for qualified this. * * @param tree a tree that might be a field access * @return the element if tree is a field access expression (implicit or explicit); null * otherwise */ - // TODO: fix value for class literals and qualified this, which are not field accesses. + // TODO: fix value for qualified this, which is not a field access. public static @Nullable VariableElement asFieldAccess(Tree tree) { + if (isClassLiteral(tree)) { + return null; + } if (tree instanceof MemberSelectTree) { - // explicit member access (or a class literal or a qualified this) + // explicit member access (or a qualified this) MemberSelectTree memberSelect = (MemberSelectTree) tree; assert isUseOfElement(memberSelect) : "@AssumeAssertion(nullness): tree kind"; Element el = TreeUtils.elementFromUse(memberSelect); @@ -1600,7 +1603,7 @@ public static boolean isFieldAccess(Tree tree) { /** * Compute the name of the field that the field access {@code tree} accesses. Requires {@code * tree} to be a field access, as determined by {@code isFieldAccess} (which currently also - * returns true for class literals and qualified this). + * returns true for qualified this). * * @param tree a field access tree * @return the name of the field accessed by {@code tree}