Skip to content

Commit fc9dbdf

Browse files
Treat instanceof as an unknown boolean
Fixes #335. instanceof had no operator in the refinement language, so any `if (x instanceof T)` crashed with a NullPointerException. It is now a fresh boolean, as null comparisons already are: both branches stay reachable and verification continues. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent be47cb6 commit fc9dbdf

3 files changed

Lines changed: 53 additions & 3 deletions

File tree

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class CorrectInstanceof {
6+
7+
static int positive(@Refinement("_ > 0") int x) {
8+
return x;
9+
}
10+
11+
static String describe(Object o) {
12+
if (o instanceof String) {
13+
return "text";
14+
}
15+
return "other";
16+
}
17+
18+
static int size(Object o, @Refinement("_ > 0") int n) {
19+
boolean isText = o instanceof String;
20+
if (isText && n > 1) {
21+
return positive(n - 1);
22+
}
23+
if (!(o instanceof Integer) || n > 3) {
24+
return positive(n);
25+
}
26+
return positive(n + 1);
27+
}
28+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public class ErrorInstanceof {
6+
7+
static int positive(@Refinement("_ > 0") int x) {
8+
return x;
9+
}
10+
11+
static int size(Object o, @Refinement("_ >= 0") int n) {
12+
if (o instanceof String) {
13+
return positive(n); // Expect: Refinement Error
14+
}
15+
return 1;
16+
}
17+
}

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ public <T> void getBinaryOpRefinements(CtBinaryOperator<T> operator) throws LJEr
8181
&& ((CtAssignment<?, ?>) parent).getAssigned()instanceof CtVariableWrite<?> parentVar) {
8282
oper = getOperationRefinements(operator, parentVar, operator);
8383

84-
} else if (hasNullOperand(operator)) {
85-
oper = createFreshValue(operator, new Predicate()); // null comparisons are not supported yet: unknown value
84+
} else if (isUntranslatable(operator)) {
85+
oper = createFreshValue(operator, new Predicate()); // null comparisons and instanceof: unknown value
8686
} else {
8787
Predicate varLeft = getOperationRefinements(operator, left);
8888
Predicate varRight = getOperationRefinements(operator, right);
@@ -239,7 +239,7 @@ private Predicate getOperationRefinements(CtBinaryOperator<?> operator, CtVariab
239239
rtc.getContext().addVarToContext(elemName, elemVar.getType(), e, elemVar);
240240
return Predicate.createVar(returnName);
241241
} else if (element instanceof CtBinaryOperator<?> binop) {
242-
if (hasNullOperand(binop)) // null comparisons are not supported yet: unknown boolean value
242+
if (isUntranslatable(binop)) // null comparisons and instanceof: unknown boolean value
243243
return createFreshValue(binop, new Predicate());
244244
Predicate right = getOperationRefinements(operator, parentVar, binop.getRightHandOperand());
245245
Predicate left = getOperationRefinements(operator, parentVar, binop.getLeftHandOperand());
@@ -328,6 +328,11 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation<?> inv) thr
328328
return getUnconstrainedInvocationVariable(inv);
329329
}
330330

331+
/** Null comparisons are not supported yet, and the logic has no types to test {@code instanceof} against. */
332+
private static boolean isUntranslatable(CtBinaryOperator<?> binop) {
333+
return binop.getKind() == BinaryOperatorKind.INSTANCEOF || hasNullOperand(binop);
334+
}
335+
331336
private static boolean hasNullOperand(CtBinaryOperator<?> binop) {
332337
return isNullLiteral(binop.getLeftHandOperand()) || isNullLiteral(binop.getRightHandOperand());
333338
}

0 commit comments

Comments
 (0)