Skip to content

Commit e46b239

Browse files
committed
final field code validator and tests
thanks to Richard for hinting at the needed infrastructure
1 parent ea6e9ca commit e46b239

File tree

10 files changed

+216
-20
lines changed

10 files changed

+216
-20
lines changed

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

Lines changed: 24 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import de.uka.ilkd.key.java.expression.Assignment;
77
import de.uka.ilkd.key.java.reference.*;
88
import de.uka.ilkd.key.logic.op.IProgramMethod;
9+
import de.uka.ilkd.key.logic.op.ProgramMethod;
910
import de.uka.ilkd.key.logic.op.ProgramVariable;
1011
import org.key_project.logic.SyntaxElement;
1112
import org.key_project.util.collection.IdentityHashSet;
@@ -27,6 +28,9 @@
2728
* Potential for relaxaions:
2829
* - Final fields may be read after initialization (locally and in called methods)
2930
* This requires a lot more bookkeeping, though.
31+
* - Effective finalness can be relaxed: If every constructor is subject to this treatment,
32+
* corresponding expansion of the methods would reveal any illegal reads. ... if the super(...)
33+
* calls are expanded for analysis.
3034
*
3135
* If this is a secondary constructor (referring to another constructor via this()), there are no restrictions.
3236
*/
@@ -40,9 +44,9 @@ public FinalFieldCodeValidator(InitConfig initConfig) {
4044
this.initConfig = initConfig;
4145
}
4246

43-
public static void validateFinalFields(IProgramMethod constructor, InitConfig initConfig) {
47+
public static void validateFinalFields(ProgramMethod constructor, InitConfig initConfig) {
4448
var validator = new FinalFieldCodeValidator(initConfig);
45-
validator.enclosingClass = null; // constructor.getEnclosingClass(); // TODO!
49+
validator.enclosingClass = constructor.getContainerType();
4650
if(isSecondaryConstructor(constructor)) {
4751
// secondary constructors are fine!
4852
return;
@@ -61,12 +65,7 @@ private static boolean isSecondaryConstructor(IProgramMethod constructor) {
6165
}
6266

6367
var firstStatement = body.getStatementAt(0);
64-
if (firstStatement instanceof MethodOrConstructorReference methodReference) {
65-
// check that this is a reference of the form this(...)
66-
return true;
67-
}
68-
69-
return false;
68+
return firstStatement instanceof ThisConstructorReference;
7069
}
7170

7271
private void validate(IProgramMethod method) {
@@ -81,19 +80,18 @@ private void validate(IProgramMethod method) {
8180
throw new FinalViolationException("Method " + method.getFullName() + " has no body.");
8281
}
8382

84-
for(int i = 0; i < body.getStatementCount(); i++) {
85-
var statement = body.getStatementAt(i);
86-
validateProgramElement(statement);
87-
}
83+
validateProgramElement(body);
8884

8985
var popped = methodStack.pop();
9086
assert popped == method;
9187
validatedMethods.add(method);
9288
}
9389

9490
private void validateProgramElement(SyntaxElement element) {
95-
if(element instanceof MethodOrConstructorReference methodReference) {
91+
if(element instanceof MethodReference methodReference) {
9692
validateMethodReference(methodReference);
93+
} else if (element instanceof ConstructorReference constructorReference) {
94+
validateConstructorReference(constructorReference);
9795
} else if(element instanceof FieldReference fieldReference) {
9896
validateFieldReference(fieldReference);
9997
} else if(element instanceof Assignment assignment) {
@@ -107,7 +105,16 @@ private void validateProgramElement(SyntaxElement element) {
107105
}
108106
}
109107

110-
private void validateMethodReference(MethodOrConstructorReference methodReference) {
108+
private void validateConstructorReference(ConstructorReference methodReference) {
109+
// TODO We have to make sure that on non-static subclass is instantiated here
110+
var hasThisArgument = methodReference.getArguments().stream().anyMatch(ThisReference.class::isInstance);
111+
112+
if(hasThisArgument) {
113+
throw new FinalViolationException("Method call " + methodReference + " leaks 'this' to called method.", methodReference);
114+
}
115+
}
116+
117+
private void validateMethodReference(MethodReference methodReference) {
111118
ReferencePrefix referencePrefix = methodReference.getReferencePrefix();
112119
var calledOnThis = referencePrefix == null || referencePrefix instanceof ThisReference;
113120
var hasThisArgument = methodReference.getArguments().stream().anyMatch(ThisReference.class::isInstance);
@@ -131,12 +138,13 @@ private void validateMethodReference(MethodOrConstructorReference methodReferenc
131138
}
132139
}
133140

134-
private IProgramMethod findMethod(MethodOrConstructorReference methodReference) {
141+
private IProgramMethod findMethod(MethodReference methodReference) {
135142
// return the program method for the method reference
136143
// YOu can use enclosingClass to get the class in which the method is defined
137144
// The method is guaranteed to be defined in the enclosing class not in a superclass.
138145
// One can also peek the method stack if needed ...
139-
throw new UnsupportedOperationException("Not implemented yet.");
146+
ExecutionContext ec = new ExecutionContext(new TypeRef(enclosingClass), methodStack.peek(), methodReference.getReferencePrefix());
147+
return methodReference.method(initConfig.getServices(), methodReference.determineStaticPrefixType(initConfig.getServices(), ec), ec);
140148
}
141149

142150
private void validateAssignment(Assignment assignment) {

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import de.uka.ilkd.key.logic.TermBuilder;
1414
import de.uka.ilkd.key.logic.op.IProgramMethod;
1515
import de.uka.ilkd.key.logic.op.LogicVariable;
16+
import de.uka.ilkd.key.logic.op.ProgramMethod;
1617
import de.uka.ilkd.key.logic.op.ProgramVariable;
1718

1819
import org.key_project.logic.Name;
@@ -40,13 +41,14 @@ public Term modifyPostTerm(AbstractOperationPO abstractPO, InitConfig proofConfi
4041

4142
// We know this holds because of isPOSupported:
4243
FunctionalOperationContractPO fpo = (FunctionalOperationContractPO) abstractPO;
43-
IProgramMethod constructor = fpo.getProgramMethod();
44-
assert constructor.isConstructor();
44+
IProgramMethod iconstructor = fpo.getProgramMethod();
45+
assert iconstructor instanceof ProgramMethod : "Contracts cannot have schema ";
46+
ProgramMethod constructor = (ProgramMethod) iconstructor;
4547

4648
FinalFieldCodeValidator.validateFinalFields(constructor, proofConfig);
4749

4850
TermBuilder tb = services.getTermBuilder();
49-
LogicVariable fv = new LogicVariable(new Name("o"),
51+
LogicVariable fv = new LogicVariable(new Name("fld"),
5052
services.getTypeConverter().getHeapLDT().getFieldSort());
5153
Term self = tb.var(selfVar);
5254
Term sel = tb.dot(JavaDLTheory.ANY, self, tb.var(fv));
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package de.uka.ilkd.key.proof.init;
2+
3+
import de.uka.ilkd.key.control.DefaultUserInterfaceControl;
4+
import de.uka.ilkd.key.control.KeYEnvironment;
5+
import de.uka.ilkd.key.java.abstraction.KeYJavaType;
6+
import de.uka.ilkd.key.logic.op.ProgramMethod;
7+
import de.uka.ilkd.key.proof.io.ProblemLoaderException;
8+
import de.uka.ilkd.key.proof.mgt.SpecificationRepository;
9+
import de.uka.ilkd.key.speclang.Contract;
10+
import de.uka.ilkd.key.util.KeYTypeUtil;
11+
import org.junit.jupiter.api.Assertions;
12+
import org.junit.jupiter.api.DynamicTest;
13+
import org.junit.jupiter.api.TestFactory;
14+
15+
import java.io.File;
16+
import java.net.URL;
17+
import java.util.HashSet;
18+
import java.util.Set;
19+
import java.util.stream.Stream;
20+
21+
class FinalFieldCodeValidatorTest {
22+
23+
@TestFactory
24+
public Stream<DynamicTest> testCodeValidatorParse() throws ProblemLoaderException {
25+
return testContracts(false, "final/shouldparse");
26+
}
27+
28+
//@TestFactory
29+
public Stream<DynamicTest> testCodeValidatorFail() throws ProblemLoaderException {
30+
return testContracts(true, "final/shouldfail");
31+
}
32+
33+
private Stream<DynamicTest> testContracts(boolean shouldfail, String directory) throws ProblemLoaderException {
34+
URL url = getClass().getResource(directory);
35+
assert url != null : directory + " not found.";
36+
assert "file".equals(url.getProtocol()): "Test cases must be in file system";
37+
File dir = new File(url.getPath());
38+
KeYEnvironment<DefaultUserInterfaceControl> env = KeYEnvironment.load(dir, null, null, null);
39+
40+
Set<KeYJavaType> kjts = env.getJavaInfo().getAllKeYJavaTypes();
41+
Set<Contract> contracts = new HashSet<>();
42+
for (KeYJavaType type : kjts) {
43+
if (!KeYTypeUtil.isLibraryClass(type)) {
44+
SpecificationRepository specRepo = env.getSpecificationRepository();
45+
for(Contract c: specRepo.getAllContracts()) {
46+
var target = c.getTarget();
47+
if (target instanceof ProgramMethod pm &&
48+
pm.isConstructor() &&
49+
!KeYTypeUtil.isLibraryClass(pm.getContainerType())) {
50+
contracts.add(c);
51+
}
52+
}
53+
}
54+
}
55+
if(shouldfail)
56+
return contracts.stream().map(c -> DynamicTest.dynamicTest("Illegal constructor " + c.getName(),
57+
() -> Assertions.assertThrowsExactly(FinalFieldCodeValidator.FinalViolationException.class,
58+
() -> testConstructor(c, env))));
59+
else return contracts.stream().map(c -> DynamicTest.dynamicTest("Legal constructor " + c.getName(),
60+
()->testConstructor(c, env)));
61+
}
62+
63+
private void testConstructor(Contract c, KeYEnvironment<?> env) throws ProofInputException {
64+
try {
65+
// System.out.println("Contract id: " + c.getName());
66+
ContractPO po = c.createProofObl(env.getInitConfig());
67+
env.createProof(po);
68+
} catch(FinalFieldCodeValidator.FinalViolationException fex) {
69+
System.err.println("Position: " + fex.getPosition());
70+
fex.printStackTrace();
71+
throw fex;
72+
}
73+
}
74+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class FinalReadBeforeWriteIndirect {
2+
final int finalField;
3+
4+
//@ ensures b;
5+
FinalReadBeforeWriteIndirect(boolean b) {
6+
int before = getFinalField();
7+
finalField = 42;
8+
int after = getFinalField();
9+
}
10+
11+
/*@ normal_behaviour
12+
@ ensures \result == finalField;
13+
@*/
14+
private int getFinalField() {
15+
return finalField;
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class FinalReadBeforeWriteIndirect {
2+
final int finalField;
3+
4+
//@ ensures b;
5+
FinalReadBeforeWriteIndirect(boolean b) {
6+
int before = getFinalField();
7+
finalField = 42;
8+
int after = getFinalField();
9+
}
10+
11+
/*@ normal_behaviour
12+
@ ensures \result == finalField;
13+
@*/
14+
private int getFinalField() {
15+
return finalField;
16+
}
17+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
class LeakThis1 {
2+
final int finalField;
3+
4+
//@ ensures b;
5+
LeakThis1(boolean b) {
6+
int before = getFinalField(this);
7+
finalField = 42;
8+
int after = getFinalField(this);
9+
}
10+
11+
/*@ normal_behaviour
12+
@ ensures \result == x.finalField;
13+
@*/
14+
private int getFinalField(LeakThis1 x) {
15+
return x.finalField;
16+
}
17+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class LeakThis2 {
2+
final int finalField;
3+
LeakThis2 other;
4+
5+
//@ ensures b;
6+
LeakThis2(boolean b) {
7+
leakThis();
8+
int before = getFinalField();
9+
finalField = 42;
10+
int after = getFinalField();
11+
}
12+
13+
private LeakThis2 leakThis() {
14+
other = true ? this : this;
15+
}
16+
17+
/*@ normal_behaviour
18+
@ ensures \result == finalField;
19+
@*/
20+
int getFinalField() {
21+
return other.finalField;
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
class Subclass {
2+
final int finalField;
3+
4+
//@ ensures b;
5+
Subclass(boolean b) {
6+
int before = getFinalField();
7+
finalField = 42;
8+
int after = getFinalField();
9+
}
10+
11+
int getFinalField() {
12+
return 0;
13+
}
14+
}
15+
16+
class Subsubclass extends Subclass {
17+
/*@ normal_behaviour
18+
@ ensures \result == finalField;
19+
@*/
20+
int getFinalField() {
21+
return finalField;
22+
}
23+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
class SecondaryConstructor {
2+
final int finalField;
3+
4+
boolean b;
5+
6+
//@ ensures b;
7+
SecondaryConstructor(int v) {
8+
finalField = v;
9+
}
10+
11+
SecondaryCosntructor() {
12+
this(42);
13+
int x = finalField;
14+
}
15+
}

key.ui/src/main/java/de/uka/ilkd/key/gui/WindowUserInterfaceControl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ public void progressStopped(Object sender) {
134134

135135
@Override
136136
public void reportException(Object sender, ProofOblInput input, Exception e) {
137-
reportStatus(sender, input.name() + " failed");
137+
IssueDialog.showExceptionDialog(mainWindow, e);
138138
}
139139

140140
@Override

0 commit comments

Comments
 (0)