diff --git a/AGENT.md b/AGENT.md new file mode 100644 index 00000000000..f1065315eea --- /dev/null +++ b/AGENT.md @@ -0,0 +1,217 @@ +# Agent Guidelines for KeY Project + +This document provides guidelines and rules for AI agents working on the KeY deductive Java program verifier project. + +## Project Overview + +**KeY** is an interactive theorem prover for formal verification and analysis of Java programs. It supports: +- Formal verification of Java programs with Java Modeling Language specifications +- Symbolic program execution +- First-order reasoning +- Test case generation + +**License**: GPL v2 (all contributions must be compatible) + +## Technical Stack + +### Build System +- **Gradle** with Groovy DSL (`build.gradle`) +- Multi-module project structure +- Java 21 compatibility required + +```bash +# Common build commands +./gradlew classes # Compile all classes +./gradlew testClasses # Compile all test classes +./gradlew test # Run full test suite (hours) +./gradlew testFast # Run lightweight tests (minutes) +./gradlew spotlessApply # Reformat source code +./gradlew :key.:test --tests "." # Specific test +./gradlew :key.ui:run # Run KeY UI +./gradlew :key.ui:run --args='--experimental' # With experimental features +./gradlew :key.ui:shadowJar # Create fat jar +``` + +### Key Technologies +- **Java 21** (source/target compatibility) +- **JUnit 6** (Jupiter) for testing +- **AssertJ** for assertions +- **SLF4J** with Logback for logging +- **Spotless** for code formatting +- **ANTLR 4** for parser generation + +### Dependencies Management +- Version Catalog (`libs.*` notation in build files) +- All dependencies must be on Maven Central repository +- Use JSpecify for nullability annotations + +## Project Structure + +### Core Modules +``` +key.util - Base utilities +key.core - Core verification engine +key.ui - GUI application +key.ncore - Logic layer +key.ncore.* - Calculus layers +``` +Extension Modules are marked by name `keyext.*` + +### Module Naming Convention +- Base components: `key.*` +- Extensions/plugins: `keyext.*` +- Follow Maven standard directory layout + +## Coding Conventions + +### Java Code Style +- Follow [Java Code Conventions](https://keyproject.github.io/key-docs/devel/CodingConventions/) +- Use Spotless plugin for automatic formatting +- Indentation: 4 spaces (no tabs) +- Line length: Reasonable limits (typically 120 chars) +- Braces: Always use braces for control structures + +### Package Structure +- Root packages: `de.uka.ilkd.key.*`, `org.key_project.*` +- Keep related classes in same package +- Avoid circular dependencies between modules + +### Documentation +- JavaDoc for public APIs +- Inline comments for complex logic +- Reference official docs: https://keyproject.github.io/key-docs/devel/ + +## Testing Guidelines + +### Test Organization +- Tests in `src/test/java` mirroring source structure +- Test fixtures in `src/testFixtures/java` +- Use JUnit 5 Jupiter API +- Prefer AssertJ for fluent assertions + +### Writing Tests +```java +// Example pattern +@Test +void shouldVerifyExpectedBehavior() { + // Given + // When + // Then - use AssertJ + assertThat(result).isEqualTo(expected); +} +``` + +### Running Tests +- Use `testFast` for quick feedback during development +- Use `test` before committing (comprehensive but slow) +- Debug with: `./gradlew test --debug-jvm` (attach at localhost:5005) + +## Quality Assurance + +### Automated Checks +All PRs are automatically checked via GitHub Actions: +- Unit tests execution +- Code formatting (Spotless) +- Static analysis (Checker Framework, SonarQube) +- License compliance + +### Pre-commit Checklist +1. Code compiles: `./gradlew classes` +2. Tests pass: `./gradlew testFast` +3. Reformatting: `./gradlew spotlessApply` +4. No new warnings introduced +5. GPL v2 license compatibility verified + +## Development Workflow + +### Branch Strategy +- Feature branches from main +- Descriptive branch names (e.g., `lastname/xxx`, `feature/xxx`, `fix/yyy`) +- Releases are in `releases/` and pre-releases in `prerelease/` +- Rebase before merging to keep history clean + +### Commit Messages +- Clear, descriptive messages +- Reference issues when applicable +- Follow conventional commits pattern + +### Pull Requests +1. Fork and create feature branch +2. Implement changes with tests +3. Ensure all CI checks pass +4. Open PR with clear description +5. Address review feedback +6. Squash/rebase as requested + +## Architecture Principles + +### Core Design +- Separation of concerns between layers +- Immutable data structures where possible +- Thread-safety considerations documented +- Performance-critical code profiled + +### Key Components +- **Proof Engine**: Sequent calculus-based theorem proving +- **SMT Integration**: Z3, cvc5, Princess solvers +- **GUI**: Java Swing with FlatLaf look-and-feel + +## Tools & Resources + +### Essential Links +- Homepage: https://key-project.org +- Developer Docs: https://keyproject.github.io/key-docs/devel/ +- Issue Tracker: https://github.com/KeYProject/key/issues +- Mailing List: key-all@lists.informatik.kit.edu + +### IDE Setup +- IntelliJ IDEA recommended (project includes `.idea` configs) +- Eclipse supported via gradle eclipse plugin +- Enable annotation processing for Checker Framework + +## Agent-Specific Rules + +### When Making Changes +1. **Understand context first**: Read existing code, tests, and documentation +2. **Follow patterns**: Match existing code style and architecture +3. **Test incrementally**: Verify each change compiles and tests pass +4. **Document decisions**: Add comments explaining non-obvious choices +5. **Respect licensing**: All code must be GPL v2 compatible + +### Communication +- Be explicit about assumptions and limitations +- Provide complete, functional code (no placeholders) +- Include usage examples when adding new features +- Reference relevant documentation or prior art + +### Code Review Expectations +- Manual review by core team required +- Automated checks must pass first +- Be prepared to iterate based on feedback +- Maintain backward compatibility when possible + +## Common Tasks Reference + +### Adding a New Module +1. Create directory following pattern: `key.module.name/` +2. Add to `settings.gradle` +3. Configure `build.gradle` with dependencies +4. Apply standard plugins (java, spotless, checkerframework) + +### Modifying Build Configuration +1. Edit root `build.gradle` for global changes +2. Edit subproject `build.gradle` for module-specific changes +3. Update version catalog if adding dependencies +4. Test build locally before committing + +### Debugging Issues +1. Check existing issues/PRs for similar problems +2. Enable debug logging +3. Use debugger attachment (localhost:5005) +4. Consult developer documentation +5. Ask on mailing list if stuck + +--- + +*Last updated: August 2026* +*For questions, refer to the [KeY Developer Documentation](https://keyproject.github.io/key-docs/devel/)* 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..4c114dfd693 --- /dev/null +++ b/key.core/src/main/java/de/uka/ilkd/key/java/visitor/BindingVariableVisitor.java @@ -0,0 +1,132 @@ +/* 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(); + Expression expr = io.getExpressionAt(0); + + // add a cast + expr = new TypeCast(expr, io.getTypeReference()); + + 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