|
56 | 56 | import org.codehaus.groovy.ast.expr.AttributeExpression; |
57 | 57 | import org.codehaus.groovy.ast.expr.BinaryExpression; |
58 | 58 | import org.codehaus.groovy.ast.expr.BitwiseNegationExpression; |
| 59 | +import org.codehaus.groovy.ast.expr.BooleanExpression; |
59 | 60 | import org.codehaus.groovy.ast.expr.CastExpression; |
60 | 61 | import org.codehaus.groovy.ast.expr.ClassExpression; |
61 | 62 | import org.codehaus.groovy.ast.expr.ClosureExpression; |
|
268 | 269 | import static org.codehaus.groovy.syntax.Types.INTDIV_EQUAL; |
269 | 270 | import static org.codehaus.groovy.syntax.Types.KEYWORD_IN; |
270 | 271 | import static org.codehaus.groovy.syntax.Types.KEYWORD_INSTANCEOF; |
| 272 | +import static org.codehaus.groovy.syntax.Types.LOGICAL_AND; |
271 | 273 | import static org.codehaus.groovy.syntax.Types.LOGICAL_OR; |
272 | 274 | import static org.codehaus.groovy.syntax.Types.MINUS_MINUS; |
273 | 275 | import static org.codehaus.groovy.syntax.Types.MOD; |
@@ -4636,13 +4638,15 @@ public void visitIfElse(final IfStatement ifElse) { |
4636 | 4638 |
|
4637 | 4639 | // GROOVY-6429: reverse instanceof tracking |
4638 | 4640 | typeCheckingContext.pushTemporaryTypeInfo(); |
4639 | | - tti.forEach(this::putNotInstanceOfTypeInfo); |
| 4641 | + // GROOVY-11983: only invert when the boolean's falsity pins down each instanceof |
| 4642 | + boolean canInvert = canInvertNarrowingForElseBranch(ifElse.getBooleanExpression()); |
| 4643 | + if (canInvert) tti.forEach(this::putNotInstanceOfTypeInfo); |
4640 | 4644 |
|
4641 | 4645 | elsePath.visit(this); |
4642 | 4646 |
|
4643 | 4647 | typeCheckingContext.popTemporaryTypeInfo(); |
4644 | 4648 | // GROOVY-8523: propagate tracking to outer scope; keep simple for now |
4645 | | - if (elsePath.isEmpty() && !GeneralUtils.maybeFallsThrough(thenPath)) { |
| 4649 | + if (canInvert && elsePath.isEmpty() && !GeneralUtils.maybeFallsThrough(thenPath)) { |
4646 | 4650 | tti.forEach(this::putNotInstanceOfTypeInfo); |
4647 | 4651 | } |
4648 | 4652 | } finally { |
@@ -4837,7 +4841,11 @@ public void visitTernaryExpression(final TernaryExpression expression) { |
4837 | 4841 | Map<Object, List<ClassNode>> tti = typeCheckingContext.temporaryIfBranchTypeInformation.pop(); |
4838 | 4842 |
|
4839 | 4843 | typeCheckingContext.pushTemporaryTypeInfo(); |
4840 | | - tti.forEach(this::putNotInstanceOfTypeInfo); // GROOVY-8412 |
| 4844 | + // GROOVY-8412 / GROOVY-11983: only invert when sound (no LOGICAL_AND in condition) |
| 4845 | + if (!(expression instanceof ElvisOperatorExpression) |
| 4846 | + && canInvertNarrowingForElseBranch(expression.getBooleanExpression())) { |
| 4847 | + tti.forEach(this::putNotInstanceOfTypeInfo); |
| 4848 | + } |
4841 | 4849 | Expression falseExpression = expression.getFalseExpression(); |
4842 | 4850 | ClassNode typeOfFalse = visitValueExpression(falseExpression); |
4843 | 4851 | typeCheckingContext.popTemporaryTypeInfo(); |
@@ -6645,6 +6653,38 @@ private void putNotInstanceOfTypeInfo(final Object key, final Collection<ClassNo |
6645 | 6653 | tti.addAll(types); // stash negative type(s) |
6646 | 6654 | } |
6647 | 6655 |
|
| 6656 | + /** |
| 6657 | + * GROOVY-11983: Whether the temporary type info captured while visiting {@code expr} |
| 6658 | + * may be soundly inverted for the else branch of {@code if (expr) ... else ...} (or |
| 6659 | + * the false branch of a ternary, or the surrounding scope after an early-returning |
| 6660 | + * {@code if (expr) return}). Inversion is only sound when {@code !expr} pins down |
| 6661 | + * the negation of every individual instanceof / !instanceof check inside it. A |
| 6662 | + * logical-AND breaks that: the else of {@code A && B} is reached when at least one |
| 6663 | + * of {@code A}, {@code B} is false, not necessarily both — so an instanceof inside |
| 6664 | + * either operand cannot be safely inverted. In particular, a {@code !instanceof} |
| 6665 | + * inside an AND would otherwise be unwrapped into a positive smart-cast, producing |
| 6666 | + * an unsound {@code checkcast} and a runtime ClassCastException. |
| 6667 | + */ |
| 6668 | + private static boolean canInvertNarrowingForElseBranch(final Expression expr) { |
| 6669 | + if (expr instanceof BinaryExpression be) { |
| 6670 | + int op = be.getOperation().getType(); |
| 6671 | + if (op == LOGICAL_AND) return false; |
| 6672 | + if (op == LOGICAL_OR) { |
| 6673 | + return canInvertNarrowingForElseBranch(be.getLeftExpression()) |
| 6674 | + && canInvertNarrowingForElseBranch(be.getRightExpression()); |
| 6675 | + } |
| 6676 | + return true; // instanceof, ==, comparisons, etc. |
| 6677 | + } |
| 6678 | + if (expr instanceof BooleanExpression be) return canInvertNarrowingForElseBranch(be.getExpression()); |
| 6679 | + if (expr instanceof NotExpression ne) return canInvertNarrowingForElseBranch(ne.getExpression()); |
| 6680 | + if (expr instanceof TernaryExpression te) { |
| 6681 | + return canInvertNarrowingForElseBranch(te.getBooleanExpression()) |
| 6682 | + && canInvertNarrowingForElseBranch(te.getTrueExpression()) |
| 6683 | + && canInvertNarrowingForElseBranch(te.getFalseExpression()); |
| 6684 | + } |
| 6685 | + return true; |
| 6686 | + } |
| 6687 | + |
6648 | 6688 | private boolean optInstanceOfTypeInfo(final Expression expression, final ClassNode type) { |
6649 | 6689 | var tti = typeCheckingContext.temporaryIfBranchTypeInformation.peek().get(extractTemporaryTypeInfoKey(expression)); |
6650 | 6690 | if (tti != null) { assert !tti.isEmpty(); |
|
0 commit comments