-
Notifications
You must be signed in to change notification settings - Fork 440
Check a constructor's purity against its class's instance initializers #8113
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
be9c329
e07272b
a31879d
d803259
b041795
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,142 @@ | ||
| import org.checkerframework.dataflow.qual.SideEffectFree; | ||
|
|
||
| // Tests that a constructor's purity is checked against the initializers that run as part of it: | ||
| // instance initializer blocks and instance field initializers. | ||
| public class PurityInitializers { | ||
|
|
||
| static int counter = 0; | ||
|
|
||
| static int bump() { | ||
| return counter++; | ||
| } | ||
|
|
||
| @SideEffectFree | ||
| static int pureValue() { | ||
| return 0; | ||
| } | ||
|
|
||
| // The effects of a field initializer are effects of the constructor. | ||
| static class FieldInitializer { | ||
| // :: error: [purity.not.sideeffectfree.call] | ||
| int x = bump(); | ||
|
|
||
| @SideEffectFree | ||
| FieldInitializer() {} | ||
| } | ||
|
|
||
| // The effects of an instance initializer block are effects of the constructor. | ||
| static class InitializerBlock { | ||
| int x; | ||
|
|
||
| { | ||
| // :: error: [purity.not.sideeffectfree.call] | ||
| bump(); | ||
| } | ||
|
|
||
| @SideEffectFree | ||
| InitializerBlock() {} | ||
| } | ||
|
|
||
| // The initializers run as part of each constructor, but each of their effects is one error, not | ||
| // one error per constructor. | ||
| static class TwoConstructors { | ||
| // :: error: [purity.not.sideeffectfree.call] | ||
| int x = bump(); | ||
|
|
||
| @SideEffectFree | ||
| TwoConstructors() {} | ||
|
|
||
| @SideEffectFree | ||
| TwoConstructors(int i) {} | ||
| } | ||
|
|
||
| // A constructor may assign the fields of its own class, in an initializer as well as in the | ||
| // constructor's body. | ||
| static class AssignOwnField { | ||
| int x; | ||
| int y = 1; | ||
|
|
||
| { | ||
| x = 2; | ||
| } | ||
|
|
||
| @SideEffectFree | ||
| AssignOwnField() { | ||
| y = 3; | ||
| } | ||
| } | ||
|
|
||
| // Pure initializers do not make the constructor impure. | ||
| static class PureInitializer { | ||
| int x = pureValue(); | ||
|
|
||
| { | ||
| x = pureValue(); | ||
| } | ||
|
|
||
| @SideEffectFree | ||
| PureInitializer() {} | ||
| } | ||
|
|
||
| // Static initializers do not run as part of a constructor, so they are not its effects. | ||
| static class StaticInitializer { | ||
| static int x = bump(); | ||
|
|
||
| static { | ||
| bump(); | ||
| } | ||
|
|
||
| @SideEffectFree | ||
| StaticInitializer() {} | ||
| } | ||
|
|
||
| // A constructor that delegates via this(...) does not run the initializers a second time, so | ||
| // their effects are reported only once, for the constructor that does run them. | ||
| static class Delegating { | ||
| // :: error: [purity.not.sideeffectfree.call] | ||
| int x = bump(); | ||
|
|
||
| @SideEffectFree | ||
| Delegating() { | ||
| this(0); | ||
| } | ||
|
|
||
| @SideEffectFree | ||
| Delegating(int i) {} | ||
| } | ||
|
|
||
| // An enum constant is a static field, so it is not an initializer of the enum's constructor. | ||
| enum SomeEnum { | ||
| A(bump()), | ||
| B(1); | ||
|
|
||
| final int x; | ||
|
|
||
| // The error is for the implicit call to the superclass constructor `Enum(String, int)`, which | ||
| // is not annotated; it is unrelated to the enum constants above. | ||
| @SideEffectFree | ||
| // :: error: [purity.not.sideeffectfree.call] | ||
| SomeEnum(int i) { | ||
| x = i; | ||
| } | ||
| } | ||
|
|
||
| // The same holds for a local class. Its initializers run when it is instantiated, so what | ||
| // matters is the class member that encloses them, not the method that encloses the class. | ||
| Object localClass() { | ||
| class Local { | ||
| int x; | ||
|
|
||
| // :: error: [purity.not.sideeffectfree.call] | ||
| int y = bump(); | ||
|
|
||
| { | ||
| x = 1; | ||
| } | ||
|
|
||
| @SideEffectFree | ||
| Local() {} | ||
| } | ||
| return new Local(); | ||
| } | ||
|
Comment on lines
+124
to
+141
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
# Description: Check how purity checking treats anonymous-class constructors.
set -euo pipefail
rg -n -C 6 'isAnonymousConstructor' framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.javaRepository: typetools/checker-framework Length of output: 642 🤖 get_repo_knowledge executed:
Length of output: 2799 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- PurityChecker references ---'
rg -n -C 10 'inConstructorOrInitializer|anonymous|local class|initializer' framework/src/main/java framework/tests/flow/PurityInitializers.java
printf '%s\n' '--- PurityInitializers context ---'
cat -n framework/tests/flow/PurityInitializers.java | sed -n '1,190p'
printf '%s\n' '--- TreeUtils anonymous constructor binding ---'
rg -n -C 8 'isAnonymousConstructor' framework/src/main/java/org/checkerframeworkRepository: typetools/checker-framework Length of output: 50384 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- PurityChecker definition ---'
rg -l 'class PurityChecker|inConstructorOrInitializer' framework/src/main/java | head -20
printf '%s\n' '--- PurityInitializers methods and directives ---'
rg -n -C 5 'localClass|anonymousClass|bump|sideeffectfree|SideEffectFree' framework/tests/flow/PurityInitializers.java
printf '%s\n' '--- exact anonymous-constructor logic ---'
rg -n -C 14 'isAnonymousConstructor' framework/src/main/java/org/checkerframework/common/basetype/BaseTypeVisitor.javaRepository: typetools/checker-framework Length of output: 4916 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- PurityChecker relevant methods ---'
rg -n -C 24 'inConstructorOrInitializer|TreeUtils.isAnonymousClass|isLocalClass|ClassTree' framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java
printf '%s\n' '--- PurityChecker file size and outline ---'
wc -l framework/src/main/java/org/checkerframework/framework/util/PurityChecker.javaRepository: typetools/checker-framework Length of output: 204 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf '%s\n' '--- PurityChecker symbols ---'
rg -n 'constructor|Initializer|class|TreeUtils' framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java | head -100
printf '%s\n' '--- PurityChecker relevant source ---'
cat -n framework/src/main/java/org/checkerframework/framework/util/PurityChecker.java | sed -n '1,260p'Repository: typetools/checker-framework Length of output: 869 🏁 Script executed: #!/bin/bash
set -euo pipefail
rg -n -C 12 'inConstructorOrInitializer|constructorOrInitializer|anonymous class|local class' . -g '*.java' -g '*.md' | head -240Repository: typetools/checker-framework Length of output: 32239 🏁 Script executed: #!/bin/bash
set -euo pipefail
cat -n dataflow/src/main/java/org/checkerframework/dataflow/util/PurityChecker.java | sed -n '428,485p'
printf '%s\n' '--- current test file tail ---'
cat -n framework/tests/flow/PurityInitializers.java | sed -n '118,150p'Repository: typetools/checker-framework Length of output: 3863 Add an anonymous-class initializer test.
🤖 Prompt for AI Agents |
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
Repository: typetools/checker-framework
Length of output: 4315
🏁 Script executed:
Repository: typetools/checker-framework
Length of output: 10167
🤖 get_repo_knowledge executed:
get_repo_knowledge typetools/checker-framework /tmp/coderabbit-repo-knowledge/typetools-checker-framework-d009055f/learningsLength of output: 2799
🏁 Script executed:
Repository: typetools/checker-framework
Length of output: 18917
🏁 Script executed:
Repository: typetools/checker-framework
Length of output: 4848
Exclude static field assignments from constructor or initializer exemptions.
PurityCheckerHelperrecursively scans nested class members.assignmentCheckpermits assignments to fields of the current class wheninConstructorOrInitializer()returnstrue. That method returnstrueat the first enclosingClassTree, including static field initializers and static initializer blocks. A static field assignment in a nested local or anonymous class can therefore bypass the purity check.Restrict the exemption to non-static fields. Remove the Javadoc claim that static initializer code runs during object construction.
🐛 Proposed fix
🤖 Prompt for AI Agents