Skip to content

Commit ef1603d

Browse files
Treat instanceof as an unknown boolean (#359)
Fixes #335. ## Problem `instanceof` has no counterpart in the refinement language, so `getOperatorFromKind` returned null for it and any `if (o instanceof String)` crashed with `Cannot invoke "String.hashCode()" because "<local1>" is null`, even in a file without refinements. ## Change `OperationsChecker` treats `instanceof` the way it already treats comparisons with `null`: the result is a fresh, unconstrained boolean (`isUntranslatable` covers both, in the binary-operator and the assignment paths). Both branches stay reachable, so nothing is assumed from the test. ## Tests - `CorrectInstanceof`: `instanceof` in an `if`, bound to a boolean local and combined with `&&`, and negated inside `||`. Crashed on main, now passes. - `ErrorInstanceof`: a refinement violation inside an `instanceof` branch is reported (crashed on main). - Pattern matching (`o instanceof String s && !s.isEmpty()`) also verifies. - `mvn test` passes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ffd51c6 commit ef1603d

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)