|
20 | 20 | import liquidjava.utils.constants.Types; |
21 | 21 |
|
22 | 22 | import org.apache.commons.lang3.NotImplementedException; |
| 23 | +import spoon.reflect.code.BinaryOperatorKind; |
23 | 24 | import spoon.reflect.code.CtArrayRead; |
24 | 25 | import spoon.reflect.code.CtArrayWrite; |
25 | 26 | import spoon.reflect.code.CtAssignment; |
|
46 | 47 | import spoon.reflect.code.CtUnaryOperator; |
47 | 48 | import spoon.reflect.code.CtVariableAccess; |
48 | 49 | import spoon.reflect.code.CtVariableRead; |
| 50 | +import spoon.reflect.code.CtVariableWrite; |
49 | 51 | import spoon.reflect.declaration.*; |
50 | 52 | import spoon.reflect.factory.Factory; |
51 | 53 | import spoon.reflect.reference.CtFieldReference; |
52 | 54 | import spoon.reflect.reference.CtTypeReference; |
53 | 55 | import spoon.reflect.reference.CtVariableReference; |
| 56 | +import spoon.reflect.visitor.filter.TypeFilter; |
54 | 57 | import spoon.support.reflect.code.CtVariableWriteImpl; |
55 | 58 |
|
56 | 59 | public class RefinementTypeChecker extends TypeChecker { |
@@ -346,6 +349,50 @@ public <T> void visitCtVariableRead(CtVariableRead<T> variableRead) { |
346 | 349 | public <T> void visitCtBinaryOperator(CtBinaryOperator<T> operator) { |
347 | 350 | super.visitCtBinaryOperator(operator); |
348 | 351 | otc.getBinaryOpRefinements(operator); |
| 352 | + forgetShortCircuitedAssignments(operator); |
| 353 | + } |
| 354 | + |
| 355 | + /** |
| 356 | + * The right operand of {@code &&}/{@code ||} runs only conditionally (it is short-circuited when the left operand |
| 357 | + * is already {@code false} resp. {@code true}). Spoon visits children before this method, so any assignment in that |
| 358 | + * operand (e.g. {@code false && ((x = 1) == 1)}) has already committed its value to the context as if it always |
| 359 | + * executed. That is unsound: at runtime the assignment may never happen, so the post-operator value of every |
| 360 | + * variable written there is uncertain. Havoc those variables (give them a fresh, unconstrained instance) so the |
| 361 | + * verifier can no longer assume the assigned value survives the operator. |
| 362 | + * |
| 363 | + * <p> |
| 364 | + * This is conservative: when the left operand is statically true (resp. false) the right operand does execute, yet |
| 365 | + * we still forget the value. Forgetting only ever weakens what is known, so it cannot accept an unsound program; it |
| 366 | + * costs precision only for the rare idiom of relying on a value assigned inside a short-circuited operand. |
| 367 | + */ |
| 368 | + private void forgetShortCircuitedAssignments(CtBinaryOperator<?> operator) { |
| 369 | + BinaryOperatorKind kind = operator.getKind(); |
| 370 | + if (kind != BinaryOperatorKind.AND && kind != BinaryOperatorKind.OR) |
| 371 | + return; |
| 372 | + |
| 373 | + CtExpression<?> conditionalOperand = operator.getRightHandOperand(); |
| 374 | + for (CtVariableWrite<?> write : conditionalOperand.getElements(new TypeFilter<>(CtVariableWrite.class))) { |
| 375 | + CtVariableReference<?> ref = write.getVariable(); |
| 376 | + if (ref == null) |
| 377 | + continue; |
| 378 | + String name = (write instanceof CtFieldWrite<?>) ? String.format(Formats.THIS, ref.getSimpleName()) |
| 379 | + : ref.getSimpleName(); |
| 380 | + havocVariable(name, write); |
| 381 | + } |
| 382 | + } |
| 383 | + |
| 384 | + /** |
| 385 | + * Drops everything currently known about {@code name} by installing a fresh, unconstrained instance as its latest |
| 386 | + * value. Subsequent reads resolve to this instance and therefore carry no refinement. |
| 387 | + */ |
| 388 | + private void havocVariable(String name, CtElement element) { |
| 389 | + RefinedVariable rv = context.getVariableByName(name); |
| 390 | + if (!(rv instanceof Variable)) |
| 391 | + return; |
| 392 | + String freshName = String.format(Formats.INSTANCE, name, context.getCounter()); |
| 393 | + context.addInstanceToContext(freshName, rv.getType(), new Predicate(), element); |
| 394 | + context.addRefinementInstanceToVariable(name, freshName); |
| 395 | + context.addRefinementToVariableInContext(name, rv.getType(), new Predicate(), element); |
349 | 396 | } |
350 | 397 |
|
351 | 398 | @Override |
|
0 commit comments