Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4cee6b1
Added checkerframework nullness testing
Jan 19, 2026
4198377
Added type annotations and null checks
Jan 19, 2026
0949387
Added type annotations and null checks
Jan 19, 2026
244654c
Added type annotations and null checks
Jan 19, 2026
5e66560
Added null checks and annotations
Jan 19, 2026
ad831bb
Added null checks and annotations
Jan 19, 2026
2dab037
Added null checks and annotations
Jan 19, 2026
7c067b9
Explictly defined checker framework version
Feb 3, 2026
18edf6e
Merge branch 'checkerframework' into checkerframework-RefactoringEngine
Feb 4, 2026
e58bb8f
Spotless Applied
Feb 4, 2026
6786b53
Merge branch 'checkerframework' into checkerframework-vgrtool
Feb 4, 2026
e27ea5e
Updated comments
Feb 4, 2026
ed6bbf9
Merge branch 'checkerframework' into checkerframework-Boolean
Feb 4, 2026
a9e3a51
Merge branch 'checkerframework' into checkerframework-NestedNull
Feb 4, 2026
b4f2f65
Merge branch 'checkerframework' into checkerframework-Sentinel
Feb 4, 2026
debaa7a
Merge branch 'checkerframework' into checkerframework-dereference
Feb 4, 2026
6235356
Updated testing engine to remove null errors and applied Spotless
Feb 4, 2026
8e0e8ef
Removed wildcard inputs
Feb 9, 2026
7721582
Removed NonNull annotations on parameter types for Boolean-Flag
Feb 9, 2026
8701fcb
Removed NonNull annotations on parameter types for AddNullCheck
Feb 9, 2026
b218a58
Removed NonNull annotations on parameter types for Refactoring Engine
Feb 9, 2026
bc8f3a4
Removed NonNull annotations on parameter types for SentinelRefactoring
Feb 9, 2026
3ccb62e
Removed NonNull annotations on parameter types for VGRTool
Feb 9, 2026
8b56f35
Removed NonNull annotations on parameter types for Boolean-Flag
Feb 9, 2026
e5bf52b
Removed NonNull annotations on parameter types for NestedNullRefactoring
Feb 9, 2026
04d3c3d
Applied spotless
Feb 9, 2026
bc3cf67
Updated BooleanFlagRefactoring commments to better explain SuppressWa…
Feb 9, 2026
31243d5
Reduced scope of SuppressWarnings in VGRTool.java
Feb 9, 2026
3bed31a
Updated comments for clarity
Feb 9, 2026
053f184
Replaced suppress warning annotations with stubfiles
Feb 16, 2026
b3cb66a
Added link to documentation for SuppressWarnings comments
Feb 16, 2026
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: 14 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ plugins {
id 'application'
id("com.diffplug.spotless") version "7.0.4"
id 'jacoco'
id("org.checkerframework").version("0.6.61")
}

apply plugin: "org.checkerframework"

group = 'com.example'
version = '1.0'

Expand All @@ -27,6 +30,11 @@ dependencies {
implementation 'org.checkerframework:checker:3.21.0'
annotationProcessor 'org.checkerframework:checker:3.21.0'

// Specify Checker Framework v3.52.0
compileOnly("org.checkerframework:checker-qual:3.52.0")
testCompileOnly("org.checkerframework:checker-qual:3.52.0")
checkerFramework("org.checkerframework:checker:3.52.0")

// Apache Commons
implementation 'commons-io:commons-io:2.16.1'

Expand All @@ -49,6 +57,12 @@ dependencies {
runtimeOnly 'org.apache.logging.log4j:log4j-layout-template-json'
}

checkerFramework {
// Define which checkers to run
checkers = [
"org.checkerframework.checker.nullness.NullnessChecker",
]
}

spotless {

Expand Down
25 changes: 17 additions & 8 deletions src/main/java/AddNullCheckBeforeDereferenceRefactoring.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.text.edits.TextEditGroup;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.*;

/**
* A refactoring module that replaces checks on variables whose nullness is
Expand Down Expand Up @@ -51,7 +53,7 @@ public class AddNullCheckBeforeDereferenceRefactoring extends Refactoring {
* key, ensuring global uniqueness. Two variables who have the same name but
* have different scopes will have different IBinding instances.
*/
private final Map<IBinding, Expression> validRefactors;
private final Map<@NonNull IBinding, @NonNull Expression> validRefactors;

/** Default constructor (for RefactoringEngine integration) */
public AddNullCheckBeforeDereferenceRefactoring() {
Expand All @@ -60,7 +62,7 @@ public AddNullCheckBeforeDereferenceRefactoring() {
}

@Override
public boolean isApplicable(ASTNode node) {
public boolean isApplicable(@NonNull ASTNode node) {
if (node instanceof VariableDeclarationFragment varFrag) {
return isApplicable(varFrag);
}
Expand All @@ -77,7 +79,7 @@ public boolean isApplicable(ASTNode node) {
return false;
}

private boolean isApplicable(VariableDeclarationFragment var) {
private boolean isApplicable(@NonNull VariableDeclarationFragment var) {
Expression initializer = var.getInitializer();
if (initializer == null)
return false;
Expand Down Expand Up @@ -111,7 +113,7 @@ private boolean isApplicable(VariableDeclarationFragment var) {
return false;
}

private boolean isApplicable(IfStatement ifStmt) {
private boolean isApplicable(@NonNull IfStatement ifStmt) {
Expression ifStmtCondition = ifStmt.getExpression();
LOGGER.debug("Analyzing if-statement: %s", ifStmtCondition);
List<Expression> conditionFragments = Refactoring.getSubExpressions(ifStmtCondition);
Expand Down Expand Up @@ -145,7 +147,7 @@ private boolean isApplicable(IfStatement ifStmt) {
}

@Override
public void apply(ASTNode node, ASTRewrite rewriter) {
public void apply(@NonNull ASTNode node, @NonNull ASTRewrite rewriter) {
if (!(node instanceof IfStatement ifStmt)) {
return;
}
Expand Down Expand Up @@ -173,15 +175,22 @@ public void apply(ASTNode node, ASTRewrite rewriter) {
}

Expression ternary = validRefactors.get(varName.resolveBinding());
if (ternary == null) {
continue;
}

AST ast = node.getAST();
ParenthesizedExpression pExpression = ast.newParenthesizedExpression();
pExpression.setExpression((Expression) ASTNode.copySubtree(ast, ternary));
Expression expr = (Expression) ASTNode.copySubtree(ast, ternary);
if (expr == null) {
continue;
}
pExpression.setExpression(expr);

LOGGER.debug("[DEBUG] Replacing Variable: " + varName);
LOGGER.debug("[DEBUG] New Value: " + pExpression);

rewriter.replace(condition, pExpression, null);
rewriter.replace(condition, pExpression, new TextEditGroup(""));
}

}
Expand All @@ -190,7 +199,7 @@ public void apply(ASTNode node, ASTRewrite rewriter) {
* Checks Assignment node to see if it re-assigns an existing valid refactoring,
* and if so removes it from validRefactors
*/
private void verifyRefactors(Assignment assignmentNode) {
private void verifyRefactors(@NonNull Assignment assignmentNode) {
Expression lhs = assignmentNode.getLeftHandSide();
if (!(lhs instanceof SimpleName varName)) {
return;
Expand Down
29 changes: 17 additions & 12 deletions src/main/java/BooleanFlagRefactoring.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import java.util.List;
import java.util.Map;

import org.checkerframework.checker.nullness.qual.*;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Assignment;
Expand All @@ -18,6 +19,7 @@
import org.eclipse.jdt.core.dom.VariableDeclarationStatement;
import org.eclipse.jdt.core.dom.InfixExpression.Operator;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.text.edits.TextEditGroup;

/**
* This class represents a refactoring in which boolean flags are replaced with
Expand All @@ -30,7 +32,7 @@ public class BooleanFlagRefactoring extends Refactoring {
* List of variable names identified as boolean flags, along with their
* corresponding initializer expression
*/
private final Map<IBinding, Expression> flagExpressions;
private final Map<IBinding, @NonNull Expression> flagExpressions;

/** Default constructor (for RefactoringEngine integration) */
public BooleanFlagRefactoring() {
Expand All @@ -39,7 +41,7 @@ public BooleanFlagRefactoring() {
}

@Override
public boolean isApplicable(ASTNode node) {
public boolean isApplicable(@NonNull ASTNode node) {
if (node instanceof VariableDeclarationStatement stmt) {
return isApplicable(stmt);
} else if (node instanceof IfStatement ifStmt) {
Expand All @@ -54,7 +56,7 @@ public boolean isApplicable(ASTNode node) {
* Checks to see if a VariableDeclarationStatement defines a boolean flag that
* represents another variable's nullness
*/
private boolean isApplicable(VariableDeclarationStatement stmt) {
private boolean isApplicable(@NonNull VariableDeclarationStatement stmt) {
boolean isBooleanDeclaration = (stmt.getType() instanceof PrimitiveType pType
&& pType.getPrimitiveTypeCode() == PrimitiveType.BOOLEAN);

Expand All @@ -66,7 +68,10 @@ private boolean isApplicable(VariableDeclarationStatement stmt) {
AST ast = stmt.getAST();

// Search through all declared variables in declaration node for a booleanflag
for (VariableDeclarationFragment frag : (List<VariableDeclarationFragment>) stmt.fragments()) {
@SuppressWarnings("unchecked") // Silence type warnings; fragments() documentation guarantees type is
// valid.
List<VariableDeclarationFragment> fragments = (List<VariableDeclarationFragment>) stmt.fragments();
for (VariableDeclarationFragment frag : fragments) {
Expression varInitializer = frag.getInitializer();
if (varInitializer == null) {
continue;
Expand All @@ -92,7 +97,7 @@ && getNullComparisonVariable(infix) != null) {
* Analyzes an IfStatement to see if it contains a check utilizing an identified
* boolean flag
*/
private boolean isApplicable(IfStatement ifStmt) {
private boolean isApplicable(@NonNull IfStatement ifStmt) {
List<Expression> exprFragments = Refactoring.getSubExpressions(ifStmt.getExpression());
for (Expression expr : exprFragments) {
if (expr instanceof InfixExpression infix && isEqualityOperator(infix.getOperator())) {
Expand All @@ -111,15 +116,15 @@ private boolean isApplicable(IfStatement ifStmt) {
return false;
}

private boolean isFlag(SimpleName potentialFlag) {
private boolean isFlag(@NonNull SimpleName potentialFlag) {
return flagExpressions.get(potentialFlag.resolveBinding()) != null;
}

private boolean isEqualityOperator(Operator op) {
private boolean isEqualityOperator(@NonNull Operator op) {
return (op == Operator.NOT_EQUALS || op == Operator.EQUALS);
}

private SimpleName getNullComparisonVariable(InfixExpression infix) {
private @Nullable SimpleName getNullComparisonVariable(@NonNull InfixExpression infix) {
Expression leftOperand = infix.getLeftOperand();
Expression rightOperand = infix.getRightOperand();
if (leftOperand instanceof SimpleName varName && rightOperand instanceof NullLiteral) {
Expand All @@ -132,7 +137,7 @@ private SimpleName getNullComparisonVariable(InfixExpression infix) {
}

@Override
public void apply(ASTNode node, ASTRewrite rewriter) {
public void apply(@NonNull ASTNode node, @NonNull ASTRewrite rewriter) {
if (!(node instanceof IfStatement ifStmt)) {
return;
}
Expand All @@ -148,13 +153,13 @@ public void apply(ASTNode node, ASTRewrite rewriter) {
}
}

private void apply(ASTRewrite rewriter, SimpleName flagName) {
private void apply(@NonNull ASTRewrite rewriter, @Nullable SimpleName flagName) {
if (flagName == null || !isFlag(flagName)) {
return;
}
Expression newExpr = flagExpressions.get(flagName.resolveBinding());
if (newExpr != null) {
rewriter.replace(flagName, newExpr, null);
rewriter.replace(flagName, newExpr, new TextEditGroup(""));
}

}
Expand All @@ -163,7 +168,7 @@ private void apply(ASTRewrite rewriter, SimpleName flagName) {
* Checks Assignment node to see if it re-assigns an existing boolean flag, and
* if so removes the flag from flagExpressions
*/
private void checkReassignment(Assignment assignmentNode) {
private void checkReassignment(@NonNull Assignment assignmentNode) {
Expression lhs = assignmentNode.getLeftHandSide();
if (!(lhs instanceof SimpleName varName)) {
return;
Expand Down
41 changes: 28 additions & 13 deletions src/main/java/NestedNullRefactoring.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;

import org.checkerframework.checker.nullness.qual.*;
import org.eclipse.text.edits.TextEditGroup;
import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Block;
Expand Down Expand Up @@ -32,14 +33,14 @@
public class NestedNullRefactoring extends Refactoring {
public static final String NAME = "NestedNullRefactoring";

private final Dictionary<IMethodBinding, Expression> applicableMethods;
private final Dictionary<IMethodBinding, @NonNull Expression> applicableMethods;

public NestedNullRefactoring() {
applicableMethods = new Hashtable<>();
}

@Override
public boolean isApplicable(ASTNode node) {
public boolean isApplicable(@NonNull ASTNode node) {
if (node instanceof MethodInvocation invocation) {
return isApplicableImpl(invocation);
}
Expand All @@ -55,7 +56,7 @@ public boolean isApplicable(ASTNode node) {
* Returns true iff the provided invocation is of a registered one-line method
* that returns the result of a null check
*/
private boolean isApplicableImpl(MethodInvocation invocation) {
private boolean isApplicableImpl(@NonNull MethodInvocation invocation) {
if (applicableMethods.get(invocation.resolveMethodBinding()) != null) {
System.out.println("[DEBUG] Invocation of applicable method found");
return true;
Expand All @@ -67,7 +68,7 @@ private boolean isApplicableImpl(MethodInvocation invocation) {
* Returns true iff Node is a one-line private method that returns the result of
* a null check
*/
private boolean isApplicableImpl(MethodDeclaration declaration) {
private boolean isApplicableImpl(@NonNull MethodDeclaration declaration) {
// getReturnType() is deprecated and replaced by getReturnType2()
Type retType = declaration.getReturnType2();
boolean returnsBoolean = (retType.isPrimitiveType()
Expand All @@ -90,7 +91,12 @@ private boolean isApplicableImpl(MethodDeclaration declaration) {
}

Block body = declaration.getBody();
List<Statement> stmts = body.statements();
if (body == null) {
return false;
}

@SuppressWarnings("unchecked") // Silence type warnings; statements() documentation guarantees type is valid.
List<Statement> stmts = (List<Statement>) body.statements();

boolean isOneLine = stmts.size() == 1;
if (!isOneLine) {
Expand All @@ -104,7 +110,7 @@ private boolean isApplicableImpl(MethodDeclaration declaration) {

// Checks that the return statement is of a single equality check
Expression retExpr = ((ReturnStatement) stmt).getExpression();
if (!(retExpr instanceof InfixExpression)) {
if (retExpr == null || !(retExpr instanceof InfixExpression)) {
return false;
}

Expand All @@ -118,7 +124,11 @@ private boolean isApplicableImpl(MethodDeclaration declaration) {
if ((isValidOperand(leftOperand) && rightOperand instanceof NullLiteral)
|| (isValidOperand(rightOperand) && leftOperand instanceof NullLiteral)) {
System.out.println("[DEBUG] Found one line null check method: " + declaration.getName());
applicableMethods.put((declaration.resolveBinding()), retExpr);
IMethodBinding binding = declaration.resolveBinding();
if (binding == null) {
return false;
}
applicableMethods.put((binding), retExpr);
}
}
return false;
Expand All @@ -128,12 +138,12 @@ private boolean isApplicableImpl(MethodDeclaration declaration) {
* Returns true iff the provided expression can be on one side of a refactorable
* null equality check, i.e. it represents a valid variable or constant.
*/
private boolean isValidOperand(Expression operand) {
private boolean isValidOperand(@NonNull Expression operand) {
return (operand instanceof SimpleName || operand instanceof FieldAccess || operand instanceof QualifiedName);
}

@Override
public void apply(ASTNode node, ASTRewrite rewriter) {
public void apply(@NonNull ASTNode node, @NonNull ASTRewrite rewriter) {
// Check if Method Invocation is in applicableMethods
if (node instanceof MethodInvocation invocation) {
replace(node, rewriter, invocation);
Expand All @@ -143,8 +153,13 @@ public void apply(ASTNode node, ASTRewrite rewriter) {
}
}

private void replace(ASTNode node, ASTRewrite rewriter, MethodInvocation invocation) {
Expression expr = (applicableMethods.get((invocation.resolveMethodBinding())));
private void replace(@NonNull ASTNode node, @NonNull ASTRewrite rewriter, @NonNull MethodInvocation invocation) {
IMethodBinding binding = invocation.resolveMethodBinding();
if (binding == null) {
return;
}

Expression expr = (applicableMethods.get(binding));
if (expr == null) {
System.err.println("Cannot find applicable method for refactoring. ");
return;
Expand All @@ -158,7 +173,7 @@ private void replace(ASTNode node, ASTRewrite rewriter, MethodInvocation invocat
pExpr.setExpression(copiedExpression);

System.out.println("Refactoring " + node + "\n\tReplacing \n\t" + invocation + "\n\tWith \n\t" + pExpr);
rewriter.replace(invocation, pExpr, null);
rewriter.replace(invocation, pExpr, new TextEditGroup(""));

}

Expand Down
Loading
Loading