Skip to content

Commit caf2c67

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 a4fbebe commit caf2c67

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
@@ -82,8 +82,8 @@ public <T> void getBinaryOpRefinements(CtBinaryOperator<T> operator) throws LJEr
8282
&& ((CtAssignment<?, ?>) parent).getAssigned()instanceof CtVariableWrite<?> parentVar) {
8383
oper = getOperationRefinements(operator, parentVar, operator);
8484

85-
} else if (hasNullOperand(operator)) {
86-
oper = createFreshValue(operator, new Predicate()); // null comparisons are not supported yet: unknown value
85+
} else if (isUntranslatable(operator)) {
86+
oper = createFreshValue(operator, new Predicate()); // null comparisons and instanceof: unknown value
8787
} else if (operatorFor(operator) == null) {
8888
oper = untranslatableOperation(operator);
8989
} else {
@@ -245,7 +245,7 @@ private Predicate getOperationRefinements(CtBinaryOperator<?> operator, CtVariab
245245
rtc.getContext().addVarToContext(elemName, elemVar.getType(), e, elemVar);
246246
return Predicate.createVar(returnName);
247247
} else if (element instanceof CtBinaryOperator<?> binop) {
248-
if (hasNullOperand(binop)) // null comparisons are not supported yet: unknown boolean value
248+
if (isUntranslatable(binop)) // null comparisons and instanceof: unknown boolean value
249249
return createFreshValue(binop, new Predicate());
250250
if (operatorFor(binop) == null)
251251
return untranslatableOperation(binop);
@@ -336,6 +336,11 @@ private Predicate getOperationRefinementFromExternalLib(CtInvocation<?> inv) thr
336336
return getUnconstrainedInvocationVariable(inv);
337337
}
338338

339+
/** Null comparisons are not supported yet, and the logic has no types to test {@code instanceof} against. */
340+
private static boolean isUntranslatable(CtBinaryOperator<?> binop) {
341+
return binop.getKind() == BinaryOperatorKind.INSTANCEOF || hasNullOperand(binop);
342+
}
343+
339344
private static boolean hasNullOperand(CtBinaryOperator<?> binop) {
340345
return isNullLiteral(binop.getLeftHandOperand()) || isNullLiteral(binop.getRightHandOperand());
341346
}

0 commit comments

Comments
 (0)