Skip to content
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

Fix WPI Non-Termination Issue with @UnknownInitialization Annotations #6657

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
ebb561b
Handle WPI loop on Unknowninitialization annotations
erfan-arvan Jun 5, 2024
30d842f
Resolved merge conflicts
erfan-arvan Jun 19, 2024
05ef6fb
Rename FlowExpression to JavaExpression in tests
smillst Jun 5, 2024
ebf782c
Update dependency io.github.classgraph:classgraph to v4.8.173 (#6653)
renovate[bot] Jun 5, 2024
5167ed1
Implement `JavaExpression.SuperReference`
jyoo980 Jun 5, 2024
ee517ea
Added a Tree argument to AnnotatedTypes.adaptParameters()
smillst Jun 6, 2024
f3a08a7
Update contributors.tex
erfan-arvan Jun 6, 2024
5df02b7
Update contributors.tex to include the latest contributors
erfan-arvan Jun 6, 2024
42d7812
Update contributors.tex to include my name
erfan-arvan Jun 6, 2024
711ae4d
How to indicate that side effects can change a value's type qualifier…
mernst Jun 6, 2024
d9a6504
Define `TreeUtils.fieldsFromClassTree()` (#6659)
mernst Jun 6, 2024
342bc94
Update contributors.tex after make it again
erfan-arvan Jun 7, 2024
b5a62d8
Try to pass CI test -> Contrubutors names
erfan-arvan Jun 7, 2024
5f3f89d
Update contributors.tex to reflect latest changes
erfan-arvan Jun 7, 2024
81f1f2b
Improve Javadoc for `getAnnotationFromJavaExpression`
smillst Jun 8, 2024
8c345fd
Add `graphviz` to list of dev dependencies for macOS
jyoo980 Jun 11, 2024
118b771
Exclude my username from contributors.tex
erfan-arvan Jun 18, 2024
4818ac2
Transferred the proposed change for handling WPI loop for unknowninit…
erfan-arvan Jun 18, 2024
134e76f
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
kelloggm Jun 19, 2024
1c31423
Rename method to adjust annotations before updating annotation set, u…
erfan-arvan Jun 18, 2024
131e864
Merge remote-tracking branch 'origin/bugfix/wpi-loop-unknowninitializ…
erfan-arvan Jun 20, 2024
4691e84
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
erfan-arvan Jun 22, 2024
872d7e1
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
erfan-arvan Jun 24, 2024
33c82a6
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
kelloggm Jun 27, 2024
5b642c3
Merge branch 'typetools:master' into bugfix/wpi-loop-unknowninitializ…
erfan-arvan Jul 1, 2024
d7acb57
Changed the name of the method, enhanced the body, and transferred th…
erfan-arvan Jul 6, 2024
1b1d56f
Merge branch 'master' into bugfix/wpi-loop-unknowninitialization
erfan-arvan Jul 6, 2024
8826275
Removed unnecessary comments
erfan-arvan Jul 7, 2024
388e584
Merge branch 'typetools:master' into bugfix/wpi-loop-unknowninitializ…
erfan-arvan Jul 7, 2024
fd16cc8
Merge branch 'bugfix/wpi-loop-unknowninitialization' of https://githu…
erfan-arvan Jul 7, 2024
eb79855
Removed the imports from the test case
erfan-arvan Jul 7, 2024
6ae7fba
Changed the name of the test case
erfan-arvan Jul 7, 2024
f057894
Update checker build.gradle to fix ainferNullnessGenerateJaifs task i…
erfan-arvan Jul 10, 2024
1a32487
Rename an instance of `VarArgsTest`
mernst Jul 7, 2024
90129a3
Give `DefaultQualifier` runtime retention
mernst Jul 7, 2024
cf451ba
Minor changes related to Non-Empty Checker
mernst Jul 8, 2024
8eafe7d
Use `filePermissions` instead of deprecated `fileMode` (#6693)
mernst Jul 8, 2024
1d5fb60
Disambiguate owners of fields in output (#6699)
mernst Jul 8, 2024
217ba50
Comment changes (#6700)
mernst Jul 8, 2024
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
14 changes: 13 additions & 1 deletion checker-qual-android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,19 @@ task copySources(type: Copy) {

into file(layout.buildDirectory.dir("generated/sources/main/java"))

fileMode(0555)
filePermissions {
user {
read = true
write = true
execute = true
}
group {
read = true
write = true
execute = true
}
other.read = true
}
}

sourceSets {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
* @checker_framework.manual #defaults Default qualifier for unannotated types
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.PACKAGE,
ElementType.TYPE,
Expand Down
4 changes: 3 additions & 1 deletion checker/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -813,6 +813,9 @@ task ainferNullnessGenerateJaifs(type: Test) {
delete('tests/ainfer-nullness/annotated/TwoCtorGenericAbstract.java')
delete('tests/ainfer-nullness/annotated/TypeVarReturnAnnotated.java')

//This test shouldn't be expected to work without the input .ajava file.
delete('tests/ainfer-nullness/annotated/WPIUnknownInitializationLoopTest.java')

// Inserting annotations from .jaif files in-place.
String jaifsDir = 'tests/ainfer-nullness/inference-output';
List<File> jaifs = fileTree(jaifsDir).matching {
Expand Down Expand Up @@ -1079,4 +1082,3 @@ publishing {
signing {
sign publishing.publications.checker
}

Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import org.checkerframework.dataflow.cfg.node.Node;
import org.checkerframework.dataflow.util.NodeUtils;
import org.checkerframework.framework.flow.CFAbstractAnalysis;
import org.checkerframework.framework.qual.TypeUseLocation;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.framework.type.AnnotatedTypeFormatter;
import org.checkerframework.framework.type.AnnotatedTypeMirror;
Expand Down Expand Up @@ -938,6 +939,8 @@ public boolean isImmutable(TypeMirror type) {
@Override
public void wpiAdjustForUpdateField(
Tree lhsTree, Element element, String fieldName, AnnotatedTypeMirror rhsATM) {
// Adjust initialization annotations first
wpiAdjustInitializationAnnotations(rhsATM, TypeUseLocation.FIELD);
// Synthetic variable names contain "#". Ignore them.
if (!rhsATM.hasPrimaryAnnotation(Nullable.class) || fieldName.contains("#")) {
return;
Expand All @@ -956,11 +959,42 @@ public void wpiAdjustForUpdateField(
// then change rhs to @Nullable
@Override
public void wpiAdjustForUpdateNonField(AnnotatedTypeMirror rhsATM) {
// Adjust initialization annotations first
wpiAdjustInitializationAnnotations(rhsATM, TypeUseLocation.OTHERWISE);
if (rhsATM.hasPrimaryAnnotation(MonotonicNonNull.class)) {
rhsATM.replaceAnnotation(NULLABLE);
}
}

/**
* Changes the type of {@code rhsATM} when assigned to any pseudo-assignment, for use by
* whole-program inference.
*
* <p>If {@code rhsATM} is Nullable or MonotonicNonNull and has the UnknownInitialization
* annotation, replace it with {@code UnknownInitialization(java.lang.Object.class)}. This ensures
* that if there is a constructor where the type hasn't been initialized, its effect is
* considered. Otherwise, whole-program inference might get stuck in a loop.
*
* @param rhsATM the type of the right-hand side of the pseudo-assignment, which is side-effected
* by this method
* @param defLoc the location where the annotation will be added. It is either a FIELD or a
* non-field (OTHERWISE).
*/
public void wpiAdjustInitializationAnnotations(
AnnotatedTypeMirror rhsATM, TypeUseLocation defLoc) {
// defLoc is defined to be used in the future if needed to differentiate between field and
// non-field locations
if ((rhsATM.hasPrimaryAnnotation(Nullable.class)
|| rhsATM.hasPrimaryAnnotation(MonotonicNonNull.class))) {
for (AnnotationMirror anno : rhsATM.getPrimaryAnnotations()) {
if (AnnotationUtils.areSameByName(
anno, "org.checkerframework.checker.initialization.qual" + ".UnknownInitialization")) {
rhsATM.replaceAnnotation(UNKNOWN_INITIALIZATION);
}
}
}
}

@Override
public boolean wpiShouldInferTypesForReceivers() {
// All receivers must be non-null, or the dereference involved in
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,15 @@
import org.checkerframework.framework.flow.CFStore;
import org.checkerframework.framework.flow.CFTransfer;
import org.checkerframework.framework.flow.CFValue;
import org.checkerframework.framework.type.AnnotatedTypeFactory;
import org.checkerframework.javacutil.AnnotationBuilder;
import org.checkerframework.javacutil.TreeUtils;

/** The transfer function for the Optional Checker. */
public class OptionalTransfer extends CFTransfer {

/** The {@link OptionalAnnotatedTypeFactory} instance for this transfer class. */
private final OptionalAnnotatedTypeFactory optionalTypeFactory;

/** The @{@link Present} annotation. */
private final AnnotationMirror PRESENT;

Expand All @@ -38,20 +40,17 @@ public class OptionalTransfer extends CFTransfer {
/** The element for java.util.Optional.ifPresentOrElse(), or null. */
private final @Nullable ExecutableElement optionalIfPresentOrElse;

/** The type factory associated with this transfer function. */
private final AnnotatedTypeFactory atypeFactory;

/**
* Create an OptionalTransfer.
*
* @param analysis the Optional Checker instance
*/
public OptionalTransfer(CFAbstractAnalysis<CFValue, CFStore, CFTransfer> analysis) {
super(analysis);
atypeFactory = analysis.getTypeFactory();
Elements elements = atypeFactory.getElementUtils();
optionalTypeFactory = (OptionalAnnotatedTypeFactory) analysis.getTypeFactory();
Elements elements = optionalTypeFactory.getElementUtils();
PRESENT = AnnotationBuilder.fromClass(elements, Present.class);
ProcessingEnvironment env = atypeFactory.getProcessingEnv();
ProcessingEnvironment env = optionalTypeFactory.getProcessingEnv();
optionalIfPresent = TreeUtils.getMethod("java.util.Optional", "ifPresent", 1, env);
optionalIfPresentOrElse =
TreeUtils.getMethodOrNull("java.util.Optional", "ifPresentOrElse", 2, env);
Expand All @@ -70,7 +69,7 @@ public CFStore initialStore(UnderlyingAST underlyingAST, List<LocalVariableNode>
LambdaExpressionTree lambdaTree = cfgLambda.getLambdaTree();
List<? extends VariableTree> lambdaParams = lambdaTree.getParameters();
if (lambdaParams.size() == 1) {
TreePath lambdaPath = atypeFactory.getPath(lambdaTree);
TreePath lambdaPath = optionalTypeFactory.getPath(lambdaTree);
Tree lambdaParent = lambdaPath.getParentPath().getLeaf();
if (lambdaParent.getKind() == Tree.Kind.METHOD_INVOCATION) {
MethodInvocationTree invok = (MethodInvocationTree) lambdaParent;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
interface Game {

@org.checkerframework.dataflow.qual.SideEffectFree
void newGame();
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
class // package-private
GameImpl implements // package-private
Game {

private MoveValidator moveValidator;

@org.checkerframework.dataflow.qual.Impure
public GameImpl(MoveValidator mValidator) {
mValidator.setGame(this);
moveValidator = mValidator;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public GameImpl() {
}

@org.checkerframework.dataflow.qual.SideEffectFree
public void newGame( // package-private
GameImpl this) {
// Implementation of starting a new game
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
interface MoveValidator {

@org.checkerframework.dataflow.qual.Impure
void setGame( MoveValidator this, Game game);
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
class // package-private
PlayerDependentMoveValidator implements // package-private
MoveValidator {

private Game game;

private MoveValidator blackMoveValidator = new SimpleMoveValidator();

@org.checkerframework.dataflow.qual.Impure
public void setGame( // package-private
PlayerDependentMoveValidator this, Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public PlayerDependentMoveValidator() {
}

@org.checkerframework.dataflow.qual.Impure
public PlayerDependentMoveValidator( Game game) {
this.setGame(game);
blackMoveValidator.setGame(game);
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.KeyForSubchecker")
class // package-private
SimpleMoveValidator implements // package-private
MoveValidator {

private Game game;

@org.checkerframework.dataflow.qual.Impure
public void setGame( // package-private
SimpleMoveValidator this, Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public SimpleMoveValidator() {
}

@org.checkerframework.dataflow.qual.Pure
public MoveValidator createMoveValidator() {
return new PlayerDependentMoveValidator(game);
}
public void test(){
PlayerDependentMoveValidator g1 =
new PlayerDependentMoveValidator(game);
this.game = g1.game;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
interface Game {

@org.checkerframework.dataflow.qual.SideEffectFree
void newGame();
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
class // package-private
GameImpl implements // package-private
Game {

private @org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.MonotonicNonNull MoveValidator moveValidator;

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.moveValidator" })
@org.checkerframework.dataflow.qual.Impure
public GameImpl(MoveValidator mValidator) {
mValidator.setGame(this);
moveValidator = mValidator;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public GameImpl() {
}

@org.checkerframework.framework.qual.EnsuresQualifier(expression = { "this.moveValidator" }, qualifier = org.checkerframework.checker.nullness.qual.Nullable.class)
@org.checkerframework.dataflow.qual.SideEffectFree
public void newGame(@org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull // package-private
GameImpl this) {
// Implementation of starting a new game
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
interface MoveValidator {

@org.checkerframework.dataflow.qual.Impure
void setGame(@org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.Nullable Game game);
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
class // package-private
PlayerDependentMoveValidator implements // package-private
MoveValidator {

private @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.Nullable Game game;

private @org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull MoveValidator blackMoveValidator = new SimpleMoveValidator();

@org.checkerframework.framework.qual.EnsuresQualifier(expression = { "this.game" }, qualifier = org.checkerframework.checker.nullness.qual.Nullable.class)
@org.checkerframework.dataflow.qual.Impure
public void setGame(@org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull // package-private
PlayerDependentMoveValidator this, @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.Nullable Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public PlayerDependentMoveValidator() {
}

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "#1" })
@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.game" })
@org.checkerframework.dataflow.qual.Impure
public PlayerDependentMoveValidator(@org.checkerframework.checker.initialization.qual.UnknownInitialization(java.lang.Object.class) @org.checkerframework.checker.nullness.qual.Nullable Game game) {
this.setGame(game);
blackMoveValidator.setGame(game);
}
}

@org.checkerframework.framework.qual.AnnotatedFor("org.checkerframework.checker.nullness.NullnessChecker")
class // package-private
SimpleMoveValidator implements // package-private
MoveValidator {

private @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.MonotonicNonNull Game game;

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.game" })
@org.checkerframework.dataflow.qual.Impure
public void setGame(@org.checkerframework.checker.initialization.qual.Initialized @org.checkerframework.checker.nullness.qual.NonNull // package-private
SimpleMoveValidator this, @org.checkerframework.checker.initialization.qual.UnknownInitialization(GameImpl.class) @org.checkerframework.checker.nullness.qual.NonNull Game game) {
this.game = game;
}

@org.checkerframework.dataflow.qual.SideEffectFree
public SimpleMoveValidator() {
}

@org.checkerframework.checker.nullness.qual.EnsuresNonNull({ "this.game" })
@org.checkerframework.dataflow.qual.Pure
public @org.checkerframework.checker.initialization.qual.UnderInitialization(PlayerDependentMoveValidator.class) @org.checkerframework.checker.nullness.qual.NonNull MoveValidator createMoveValidator() {
return new PlayerDependentMoveValidator(game);
}
public void test(){
PlayerDependentMoveValidator g1 =
new PlayerDependentMoveValidator(game);
this.game = g1.game;
}
}
Loading
Loading