Skip to content

Commit cedbdec

Browse files
Model null comparisons as unknown booleans instead of true
Treating `x == null` / `x != null` as the constant `true` made the negated condition `false`, so else branches of `||` conditions were considered dead, `&&` else branches gained a false fact, and ternaries and boolean locals took a fixed value. A null comparison now becomes a fresh unconstrained boolean variable; other operands keep their facts. Adds ErrorNullLiterals (negative test) and extends CorrectNullLiterals. Refs #301 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 5bf4604 commit cedbdec

3 files changed

Lines changed: 63 additions & 3 deletions

File tree

‎liquidjava-example/src/main/java/testSuite/CorrectNullLiterals.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,17 @@ public static void main(String[] args) throws IOException {
3838
int y = x + 1;
3939
}
4040
}
41+
42+
// facts next to a null comparison are kept
43+
static void conjunction(String s, int y) {
44+
if (s == null && y > 0) {
45+
@Refinement("_ > 0")
46+
int z = y;
47+
}
48+
}
49+
50+
static void ternary(Object o) {
51+
@Refinement("_ == -1 || _ == 1")
52+
int x = (o == null) ? -1 : 1;
53+
}
4154
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
@SuppressWarnings("unused")
6+
public class ErrorNullLiterals {
7+
8+
// comparisons with null are unknown booleans, so no branch may be considered unreachable
9+
10+
static void elseBranch(String name) {
11+
if (name == null) {
12+
System.out.println("none");
13+
} else {
14+
@Refinement("_ > 0")
15+
int x = -1; // Expect: Refinement Error
16+
}
17+
}
18+
19+
static void elseBranchOfDisjunction(String name, int y) {
20+
if (name == null || y > 0) {
21+
System.out.println("some");
22+
} else {
23+
@Refinement("_ > 0")
24+
int x = -1; // Expect: Refinement Error
25+
}
26+
}
27+
28+
static void elseBranchOfConjunction(String name, int y) {
29+
if (name != null && y > 0) {
30+
System.out.println("some");
31+
} else {
32+
@Refinement("_ <= 0")
33+
int z = y; // Expect: Refinement Error
34+
}
35+
}
36+
37+
static void ternary(Object o) {
38+
@Refinement("_ < 0")
39+
int x = (o == null) ? -1 : 1; // Expect: Refinement Error
40+
}
41+
42+
static void booleanValue(Object o) {
43+
boolean isNull = o == null;
44+
@Refinement("_ == true")
45+
boolean b = isNull; // Expect: Refinement Error
46+
}
47+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/OperationsChecker.java‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ public <T> void getBinaryOpRefinements(CtBinaryOperator<T> operator) throws LJEr
8080
oper = getOperationRefinements(operator, parentVar, operator);
8181

8282
} else if (hasNullOperand(operator)) {
83-
oper = new Predicate(); // comparisons with null are not supported yet, carry no information
83+
oper = createFreshValue(operator, new Predicate()); // null comparisons are not supported yet: unknown value
8484
} else {
8585
Predicate varLeft = getOperationRefinements(operator, left);
8686
Predicate varRight = getOperationRefinements(operator, right);
@@ -225,8 +225,8 @@ private Predicate getOperationRefinements(CtBinaryOperator<?> operator, CtVariab
225225
rtc.getContext().addVarToContext(elemName, elemVar.getType(), e, elemVar);
226226
return Predicate.createVar(returnName);
227227
} else if (element instanceof CtBinaryOperator<?> binop) {
228-
if (hasNullOperand(binop))
229-
return new Predicate();
228+
if (hasNullOperand(binop)) // null comparisons are not supported yet: unknown boolean value
229+
return createFreshValue(binop, new Predicate());
230230
Predicate right = getOperationRefinements(operator, parentVar, binop.getRightHandOperand());
231231
Predicate left = getOperationRefinements(operator, parentVar, binop.getLeftHandOperand());
232232
return Predicate.createOperation(left, getOperatorFromKind(binop.getKind()), right);

0 commit comments

Comments
 (0)