Skip to content

Commit 8bce18b

Browse files
authored
Fix #3452 (#3540)
2 parents 627d745 + 33185d2 commit 8bce18b

10 files changed

Lines changed: 220 additions & 45 deletions

File tree

key.core/src/main/java/de/uka/ilkd/key/api/ProofManagementApi.java

Lines changed: 1 addition & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,10 @@
1010

1111
import de.uka.ilkd.key.control.KeYEnvironment;
1212
import de.uka.ilkd.key.java.Services;
13-
import de.uka.ilkd.key.java.abstraction.KeYJavaType;
14-
import de.uka.ilkd.key.logic.op.IObserverFunction;
1513
import de.uka.ilkd.key.proof.init.ProofInputException;
1614
import de.uka.ilkd.key.proof.init.ProofOblInput;
1715
import de.uka.ilkd.key.speclang.Contract;
18-
import de.uka.ilkd.key.util.KeYTypeUtil;
1916

20-
import org.key_project.util.collection.ImmutableSet;
2117

2218
/**
2319
* This class serves as a facade to all functionalities that are needed for proof management, i.e.,
@@ -45,31 +41,7 @@ public Services getServices() {
4541
* exception here)
4642
*/
4743
public List<Contract> getProofContracts() {
48-
if (proofContracts.isEmpty()) {
49-
buildContracts();
50-
}
51-
return proofContracts;
52-
}
53-
54-
/**
55-
* constructs the possible proof contracts from the java info in the environment.
56-
*/
57-
private void buildContracts() {
58-
proofContracts.clear();
59-
Set<KeYJavaType> kjts = currentEnv.getJavaInfo().getAllKeYJavaTypes();
60-
for (KeYJavaType type : kjts) {
61-
if (!KeYTypeUtil.isLibraryClass(type)) {
62-
ImmutableSet<IObserverFunction> targets =
63-
currentEnv.getSpecificationRepository().getContractTargets(type);
64-
for (IObserverFunction target : targets) {
65-
ImmutableSet<Contract> contracts =
66-
currentEnv.getSpecificationRepository().getContracts(type, target);
67-
for (Contract contract : contracts) {
68-
proofContracts.add(contract);
69-
}
70-
}
71-
}
72-
}
44+
return currentEnv.getProofContracts();
7345
}
7446

7547
/**

key.core/src/main/java/de/uka/ilkd/key/control/KeYEnvironment.java

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,16 @@
44
package de.uka.ilkd.key.control;
55

66
import java.io.File;
7+
import java.util.ArrayList;
78
import java.util.List;
89
import java.util.Properties;
10+
import java.util.Set;
911
import java.util.function.Consumer;
1012

1113
import de.uka.ilkd.key.java.JavaInfo;
1214
import de.uka.ilkd.key.java.Services;
15+
import de.uka.ilkd.key.java.abstraction.KeYJavaType;
16+
import de.uka.ilkd.key.logic.op.IObserverFunction;
1317
import de.uka.ilkd.key.nparser.ProofScriptEntry;
1418
import de.uka.ilkd.key.proof.Proof;
1519
import de.uka.ilkd.key.proof.init.InitConfig;
@@ -20,6 +24,10 @@
2024
import de.uka.ilkd.key.proof.io.AbstractProblemLoader.ReplayResult;
2125
import de.uka.ilkd.key.proof.io.ProblemLoaderException;
2226
import de.uka.ilkd.key.proof.mgt.SpecificationRepository;
27+
import de.uka.ilkd.key.speclang.Contract;
28+
import de.uka.ilkd.key.util.KeYTypeUtil;
29+
30+
import org.key_project.util.collection.ImmutableSet;
2331

2432
import org.jspecify.annotations.Nullable;
2533

@@ -320,4 +328,26 @@ public boolean isDisposed() {
320328
public @Nullable ProofScriptEntry getProofScript() {
321329
return proofScript;
322330
}
331+
332+
/**
333+
* constructs the possible proof contracts from the java info in the environment.
334+
*/
335+
public List<Contract> getProofContracts() {
336+
var proofContracts = new ArrayList<Contract>();
337+
Set<KeYJavaType> kjts = getJavaInfo().getAllKeYJavaTypes();
338+
for (KeYJavaType type : kjts) {
339+
if (!KeYTypeUtil.isLibraryClass(type)) {
340+
ImmutableSet<IObserverFunction> targets =
341+
getSpecificationRepository().getContractTargets(type);
342+
for (IObserverFunction target : targets) {
343+
ImmutableSet<Contract> contracts =
344+
getSpecificationRepository().getContracts(type, target);
345+
for (Contract contract : contracts) {
346+
proofContracts.add(contract);
347+
}
348+
}
349+
}
350+
}
351+
return proofContracts;
352+
}
323353
}

key.core/src/main/java/de/uka/ilkd/key/proof/init/AbstractOperationPO.java

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import org.key_project.util.collection.ImmutableSLList;
3838
import org.key_project.util.collection.ImmutableSet;
3939

40+
import org.jspecify.annotations.Nullable;
41+
4042
/**
4143
* <p>
4244
* This abstract implementation of {@link ProofOblInput} extends the functionality of
@@ -353,26 +355,29 @@ private static Term addTransactionPrecondition(Term pre, boolean transactionFlag
353355
return pre;
354356
}
355357

356-
private static Term createProgPost(final IObserverFunction target,
357-
final LocationVariable selfVar, final ImmutableList<LocationVariable> paramVars,
358-
final LocationVariable resultVar, final List<LocationVariable> modifiableHeaps,
359-
final Map<LocationVariable, LocationVariable> atPreVars, final Term saveBeforeHeaps,
360-
final Term representsFromContract, final Term post, final TermBuilder tb) {
361-
final Term progPost;
358+
private static Term createProgPost(IObserverFunction target,
359+
LocationVariable selfVar, ImmutableList<LocationVariable> paramVars,
360+
LocationVariable resultVar, List<LocationVariable> modifiableHeaps,
361+
Map<LocationVariable, LocationVariable> atPreVars, Term saveBeforeHeaps,
362+
@Nullable Term representsFromContract, Term post, TermBuilder tb) {
362363
if (representsFromContract == null) {
363364
final Term[] updateSubs =
364365
createUpdateSubs(target, selfVar, paramVars, modifiableHeaps, atPreVars, tb);
365-
progPost = tb.apply(saveBeforeHeaps,
366-
tb.apply(tb.elementary(tb.var(resultVar), tb.func(target, updateSubs)), post));
366+
var term =
367+
tb.apply(tb.elementary(tb.var(resultVar), tb.func(target, updateSubs)), post);
368+
if (saveBeforeHeaps == null) { // null on no_state methods
369+
return term;
370+
} else {
371+
return tb.apply(saveBeforeHeaps, term);
372+
}
367373
} else {
368374
final Term body = representsFromContract;
369375
assert body.op() == Equality.EQUALS
370376
: "Only fully functional represents clauses for model"
371377
+ " methods are supported!";
372-
progPost = tb.apply(saveBeforeHeaps,
378+
return tb.apply(saveBeforeHeaps,
373379
tb.apply(tb.elementary(tb.var(resultVar), body.sub(1)), post));
374380
}
375-
return progPost;
376381
}
377382

378383
/**
@@ -789,7 +794,7 @@ protected Term createUninterpretedPredicate(ImmutableList<LocationVariable> form
789794
/**
790795
* Builds the frame clause including the modifiable clause.
791796
*
792-
* @param modifiableHeaps The heaps.
797+
* @param modifiableHeaps a non-empty list of heaps variables
793798
* @param heapToAtPre The previous heap before execution.
794799
* @param selfVar The self variable.
795800
* @param paramVars The parameters {@link ProgramVariable}s.
@@ -1026,13 +1031,19 @@ private ImmutableList<FunctionalOperationContract> collectLookupContracts(
10261031
return lookupContracts;
10271032
}
10281033

1029-
private Term getRepresentsFromContract(final IProgramMethod pm, final LocationVariable selfVar,
1034+
private @Nullable Term getRepresentsFromContract(final IProgramMethod pm,
1035+
final LocationVariable selfVar,
10301036
final ImmutableList<LocationVariable> paramVars, final LocationVariable resultVar,
10311037
final List<LocationVariable> heaps,
10321038
final Map<LocationVariable, LocationVariable> atPreVars, final Services proofServices) {
10331039
ImmutableList<FunctionalOperationContract> lookupContracts =
10341040
collectLookupContracts(pm, proofServices);
10351041
Term representsFromContract = null;
1042+
1043+
if (heaps.isEmpty()) {
1044+
return null; // represents not possible on `no_state` model methods.
1045+
}
1046+
10361047
for (FunctionalOperationContract fop : lookupContracts) {
10371048
representsFromContract = fop.getRepresentsAxiom(heaps.get(0), selfVar, paramVars,
10381049
resultVar, atPreVars, proofServices);
@@ -1085,6 +1096,11 @@ private Term createPost(final LocationVariable selfVar,
10851096
formalParamVars, exceptionVar, getUninterpretedPredicateName(), proofServices));
10861097
}
10871098

1099+
if (heaps.isEmpty()) {
1100+
// happens in cases of `no_state` model methods, than no heap can be modified.
1101+
return postTerm;
1102+
}
1103+
10881104
Term frameTerm = buildFrameClause(heaps, heapToBefore, selfVar, paramVars, proofServices);
10891105
return tb.and(postTerm, frameTerm);
10901106
}

key.core/src/main/java/de/uka/ilkd/key/proof/init/FunctionalOperationContractPO.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.key_project.util.collection.ImmutableSLList;
3636

3737
import org.jspecify.annotations.NonNull;
38+
import org.jspecify.annotations.Nullable;
3839

3940
import static de.uka.ilkd.key.java.KeYJavaASTFactory.declare;
4041

@@ -228,7 +229,7 @@ protected Term getGlobalDefs(LocationVariable heap, Term heapTerm, Term selfTerm
228229
* {@inheritDoc}
229230
*/
230231
@Override
231-
protected Term buildFrameClause(List<LocationVariable> modifiableHeaps,
232+
protected @Nullable Term buildFrameClause(List<LocationVariable> modifiableHeaps,
232233
Map<Term, Term> heapToAtPre, LocationVariable selfVar,
233234
ImmutableList<LocationVariable> paramVars, Services services) {
234235
Term frameTerm = null;

key.core/src/main/java/de/uka/ilkd/key/rule/tacletbuilder/TacletGenerator.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -419,9 +419,13 @@ private Term functionalRepresentsSatisfiability(IObserverFunction target, TermSe
419419
for (SchemaVariable paramSV : paramSVs) {
420420
tacletBuilder.addVarsNotFreeIn(targetSV, paramSV);
421421
}
422-
axiomSatisfiable =
423-
TB.ex(targetSV, TB.and(targetSVReachable, OpReplacer.replace(targetTerm,
424-
TB.var(targetSV), schemaRepresents.term, services.getTermFactory())));
422+
final var replaced = OpReplacer.replace(targetTerm,
423+
TB.var(targetSV), schemaRepresents.term, services.getTermFactory());
424+
if (targetSVReachable == null) { // no heaps are given e.g. no_state method
425+
axiomSatisfiable = TB.ex(targetSV, replaced);
426+
} else {
427+
axiomSatisfiable = TB.ex(targetSV, TB.and(targetSVReachable, replaced));
428+
}
425429
}
426430
return axiomSatisfiable;
427431
}

key.core/src/main/java/de/uka/ilkd/key/speclang/njml/Translator.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import de.uka.ilkd.key.speclang.translation.SLParameters;
3535
import de.uka.ilkd.key.speclang.translation.SLTranslationException;
3636
import de.uka.ilkd.key.util.InfFlowSpec;
37+
import de.uka.ilkd.key.util.TermUtil;
3738
import de.uka.ilkd.key.util.mergerule.MergeParamsSpec;
3839
import de.uka.ilkd.key.util.parsing.BuildingException;
3940

@@ -2366,6 +2367,16 @@ public SLExpression visitMethod_declaration(JmlParser.Method_declarationContext
23662367
SLParameters params = visitParameters(ctx.param_list());
23672368
SLExpression apply = lookupIdentifier(ctx.IDENT().getText(), null, params, ctx);
23682369

2370+
var forbiddenHeapVar = services.getTypeConverter().getHeapLDT().getHeap();
2371+
boolean applyContainsHeap = TermUtil.contains(apply.getTerm(), forbiddenHeapVar);
2372+
boolean bodyContainsHeap = TermUtil.contains(body.getTerm(), forbiddenHeapVar);
2373+
2374+
2375+
if (!applyContainsHeap && bodyContainsHeap) {
2376+
// NOT (no heap in applies --> no heap in body)
2377+
raiseError(ctx, "Heap used in a `no_state` method.");
2378+
}
2379+
23692380
return termFactory.eq(apply, body);
23702381
}
23712382

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* This file is part of KeY - https://key-project.org
2+
* KeY is licensed under the GNU General Public License Version 2
3+
* SPDX-License-Identifier: GPL-2.0-only */
4+
package de.uka.ilkd.key.util;
5+
6+
import java.util.LinkedList;
7+
import java.util.Queue;
8+
9+
import de.uka.ilkd.key.logic.Term;
10+
import de.uka.ilkd.key.logic.op.Operator;
11+
12+
import org.jspecify.annotations.NonNull;
13+
14+
/**
15+
* @author Alexander Weigl
16+
* @version 1 (06.02.25)
17+
*/
18+
public class TermUtil {
19+
20+
/**
21+
* Checks if a given term contains a specific operator.
22+
*
23+
* This method performs a breadth-first search on the term's subterms to find
24+
* the specified operator.
25+
*
26+
* Caveat: It does not return true if op (only) occurs as the target of an update in term.
27+
*
28+
* @param term the term to be checked
29+
* @param op the operator to search for
30+
* @return true if the term or any of its subterms contains the operator, false otherwise
31+
*/
32+
public static boolean contains(@NonNull Term term, @NonNull Operator op) {
33+
Queue<Term> queue = new LinkedList<>();
34+
queue.add(term);
35+
while (!queue.isEmpty()) {
36+
Term current = queue.poll();
37+
if (current.op() == op) {
38+
return true;
39+
}
40+
queue.addAll(current.subs().toList());
41+
}
42+
return false;
43+
}
44+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* This file is part of KeY - https://key-project.org
2+
* KeY is licensed under the GNU General Public License Version 2
3+
* SPDX-License-Identifier: GPL-2.0-only */
4+
package de.uka.ilkd.key.nparser;
5+
6+
import java.io.File;
7+
8+
import de.uka.ilkd.key.control.KeYEnvironment;
9+
import de.uka.ilkd.key.java.Services;
10+
import de.uka.ilkd.key.logic.Namespace;
11+
import de.uka.ilkd.key.logic.op.JFunction;
12+
import de.uka.ilkd.key.proof.init.ProofInputException;
13+
import de.uka.ilkd.key.proof.io.ProblemLoaderException;
14+
15+
import org.junit.jupiter.api.Test;
16+
17+
import static org.junit.jupiter.api.Assertions.*;
18+
19+
/**
20+
* @author Alexander Weigl
21+
* @version 1 (31.01.25)
22+
*/
23+
public class Issue3452Test {
24+
25+
26+
@Test
27+
void testNoStateParsedCorrectly() throws ProblemLoaderException, ProofInputException {
28+
final var input =
29+
new File(
30+
"src/test/resources/de/uka/ilkd/key/nparser/fix3452/fix/Issue3452Fixture.java");
31+
var env = KeYEnvironment.load(input, null, null, null);
32+
final var contract = env.getProofContracts().getFirst();
33+
var po = contract.createProofObl(env.getInitConfig(), contract);
34+
var proof = env.createProof(po); // just to ensure there is exception
35+
Services services = proof.getInitConfig().getServices();
36+
Namespace<JFunction> functions = services.getNamespaces().functions();
37+
assertEquals("[int]", functions.lookup("A::b").argSorts().toString());
38+
assertEquals("[Heap,int]", functions.lookup("A::c").argSorts().toString());
39+
}
40+
41+
@Test
42+
void testIllegalNoState() throws ProblemLoaderException, ProofInputException {
43+
final var input =
44+
new File(
45+
"src/test/resources/de/uka/ilkd/key/nparser/fix3452/problem/Issue3452IllegalNoState.java");
46+
47+
ProblemLoaderException exception = assertThrows(ProblemLoaderException.class, () -> {
48+
var env = KeYEnvironment.load(input, null, null, null);
49+
System.err.println(env);
50+
System.err.println("Unexpected load success");
51+
});
52+
53+
if (!exception.getMessage().startsWith("Heap used in a `no_state` method.")) {
54+
fail("Unexpected exception message: " + exception.getMessage());
55+
}
56+
}
57+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
class A {
2+
/*@ public model_behaviour
3+
@ requires n >= 0;
4+
@ ensures \result.length == n;
5+
@ measured_by n;
6+
@ public no_state static model \seq b(\bigint n) {
7+
@ return n == 0 ? \seq_empty : \seq_concat(b(n-1), \seq(0));
8+
@ }
9+
@*/
10+
11+
/*@ public model_behaviour
12+
@ requires n >= 0;
13+
@ ensures \result.length == n;
14+
@ accessible \nothing;
15+
@ measured_by n;
16+
@ public static model \seq c(\bigint n) {
17+
@ return n == 0 ? \seq_empty : \seq_concat(c(n-1), \seq(0));
18+
@ }
19+
@*/
20+
21+
int someHeapField;
22+
23+
//@ ensures true;
24+
void m() {
25+
//@ ghost \seq x = c(5);
26+
someHeapField = 42;
27+
//@ assert x == c(5);
28+
}
29+
30+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
class A {
2+
3+
/*@ public static no_state model int obs() {
4+
@ return A.someHeapField;
5+
@ }
6+
@*/
7+
8+
static int someHeapField;
9+
10+
}

0 commit comments

Comments
 (0)