-
Notifications
You must be signed in to change notification settings - Fork 440
Inherited @SideEffectsOnly annotations
#8003
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
Merged
smillst
merged 16 commits into
typetools:master
from
mernst:side-effects-only-2-7-split-4b
Aug 18, 2026
Merged
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
c456d54
Retain type refinements across calls to @SideEffectsOnly methods
mernst 578a6dc
Inherit @SideEffectsOnly from overridden methods
mernst 4e4d05d
Merge ../checker-framework-branch-master into side-effects-only-2-7-s…
mernst fa14b99
Javadoc
mernst 4bb7668
Merge ../checker-framework-branch-master into side-effects-only-2-7-s…
mernst c21c8a4
Merge ../checker-framework-fork-mernst-branch-side-effects-only-2-7-s…
mernst 5e3a322
Improve doc
mernst 98286e9
Note a limitation
mernst a5f4097
Merge ../checker-framework-branch-master into side-effects-only-2-7-s…
mernst bb14727
Merge ../checker-framework-fork-mernst-branch-side-effects-only-2-7-s…
mernst 0a51ae2
Consistent argument order, TODO
mernst 272dde4
Merge ../checker-framework-fork-mernst-branch-side-effects-only-2-7-s…
mernst 347bd72
Merge ../checker-framework-branch-master into side-effects-only-2-7-s…
mernst 0a89298
Merge ../checker-framework-branch-master into side-effects-only-2-7-s…
mernst 7aeb0ee
Merge ../checker-framework-branch-master into side-effects-only-2-7-s…
mernst 1feb054
Merge ../checker-framework-branch-master into side-effects-only-2-7-s…
mernst File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,71 @@ | ||
| // A method that overrides methods in two supertypes inherits the union of their | ||
| // `@SideEffectsOnly` expressions, not just the first supertype's. The `value` element of | ||
| // `@SideEffectsOnly` is significant, unlike that of the other inherited declaration annotations. | ||
|
|
||
| import org.checkerframework.checker.tainting.qual.Tainted; | ||
| import org.checkerframework.checker.tainting.qual.Untainted; | ||
| import org.checkerframework.dataflow.qual.SideEffectsOnly; | ||
| import org.checkerframework.framework.qual.EnsuresQualifier; | ||
|
|
||
| public class SideEffectsOnlyInherit { | ||
|
|
||
| static class Cell { | ||
| @Tainted Object f; | ||
| @Tainted Object g; | ||
| } | ||
|
|
||
| interface I { | ||
| @SideEffectsOnly("#1.f") | ||
| void m(Cell c); | ||
| } | ||
|
|
||
| static class Base { | ||
| @SideEffectsOnly("#1.g") | ||
| public void m(Cell c) {} | ||
| } | ||
|
|
||
| // `C.m` cannot satisfy both supertype specifications; the override errors are suppressed in | ||
| // order to test what `C.m` inherits. Whichever supertype `AnnotatedTypes.overriddenMethods` | ||
| // yields first, `C.m` is treated as side-effecting both `#1.f` and `#1.g`. | ||
| @SuppressWarnings("purity.sideeffectsonly.overriding") | ||
| static class C extends Base implements I { | ||
| @Override | ||
| public void m(Cell c) {} | ||
| } | ||
|
|
||
| @EnsuresQualifier(expression = "#1.f", qualifier = Untainted.class) | ||
| // :: error: contracts.postcondition | ||
| static void makeFUntainted(Cell c) {} | ||
|
|
||
| @EnsuresQualifier(expression = "#1.g", qualifier = Untainted.class) | ||
| // :: error: contracts.postcondition | ||
| static void makeGUntainted(Cell c) {} | ||
|
|
||
| static void testF(C receiver, Cell c) { | ||
| makeFUntainted(c); | ||
| receiver.m(c); | ||
| // :: error: assignment | ||
| @Untainted Object y = c.f; | ||
| } | ||
|
|
||
| static void testG(C receiver, Cell c) { | ||
| makeGUntainted(c); | ||
| receiver.m(c); | ||
| // :: error: assignment | ||
| @Untainted Object y = c.g; | ||
| } | ||
|
|
||
| // A `@SideEffectsOnly` written on the method itself is authoritative: nothing is inherited. | ||
| @SuppressWarnings("purity.sideeffectsonly.overriding") | ||
| static class D extends Base implements I { | ||
| @SideEffectsOnly("#1.f") | ||
| @Override | ||
| public void m(Cell c) {} | ||
| } | ||
|
|
||
| static void testOwnAnnotationWins(D receiver, Cell c) { | ||
| makeGUntainted(c); | ||
| receiver.m(c); | ||
| @Untainted Object y = c.g; | ||
| } | ||
| } | ||
47 changes: 47 additions & 0 deletions
47
checker/tests/sideeffectsonly/SideEffectsOnlyInheritScope.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| // The expressions of an inherited `@SideEffectsOnly` annotation are resolved in the scope of the | ||
| // supertype method that declares them, not in the scope of the method that inherits them. | ||
|
|
||
| import org.checkerframework.checker.tainting.qual.Tainted; | ||
| import org.checkerframework.checker.tainting.qual.Untainted; | ||
| import org.checkerframework.dataflow.qual.SideEffectsOnly; | ||
| import org.checkerframework.framework.qual.EnsuresQualifier; | ||
|
|
||
| public class SideEffectsOnlyInheritScope { | ||
|
|
||
| static class Sup { | ||
| @Tainted Object f; | ||
|
|
||
| @SideEffectsOnly("this.f") | ||
| void m() {} | ||
| } | ||
|
|
||
| static class Sub extends Sup { | ||
| // Shadows `Sup.f`. `Sub.m` inherits a specification about `Sup.f`, not about `Sub.f`. | ||
| @Tainted Object f; | ||
|
|
||
| @Override | ||
| void m() {} | ||
| } | ||
|
|
||
| @EnsuresQualifier(expression = "#1.f", qualifier = Untainted.class) | ||
| // :: error: contracts.postcondition | ||
| static void makeSupFUntainted(Sup s) {} | ||
|
|
||
| @EnsuresQualifier(expression = "#1.f", qualifier = Untainted.class) | ||
| // :: error: contracts.postcondition | ||
| static void makeSubFUntainted(Sub s) {} | ||
|
|
||
| static void testShadowingFieldIsRetained(Sub s) { | ||
| makeSubFUntainted(s); | ||
| s.m(); | ||
| // `Sub.f` is not side-effected, so its refinement is retained. | ||
| @Untainted Object y = s.f; | ||
| } | ||
|
|
||
| static void testShadowedFieldIsDiscarded(Sub s) { | ||
| makeSupFUntainted(s); | ||
| s.m(); | ||
| // :: error: assignment | ||
| @Untainted Object y = ((Sup) s).f; | ||
| } | ||
| } |
85 changes: 85 additions & 0 deletions
85
checker/tests/sideeffectsonly/SideEffectsOnlyOverride.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| // An overriding method must not side-effect more than the overridden method's | ||
| // `@SideEffectsOnly` annotation permits. Otherwise a call whose receiver is statically of the | ||
| // supertype would retain a refinement that the override invalidates. | ||
|
|
||
| import org.checkerframework.checker.tainting.qual.Tainted; | ||
| import org.checkerframework.checker.tainting.qual.Untainted; | ||
| import org.checkerframework.dataflow.qual.SideEffectFree; | ||
| import org.checkerframework.dataflow.qual.SideEffectsOnly; | ||
| import org.checkerframework.framework.qual.EnsuresQualifier; | ||
|
|
||
| public class SideEffectsOnlyOverride { | ||
|
|
||
| static class Cell { | ||
| @Tainted Object g; | ||
| @Tainted Cell inner; | ||
| } | ||
|
|
||
| static class Super { | ||
| @SideEffectsOnly("#1.inner") | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| /** Side-effects exactly what the supertype permits. */ | ||
| static class SubSame extends Super { | ||
| @SideEffectsOnly("#1.inner") | ||
| @Override | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| /** Side-effects less than the supertype permits: `#1.inner.g` is reached through `#1.inner`. */ | ||
| static class SubDeeper extends Super { | ||
| @SideEffectsOnly("#1.inner.g") | ||
| @Override | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| /** Side-effects nothing at all. */ | ||
| static class SubSideEffectFree extends Super { | ||
| @SideEffectFree | ||
| @Override | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| /** Side-effects more than the supertype permits. */ | ||
| static class SubMore extends Super { | ||
| @SideEffectsOnly({"#1.inner", "#1.g"}) | ||
| @Override | ||
| // TODO :: error: purity.sideeffectsonly.overriding | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| /** `#1` is not reached through `#1.inner`, so side-effecting it is more than permitted. */ | ||
| static class SubWhole extends Super { | ||
| @SideEffectsOnly("#1") | ||
| @Override | ||
| // TODO :: error: purity.sideeffectsonly.overriding | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| /** A supertype without `@SideEffectsOnly` constrains nothing. */ | ||
| static class Unconstrained { | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| static class SubOfUnconstrained extends Unconstrained { | ||
| @SideEffectsOnly("#1.inner") | ||
| @Override | ||
| void m(Cell c) {} | ||
| } | ||
|
|
||
| @EnsuresQualifier(expression = "#1.g", qualifier = Untainted.class) | ||
| // :: error: contracts.postcondition | ||
| static void makeUntainted(Cell c) {} | ||
|
|
||
| /** | ||
| * Without the override check, this refinement would be wrongly retained: the call resolves | ||
| * statically to {@code Super.m}, which does not permit side-effecting {@code c.g}, but it may | ||
| * execute {@code SubMore.m}, which does. | ||
| */ | ||
| static void testDynamicDispatch(Super s, Cell c) { | ||
| makeUntainted(c); | ||
| s.m(c); | ||
| @Untainted Object y = c.g; | ||
| } | ||
|
mernst marked this conversation as resolved.
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.