|
3 | 3 | import java.lang.annotation.Annotation; |
4 | 4 | import java.util.ArrayList; |
5 | 5 | import java.util.Arrays; |
| 6 | +import java.util.IdentityHashMap; |
6 | 7 | import java.util.LinkedHashSet; |
7 | 8 | import java.util.List; |
| 9 | +import java.util.Map; |
8 | 10 | import java.util.Optional; |
9 | 11 | import java.util.Set; |
10 | 12 |
|
|
31 | 33 | import spoon.reflect.code.CtBinaryOperator; |
32 | 34 | import spoon.reflect.code.CtBlock; |
33 | 35 | import spoon.reflect.code.CtBreak; |
| 36 | +import spoon.reflect.code.CtCatch; |
34 | 37 | import spoon.reflect.code.CtConditional; |
35 | 38 | import spoon.reflect.code.CtContinue; |
36 | 39 | import spoon.reflect.code.CtDo; |
|
53 | 56 | import spoon.reflect.code.CtStatement; |
54 | 57 | import spoon.reflect.code.CtThisAccess; |
55 | 58 | import spoon.reflect.code.CtThrow; |
| 59 | +import spoon.reflect.code.CtTry; |
| 60 | +import spoon.reflect.code.CtTryWithResource; |
56 | 61 | import spoon.reflect.code.CtUnaryOperator; |
57 | 62 | import spoon.reflect.code.CtVariableAccess; |
58 | 63 | import spoon.reflect.code.CtVariableRead; |
@@ -474,6 +479,95 @@ public void visitCtIf(CtIf ifElement) { |
474 | 479 | context.variablesFinishIfCombination(); |
475 | 480 | } |
476 | 481 |
|
| 482 | + @Override |
| 483 | + public void visitCtTry(CtTry tryBlock) { |
| 484 | + Map<Variable, Optional<VariableInstance>> before = new IdentityHashMap<>(); |
| 485 | + for (RefinedVariable rv : context.getCtxVars()) |
| 486 | + if (rv instanceof Variable v) |
| 487 | + before.put(v, v.getLastInstance()); |
| 488 | + visitTryBranches(tryBlock, 0, before, new ArrayList<>()); |
| 489 | + scan(tryBlock.getFinalizer()); |
| 490 | + } |
| 491 | + |
| 492 | + @Override |
| 493 | + public void visitCtTryWithResource(CtTryWithResource tryWithResource) { |
| 494 | + scan(tryWithResource.getResources()); |
| 495 | + visitCtTry(tryWithResource); |
| 496 | + } |
| 497 | + |
| 498 | + /** |
| 499 | + * Visits the try block (branch 0) and its catch blocks (branches 1..n) from branch {@code i} like an if-else-if |
| 500 | + * chain with unknown conditions, so that after them each variable is combined from all the branches that can |
| 501 | + * complete normally |
| 502 | + * |
| 503 | + * @return whether some branch from {@code i} can complete normally |
| 504 | + */ |
| 505 | + private boolean visitTryBranches(CtTry tryBlock, int i, Map<Variable, Optional<VariableInstance>> before, |
| 506 | + List<Variable> changed) { |
| 507 | + if (i == tryBlock.getCatchers().size()) |
| 508 | + return visitTryBranch(tryBlock, i, before, changed); |
| 509 | + |
| 510 | + String pathVarName = String.format(Formats.FRESH, context.getCounter()); |
| 511 | + Predicate cond = Predicate.createVar(pathVarName); |
| 512 | + RefinedVariable freshRV = context.addInstanceToContext(pathVarName, factory.Type().BOOLEAN_PRIMITIVE, |
| 513 | + new Predicate(), tryBlock); |
| 514 | + vcChecker.addPathVariable(freshRV); |
| 515 | + |
| 516 | + context.variablesNewIfCombination(); |
| 517 | + context.variablesSetBeforeIf(); |
| 518 | + context.enterContext(); |
| 519 | + |
| 520 | + context.enterContext(); |
| 521 | + boolean thenCompletes = visitTryBranch(tryBlock, i, before, changed); |
| 522 | + if (thenCompletes) |
| 523 | + context.variablesSetThenIf(); |
| 524 | + context.exitContext(); |
| 525 | + |
| 526 | + context.newRefinementToVariableInContext(pathVarName, cond.negate()); |
| 527 | + context.enterContext(); |
| 528 | + boolean elseCompletes = visitTryBranches(tryBlock, i + 1, before, changed); |
| 529 | + if (elseCompletes) |
| 530 | + context.variablesSetElseIf(); |
| 531 | + context.exitContext(); |
| 532 | + |
| 533 | + if (thenCompletes == elseCompletes) { |
| 534 | + // after the join either branch may have run |
| 535 | + context.newRefinementToVariableInContext(pathVarName, new Predicate()); |
| 536 | + vcChecker.removePathVariable(freshRV); |
| 537 | + } else { |
| 538 | + context.newRefinementToVariableInContext(pathVarName, thenCompletes ? cond : cond.negate()); |
| 539 | + } |
| 540 | + context.exitContext(); |
| 541 | + context.variablesCombineFromIf(cond); |
| 542 | + context.variablesFinishIfCombination(); |
| 543 | + return thenCompletes || elseCompletes; |
| 544 | + } |
| 545 | + |
| 546 | + /** |
| 547 | + * Visits branch {@code i} of a try. A catch block may start after any statement of the try block, so the variables |
| 548 | + * changed in the try block are unknown at its start |
| 549 | + * |
| 550 | + * @return whether the branch can complete normally |
| 551 | + */ |
| 552 | + private boolean visitTryBranch(CtTry tryBlock, int i, Map<Variable, Optional<VariableInstance>> before, |
| 553 | + List<Variable> changed) { |
| 554 | + if (i == 0) { |
| 555 | + scan(tryBlock.getBody()); |
| 556 | + for (Map.Entry<Variable, Optional<VariableInstance>> e : before.entrySet()) |
| 557 | + if (e.getKey().getLastInstance().orElse(null) != e.getValue().orElse(null)) |
| 558 | + changed.add(e.getKey()); |
| 559 | + return canCompleteNormally(tryBlock.getBody()); |
| 560 | + } |
| 561 | + CtCatch catcher = tryBlock.getCatchers().get(i - 1); |
| 562 | + for (Variable v : changed) { |
| 563 | + String name = String.format(Formats.INSTANCE, v.getName(), context.getCounter()); |
| 564 | + context.addInstanceToContext(name, v.getType(), new Predicate(), catcher); |
| 565 | + context.addRefinementInstanceToVariable(v.getName(), name); |
| 566 | + } |
| 567 | + scan(catcher); |
| 568 | + return canCompleteNormally(catcher.getBody()); |
| 569 | + } |
| 570 | + |
477 | 571 | /** |
478 | 572 | * A condition is uninformative when its refinement is the trivial {@code true} predicate yet the expression itself |
479 | 573 | * is not a boolean literal — i.e. the verifier has no symbolic information to relate the branch to. Treating such a |
|
0 commit comments