From 616b08e28ebe78152650fa2e1e6eba2427a93b13 Mon Sep 17 00:00:00 2001 From: Alexander Weigl Date: Wed, 19 Aug 2026 18:03:24 +0200 Subject: [PATCH 1/4] add `x instanceof T y` --- key.core/src/main/antlr4/JavaKeYLexer.g4 | 2 + key.core/src/main/antlr4/JavaKeYParser.g4 | 2 + .../expression/operator/BinaryOperator.java | 14 +- .../operator/InstanceofPattern.java | 142 ++++++++++ .../ilkd/key/java/loader/JP2KeYConverter.java | 45 +++- .../java/visitor/BindingVariableVisitor.java | 128 +++++++++ .../key/java/visitor/CreatingASTVisitor.java | 11 + .../ilkd/key/java/visitor/JavaASTVisitor.java | 5 + .../de/uka/ilkd/key/java/visitor/Visitor.java | 2 + .../varexp/TacletBuilderManipulators.java | 14 +- .../de/uka/ilkd/key/pp/PrettyPrinter.java | 22 ++ .../conditions/AbnormallyTerminatesCond.java | 59 ++++ .../AlwaysAbnormallyTerminatingAnalysis.java | 255 ++++++++++++++++++ .../conditions/BindingExpressionCond.java | 102 +++++++ .../jml/translation/JMLSpecFactory.java | 40 ++- .../de/uka/ilkd/key/proof/rules/java5.key | 4 +- .../de/uka/ilkd/key/proof/rules/javaRules.key | 78 +++++- ...waysAbnormallyTerminatingAnalysisTest.java | 127 +++++++++ .../Java/InstanceOfPattern/README.txt | 72 +++++ .../Java/InstanceOfPattern/project.key | 3 + .../src/InstanceOfPatternExample.java | 149 ++++++++++ key.ui/examples/index/samplesIndex.txt | 1 + 22 files changed, 1266 insertions(+), 11 deletions(-) create mode 100644 key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/InstanceofPattern.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/java/visitor/BindingVariableVisitor.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AbnormallyTerminatesCond.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AlwaysAbnormallyTerminatingAnalysis.java create mode 100644 key.core/src/main/java/de/uka/ilkd/key/rule/conditions/BindingExpressionCond.java create mode 100644 key.core/src/test/java/de/uka/ilkd/key/rule/conditions/AlwaysAbnormallyTerminatingAnalysisTest.java create mode 100644 key.ui/examples/Java/InstanceOfPattern/README.txt create mode 100644 key.ui/examples/Java/InstanceOfPattern/project.key create mode 100644 key.ui/examples/Java/InstanceOfPattern/src/InstanceOfPatternExample.java diff --git a/key.core/src/main/antlr4/JavaKeYLexer.g4 b/key.core/src/main/antlr4/JavaKeYLexer.g4 index 9ea0ac834ee..0a8e4f57d58 100644 --- a/key.core/src/main/antlr4/JavaKeYLexer.g4 +++ b/key.core/src/main/antlr4/JavaKeYLexer.g4 @@ -57,6 +57,8 @@ NEWLABEL : '\\newLabel'; CONTAINS_ASSIGNMENT : '\\containsAssignment'; // label occurs again for character `!' NOTFREEIN : '\\notFreeIn'; +ISBINDINGEXPR : '\\isBindingExpr'; +ALWAYS_ABNORMALLY_TERMINATES: '\\alwaysAbnormallyTerminates'; STATIC : '\\static'; STATICMETHODREFERENCE : '\\staticMethodReference'; MAXEXPANDMETHOD : '\\mayExpandMethod'; diff --git a/key.core/src/main/antlr4/JavaKeYParser.g4 b/key.core/src/main/antlr4/JavaKeYParser.g4 index 48c37383526..84d79ebe253 100644 --- a/key.core/src/main/antlr4/JavaKeYParser.g4 +++ b/key.core/src/main/antlr4/JavaKeYParser.g4 @@ -247,6 +247,8 @@ varexpId: // weigl, 2021-03-12: This will be later just an arbitrary identifier. | STRICT ISSUBTYPE | DISJOINTMODULONULL | NOTFREEIN + | ISBINDINGEXPR + | ALWAYS_ABNORMALLY_TERMINATES | HASSORT | NEWLABEL | ISREFERENCE diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/BinaryOperator.java b/key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/BinaryOperator.java index 4ae33f4fbbc..1eb895cad04 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/BinaryOperator.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/BinaryOperator.java @@ -8,11 +8,7 @@ import de.uka.ilkd.key.java.Services; import de.uka.ilkd.key.java.TypeConverter; -import de.uka.ilkd.key.java.ast.Comment; -import de.uka.ilkd.key.java.ast.PositionInfo; -import de.uka.ilkd.key.java.ast.ProgramElement; -import de.uka.ilkd.key.java.ast.ProgramElementWithKind; -import de.uka.ilkd.key.java.ast.SourceData; +import de.uka.ilkd.key.java.ast.*; import de.uka.ilkd.key.java.ast.abstraction.KeYJavaType; import de.uka.ilkd.key.java.ast.expression.Expression; import de.uka.ilkd.key.java.ast.expression.Operator; @@ -110,4 +106,12 @@ public boolean equals(Object o) { protected int computeHashCode() { return 0x01000193 * super.computeHashCode() + kind.hashCode(); } + + public Expression getLeft() { + return this.getExpressionAt(0); + } + + public Expression getRight() { + return this.getExpressionAt(1); + } } diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/InstanceofPattern.java b/key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/InstanceofPattern.java new file mode 100644 index 00000000000..8e3166edcd6 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/java/ast/expression/operator/InstanceofPattern.java @@ -0,0 +1,142 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.java.ast.expression.operator; + +import java.util.List; +import java.util.Objects; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.java.ast.*; +import de.uka.ilkd.key.java.ast.abstraction.KeYJavaType; +import de.uka.ilkd.key.java.ast.abstraction.PrimitiveType; +import de.uka.ilkd.key.java.ast.declaration.VariableSpecification; +import de.uka.ilkd.key.java.ast.expression.Expression; +import de.uka.ilkd.key.java.ast.reference.ExecutionContext; +import de.uka.ilkd.key.java.ast.reference.TypeReference; +import de.uka.ilkd.key.java.visitor.Visitor; + +import org.key_project.util.ExtList; +import org.key_project.util.collection.ImmutableArray; + +import org.jspecify.annotations.NullMarked; + +/** + * Instanceof with pattern matching (Java 16+). + * Supports syntax: expr instanceof Type varName + * where varName is bound as a local variable in the true branch. + */ +@NullMarked +public class InstanceofPattern extends TypeOperator { + protected final VariableSpecification patternVariable; + + public InstanceofPattern(ExtList children) { + super(children); + this.patternVariable = + Objects.requireNonNull(children.get(VariableSpecification.class)); + } + + public InstanceofPattern(Expression lhs, TypeReference type, VariableSpecification patternVar) { + super(lhs, type); + this.patternVariable = Objects.requireNonNull(patternVar); + } + + public InstanceofPattern(PositionInfo pi, List c, Expression lhs, TypeReference type, + VariableSpecification patternVar) { + super(pi, c, new ImmutableArray<>(lhs), type); + this.patternVariable = Objects.requireNonNull(patternVar); + } + + /** + * Returns the number of children of this node. + * + * @return an int giving the number of children of this node + */ + public int getChildCount() { + int result = 0; + if (children != null) { + result += children.size(); + } + if (typeReference != null) { + result++; + } + + result++; + assert result == 3; + return result; + } + + public SourceElement getLastElement() { + return patternVariable; + } + + /** + * Returns the child at the specified index in this node's "virtual" child array + * + * @param index an index into this node's "virtual" child array + * @return the program element at the given position + * @throws ArrayIndexOutOfBoundsException if index is out of bounds + */ + public ProgramElement getChildAt(int index) { + return switch (index) { + case 0 -> children.get(0); + case 1 -> typeReference; + case 2 -> patternVariable; + default -> throw new IllegalStateException("Unexpected value: " + index); + }; + } + + /** + * Get arity. + * + * @return the int value. + */ + public int getArity() { + return 1; + } + + /** + * Get precedence. + * + * @return the int value. + */ + public int getPrecedence() { + return 5; + } + + /** + * Get notation. + * + * @return the int value. + */ + public int getNotation() { + return POSTFIX; + } + + /** + * Get the pattern variable bound by this instanceof expression. + * + * @return the VariableSpecification for the pattern variable, or null if no pattern + */ + public VariableSpecification getPatternVariable() { + return patternVariable; + } + + /** + * calls the corresponding method of a visitor in order to perform some action/transformation on + * this element + * + * @param v the Visitor + */ + public void visit(Visitor v) { + v.performActionOnInstanceofPattern(this); + } + + public KeYJavaType getKeYJavaType(Services javaServ) { + return javaServ.getJavaInfo().getKeYJavaType(PrimitiveType.JAVA_BOOLEAN); + } + + public KeYJavaType getKeYJavaType(Services javaServ, ExecutionContext ec) { + return getKeYJavaType(javaServ); + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/loader/JP2KeYConverter.java b/key.core/src/main/java/de/uka/ilkd/key/java/loader/JP2KeYConverter.java index 773c8bcbb0b..20b7066e6f5 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/loader/JP2KeYConverter.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/loader/JP2KeYConverter.java @@ -924,6 +924,31 @@ public Object visit(InstanceOfExpr n, Void arg) { List c = createComments(n); Expression lhs = accept(n.getExpression()); TypeReference type = requireTypeReference(n.getType()); + + if (n.getName().isPresent()) { + final SimpleName nName = n.getName().get(); + var name = nName.getIdentifier(); + + PositionInfo piVar = createPositionInfo(nName); + List cVar = createComments(nName); + + IProgramVariable pvar; + if (!name.startsWith("#")) { + pvar = new LocationVariable( + new ProgramElementName(name), type.getKeYJavaType()); + } else { + pvar = (ProgramSV) lookupSchemaVariable(nName); + } + + final var vs = + new VariableSpecification(piVar, cVar, null, pvar, 0, type.getKeYJavaType()); + return new InstanceofPattern(pi, c, lhs, type, vs); + } + + if (n.getPattern().isPresent()) { + reportUnsupportedElement(n); + } + return new Instanceof(pi, c, lhs, type); } @@ -2178,7 +2203,25 @@ public Object visit(RecordPatternExpr n, Void arg) { @Override public Object visit(TypePatternExpr n, Void arg) { - return reportUnsupportedElement(n); + // weigl: This is somehow crude and called by a type resolution in NameExpr, expecting a + // VariableDeclaration + var nName = n.getName(); + TypeRef type = accept(n.getType()); + var name = nName.getIdentifier(); + + PositionInfo piVar = createPositionInfo(nName); + List cVar = createComments(nName); + + IProgramVariable pvar; + if (!name.startsWith("#")) { + pvar = new LocationVariable( + new ProgramElementName(name), type.getKeYJavaType()); + } else { + pvar = (ProgramSV) lookupSchemaVariable(nName); + } + + ImmutableArray mods = map(n.getModifiers()); + return new LocalVariableDeclaration(mods, type, new VariableSpecification(pvar)); } @Override diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/BindingVariableVisitor.java b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/BindingVariableVisitor.java new file mode 100644 index 00000000000..cd0fe5bee21 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/BindingVariableVisitor.java @@ -0,0 +1,128 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.java.visitor; + +import java.util.*; + +import de.uka.ilkd.key.java.ast.expression.Expression; +import de.uka.ilkd.key.java.ast.expression.ParenthesizedExpression; +import de.uka.ilkd.key.java.ast.expression.operator.*; +import de.uka.ilkd.key.logic.op.LocationVariable; + +/** + * + * @author Alexander Weigl + * @version 1 (20.08.26) + */ +public class BindingVariableVisitor { + public record Bindings(Map whenTrue, + Map whenFalse) { + public Bindings(Map whenTrue, + Map whenFalse) { + this.whenTrue = Collections.unmodifiableMap(whenTrue); + this.whenFalse = Collections.unmodifiableMap(whenFalse); + } + + static Bindings empty() { + return new Bindings(Map.of(), Map.of()); + } + + @Override + public String toString() { + return "Bindings{whenTrue=" + whenTrue + ", whenFalse=" + whenFalse + "}"; + } + } + + public static Bindings analyze(Expression e) { + if (e instanceof InstanceofPattern io) { + // §6.3.1.5 — a instanceof p + // "when true" = variables declared by p. No "when false" rule: + // it can't be determined at compile time that the match failed + // in a way that still binds anything. + var name = (LocationVariable) io.getPatternVariable().getProgramVariable(); + var expr = io.getExpressionAt(0); + return new Bindings(Map.of(name, expr), Map.of()); + } else if (e instanceof BinaryOperator bo) { + if (bo.getKind() == BinaryOperatorKind.LOGICAL_AND) { + // §6.3.1.1 — a && b + Bindings a = analyze(bo.getExpressionAt(0)); + Bindings b = analyze(bo.getExpressionAt(1)); + + requireDisjoint(a.whenTrue, b.whenTrue, + "both operands of && declare pattern variable when true"); + requireDisjoint(a.whenFalse, b.whenFalse, + "both operands of && declare pattern variable when false"); + + // a's "when true" set is definitely matched at b (scope flows rightward). + var whenTrue = union(a.whenTrue, b.whenTrue); + // No rule for a && b when false: can't tell at compile time which + // operand caused the false result. + return new Bindings(whenTrue, Map.of()); + } else if (bo.getKind() == BinaryOperatorKind.LOGICAL_OR) { + // §6.3.1.2 — a || b (mirror image of &&) + Bindings a = analyze(bo.getExpressionAt(0)); + Bindings b = analyze(bo.getExpressionAt(1)); + + requireDisjoint(a.whenTrue, b.whenTrue, + "both operands of || declare pattern variable when true"); + requireDisjoint(a.whenFalse, b.whenFalse, + "both operands of || declare pattern variable when false"); + // a's "when false" set is definitely matched at b. + var whenFalse = union(a.whenFalse, b.whenFalse); + // No rule for a || b when true. + return new Bindings(Map.of(), whenFalse); + } + } else if (e instanceof UnaryOperator not && not.kind == UnaryOperatorKind.LOGICAL_NOT) { + // §6.3.1.3 — !a: true/false swap. + Bindings a = analyze(not.getExpressionAt(0)); + return new Bindings(a.whenFalse, a.whenTrue); + } else if (e instanceof Conditional c) { + // §6.3.1.4 — a ? b : c + Bindings condB = analyze(c.getExpressionAt(0)); + Bindings thenB = analyze(c.getExpressionAt(1)); + Bindings elseB = analyze(c.getExpressionAt(2)); + // (condB.whenTrue is definitely matched at b; condB.whenFalse at c — + // see DefinitelyMatchedAt below if you need those sets directly.) + + requireDisjoint(condB.whenTrue, elseB.whenTrue, + "cond(true) and else-branch(true) both declare pattern variable"); + requireDisjoint(condB.whenTrue, elseB.whenFalse, + "cond(true) and else-branch(false) both declare pattern variable"); + requireDisjoint(condB.whenFalse, thenB.whenTrue, + "cond(false) and then-branch(true) both declare pattern variable"); + requireDisjoint(condB.whenFalse, thenB.whenFalse, + "cond(false) and then-branch(false) both declare pattern variable"); + requireDisjoint(thenB.whenTrue, elseB.whenTrue, + "then-branch(true) and else-branch(true) both declare pattern variable"); + requireDisjoint(thenB.whenFalse, elseB.whenFalse, + "then-branch(false) and else-branch(false) both declare pattern variable"); + + + // No rule for introducing bindings from a ? b : c itself, in either + // direction: it can't be known at compile time whether a is true. + return Bindings.empty(); + } else if (e instanceof ParenthesizedExpression p) { + // §6.3.1.7 — (a): pass through unchanged. + return analyze(p.getExpressionAt(0)); + + } + return Bindings.empty(); + } + + private static Map union(Map a, + Map b) { + var result = new LinkedHashMap<>(a); + result.putAll(b); + return result; + } + + private static void requireDisjoint(Map a, + Map b, String message) { + for (LocationVariable name : a.keySet()) { + if (b.containsKey(name)) { + throw new IllegalStateException(message + ": '" + name + "'"); + } + } + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java index 8a7e73b6849..bd61369a845 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/CreatingASTVisitor.java @@ -469,6 +469,17 @@ ProgramElement createNewElement(ExtList changeList) { def.doAction(x); } + @Override + public void performActionOnInstanceofPattern(InstanceofPattern x) { + DefaultAction def = new DefaultAction(x) { + @Override + ProgramElement createNewElement(ExtList changeList) { + return new InstanceofPattern(changeList); + } + }; + def.doAction(x); + } + @Override public void performActionOnBreak(Break x) { DefaultAction def = new DefaultAction(x) { diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/JavaASTVisitor.java b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/JavaASTVisitor.java index 10619d18977..7dea53d312e 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/JavaASTVisitor.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/JavaASTVisitor.java @@ -248,6 +248,11 @@ public void performActionOnExactInstanceof(ExactInstanceof x) { doDefaultAction(x); } + @Override + public void performActionOnInstanceofPattern(InstanceofPattern x) { + doDefaultAction(x); + } + @Override public void performActionOnExecutionContext(ExecutionContext x) { doDefaultAction(x); diff --git a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/Visitor.java b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/Visitor.java index 4cc400fdb05..cb47e524200 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/java/visitor/Visitor.java +++ b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/Visitor.java @@ -157,6 +157,8 @@ public interface Visitor { void performActionOnExactInstanceof(ExactInstanceof x); + void performActionOnInstanceofPattern(InstanceofPattern x); + void performActionOnNew(New x); void performActionOnTypeCast(TypeCast x); diff --git a/key.core/src/main/java/de/uka/ilkd/key/nparser/varexp/TacletBuilderManipulators.java b/key.core/src/main/java/de/uka/ilkd/key/nparser/varexp/TacletBuilderManipulators.java index 67065ce497c..47e35ab0051 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/nparser/varexp/TacletBuilderManipulators.java +++ b/key.core/src/main/java/de/uka/ilkd/key/nparser/varexp/TacletBuilderManipulators.java @@ -167,6 +167,17 @@ public void apply(TacletBuilder tacletBuilder, Object[] arguments, public static final TacletBuilderCommand NEW_LOCAL_VARS = new ConstructorBasedBuilder( "newLocalVars", NewLocalVarsCondition.class, SV, SV, SV, SV); + public static final TacletBuilderCommand BINDING_EXPRESSION_FULL = new ConstructorBasedBuilder( + "isBindingExpr", BindingExpressionCond.class, SV, SV, SV); + + public static final TacletBuilderCommand ABNORMALLY_TERMINATE = new ConstructorBasedBuilder( + "alwaysAbnormallyTerminates", BindingExpressionCond.class, SV); + + + public static final TacletBuilderCommand BINDING_EXPRESSION_SIMPLE = + new ConstructorBasedBuilder( + "isBindingExpr", BindingExpressionCond.class, SV); + static class NotFreeInTacletBuilderCommand extends AbstractTacletBuilderCommand { public NotFreeInTacletBuilderCommand(@NonNull ArgumentType... argumentsTypes) { super("notFreeIn", argumentsTypes); @@ -382,7 +393,8 @@ public IsLabeledCondition build(Object[] arguments, List parameters, applyUpdateOnRigid, DROP_EFFECTLESS_ELEMENTARIES, SIMPLIFY_ITE_UPDATE, SUBFORMULAS, STATIC_FIELD, MODEL_FIELD, SUBFORMULA, DROP_EFFECTLESS_STORES, EQUAL_UNIQUE, META_DISJOINT, - IS_OBSERVER, CONSTANT, HAS_SORT, LABEL, NEW_LABEL, HAS_ELEM_SORT, IS_IN_STRICTFP); + IS_OBSERVER, CONSTANT, HAS_SORT, LABEL, NEW_LABEL, HAS_ELEM_SORT, IS_IN_STRICTFP, + BINDING_EXPRESSION_FULL, BINDING_EXPRESSION_SIMPLE, ABNORMALLY_TERMINATE); register(STORE_TERM_IN, STORE_STMT_IN, HAS_INVARIANT, GET_INVARIANT, GET_FREE_INVARIANT, GET_VARIANT, IS_LABELED); loadWithServiceLoader(); diff --git a/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java b/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java index 09348df07c7..9108e6dbb35 100644 --- a/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java +++ b/key.core/src/main/java/de/uka/ilkd/key/pp/PrettyPrinter.java @@ -1310,6 +1310,28 @@ public void performActionOnExactInstanceof(ExactInstanceof x) { printInstanceOfLike(x, "exactInstanceof"); } + @Override + public void performActionOnInstanceofPattern(InstanceofPattern x) { + boolean addParentheses = x.isToBeParenthesized(); + if (addParentheses) { + layouter.print("("); + } + if (x.getArguments() != null) { + x.getExpressionAt(0).visit(this); + } + layouter.print(" instanceof "); + if (x.getTypeReference() != null) { + x.getTypeReference().visit(this); + } + if (x.getPatternVariable() != null) { + layouter.print(" "); + x.getPatternVariable().visit(this); + } + if (addParentheses) { + layouter.print(")"); + } + } + @Override public void performActionOnNew(New x) { boolean addParentheses = x.isToBeParenthesized(); diff --git a/key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AbnormallyTerminatesCond.java b/key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AbnormallyTerminatesCond.java new file mode 100644 index 00000000000..75f718058a7 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AbnormallyTerminatesCond.java @@ -0,0 +1,59 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.rule.conditions; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.java.ast.Statement; + +import org.key_project.logic.LogicServices; +import org.key_project.logic.SyntaxElement; +import org.key_project.logic.op.sv.SchemaVariable; +import org.key_project.prover.rules.VariableCondition; +import org.key_project.prover.rules.instantiation.MatchResultInfo; +import org.key_project.util.collection.ImmutableArray; + +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; + +/// This is a variable condition, that checks if a given variable binds new variables. +/// +/// @author Alexander Weigl +/// @version 1 (20.08.26) +public class AbnormallyTerminatesCond implements VariableCondition { + /// Is negated + private final boolean negated; + + /// A schema variable representing a Java Expression. + private final SchemaVariable svStmts; + + public AbnormallyTerminatesCond(SchemaVariable svStmts, boolean negated) { + this.svStmts = svStmts; + this.negated = negated; + } + + @Override + public @Nullable MatchResultInfo check( + @Nullable SchemaVariable var, @Nullable SyntaxElement instCandidate, + @NonNull MatchResultInfo matchCond, @NonNull LogicServices services) { + var svInst = (de.uka.ilkd.key.rule.inst.SVInstantiations) matchCond.getInstantiations(); + var s = svInst.getInstantiation(svStmts); + boolean isAbnormallyTerminating = false; + + var analysis = new AlwaysAbnormallyTerminatingAnalysis(null, (Services) services); + if (s instanceof ImmutableArray stmts) { + + } else if (s instanceof Statement stmts) { + + } else { + matchCond = null; + } + + + if (negated) { + return isAbnormallyTerminating ? null : matchCond; + } else { + return isAbnormallyTerminating ? matchCond : null; + } + } +} diff --git a/key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AlwaysAbnormallyTerminatingAnalysis.java b/key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AlwaysAbnormallyTerminatingAnalysis.java new file mode 100644 index 00000000000..da45e54af4f --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/rule/conditions/AlwaysAbnormallyTerminatingAnalysis.java @@ -0,0 +1,255 @@ +/* This file is part of KeY - https://key-project.org + * KeY is licensed under the GNU General Public License Version 2 + * SPDX-License-Identifier: GPL-2.0-only */ +package de.uka.ilkd.key.rule.conditions; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +import de.uka.ilkd.key.java.Services; +import de.uka.ilkd.key.java.ast.Label; +import de.uka.ilkd.key.java.ast.Statement; +import de.uka.ilkd.key.java.ast.StatementBlock; +import de.uka.ilkd.key.java.ast.abstraction.KeYJavaType; +import de.uka.ilkd.key.java.ast.expression.Expression; +import de.uka.ilkd.key.java.ast.expression.literal.BooleanLiteral; +import de.uka.ilkd.key.java.ast.expression.operator.BinaryOperator; +import de.uka.ilkd.key.java.ast.expression.operator.BinaryOperatorKind; +import de.uka.ilkd.key.java.ast.reference.ExecutionContext; +import de.uka.ilkd.key.java.ast.statement.*; + +import org.key_project.util.collection.ImmutableArray; + +import org.jspecify.annotations.Nullable; + +/// This analysis returns true iff a statement has the possibility to terminate normally +/// iff not every run terminates abnormally. +/// +class AlwaysAbnormallyTerminatingAnalysis { + + record TerminationReason(boolean isReturn, Set isThrow, String breakTo, + String continueTo) { + public TerminationReason(boolean isReturn) { + this(isReturn, Set.of(), null, null); + } + + public TerminationReason(KeYJavaType keYJavaType) { + this(false, Set.of(keYJavaType), null, null); + } + + public boolean isAbnormallTerminating() { + return isReturn || !isThrow.isEmpty() || breakTo != null && continueTo != null; + } + } + + private final KeYJavaType assertionException; + private final LinkedList