Skip to content

Commit ed131ae

Browse files
committed
more of everything
1 parent 95780e3 commit ed131ae

File tree

11 files changed

+350
-182
lines changed

11 files changed

+350
-182
lines changed

key.core/src/main/java/de/uka/ilkd/key/java/SpecialJavaPrinter.java

Lines changed: 121 additions & 91 deletions
Large diffs are not rendered by default.

key.core/src/main/java/de/uka/ilkd/key/java/loader/JavaParserFactory.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@
2828
import com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver;
2929
import com.google.common.cache.Cache;
3030
import com.google.common.cache.CacheBuilder;
31-
import org.jspecify.annotations.NonNull;
3231
import org.jspecify.annotations.NullMarked;
3332
import org.jspecify.annotations.Nullable;
3433

key.core/src/main/java/de/uka/ilkd/key/java/transformations/pipeline/JavaTransformer.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@
44
package de.uka.ilkd.key.java.transformations.pipeline;
55

66

7+
import javax.annotation.processing.Generated;
8+
79
import com.github.javaparser.ast.CompilationUnit;
810
import com.github.javaparser.ast.Node;
911
import com.github.javaparser.ast.body.TypeDeclaration;
1012
import com.github.javaparser.ast.expr.StringLiteralExpr;
1113
import com.github.javaparser.ast.nodeTypes.NodeWithAnnotations;
1214
import org.jspecify.annotations.NullMarked;
1315

14-
import javax.annotation.processing.Generated;
15-
1616
/**
1717
* The JavaDL requires some implicit fields, that are available in each
1818
* Java class. The name of the implicit fields starts usually with a dollar sign.
@@ -90,7 +90,7 @@ public static RuntimeException reportError(Node node, String message, Object...
9090

9191
protected void addGenerated(NodeWithAnnotations<?> node) {
9292
node.addSingleMemberAnnotation(
93-
Generated.class.getName(),
94-
new StringLiteralExpr(this.getClass().getSimpleName()));
93+
Generated.class.getName(),
94+
new StringLiteralExpr(this.getClass().getSimpleName()));
9595
}
9696
}

key.core/src/main/java/de/uka/ilkd/key/java/transformations/pipeline/RecordClassBuilder.java

Lines changed: 140 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,26 @@
33
* SPDX-License-Identifier: GPL-2.0-only */
44
package de.uka.ilkd.key.java.transformations.pipeline;
55

6+
import java.util.Arrays;
7+
import java.util.List;
8+
import java.util.stream.Collectors;
9+
610
import com.github.javaparser.StaticJavaParser;
711
import com.github.javaparser.ast.CompilationUnit;
12+
import com.github.javaparser.ast.Modifier;
813
import com.github.javaparser.ast.NodeList;
914
import com.github.javaparser.ast.body.*;
1015
import com.github.javaparser.ast.expr.*;
1116
import com.github.javaparser.ast.key.JmlDocModifier;
1217
import com.github.javaparser.ast.nodeTypes.NodeWithSimpleName;
13-
import com.github.javaparser.ast.stmt.BlockStmt;
1418
import com.github.javaparser.ast.stmt.ReturnStmt;
19+
import com.github.javaparser.ast.type.PrimitiveType;
1520
import com.github.javaparser.ast.type.Type;
1621
import org.jspecify.annotations.Nullable;
1722

18-
import java.util.Arrays;
19-
import java.util.List;
20-
import java.util.stream.Collectors;
21-
2223
import static com.github.javaparser.ast.Modifier.DefaultKeyword.*;
23-
import static de.uka.ilkd.key.java.transformations.AstFactory.*;
24+
import static de.uka.ilkd.key.java.transformations.AstFactory.assign;
25+
import static de.uka.ilkd.key.java.transformations.AstFactory.attributeThis;
2426

2527
/// This transformation is made to transform any found [RecordDeclaration] into a corresponding
2628
/// [ClassOrInterfaceDeclaration].
@@ -52,11 +54,9 @@ private ClassOrInterfaceDeclaration transform(RecordDeclaration recordDeclaratio
5254
clazz.addExtendedType(java.lang.Record.class);
5355
addGenerated(clazz);
5456

55-
services.attachTypeSpec(clazz,
56-
equalFieldsEqualRecords(recordDeclaration));
57-
5857
for (Parameter parameter : recordDeclaration.getParameters()) {
59-
FieldDeclaration field = clazz.addField(parameter.type(), parameter.getNameAsString(), PRIVATE, FINAL);
58+
FieldDeclaration field =
59+
clazz.addField(parameter.type(), parameter.getNameAsString(), PRIVATE, FINAL);
6060
field.getModifiers().addAll(parameter.getModifiers());
6161
addGenerated(field);
6262

@@ -71,21 +71,50 @@ private ClassOrInterfaceDeclaration transform(RecordDeclaration recordDeclaratio
7171
getter.getModifiers().add(mod.clone());
7272
}
7373
}
74-
getter.getBody().get().addStatement(new ReturnStmt(parameter.getNameAsExpression()));
74+
getter.getBody().get()
75+
.addStatement(new ReturnStmt(parameter.getNameAsExpression()));
7576
addGenerated(getter);
7677
}
7778
}
7879

7980
createConstructor(recordDeclaration, clazz);
8081

81-
createEquals(recordDeclaration, clazz);
82-
createHashCode(recordDeclaration, clazz);
82+
var generated =
83+
createEquals(recordDeclaration, clazz) && createHashCode(recordDeclaration, clazz);
84+
85+
if (generated) {
86+
services.attachTypeSpec(clazz,
87+
equalFieldsEqualRecords(recordDeclaration));
88+
89+
services.attachTypeSpec(clazz,
90+
"public static invariant_free (\\forall %s a; a.equals(a));"
91+
.formatted(recordDeclaration.getNameAsString()));
92+
93+
services.attachTypeSpec(clazz,
94+
"public static invariant_free (\\forall %s a,b; a.equals(b); b.equals(a));"
95+
.formatted(recordDeclaration.getNameAsString()));
96+
97+
services.attachTypeSpec(clazz,
98+
"public static invariant_free (\\forall %s a,b,c; a.equals(b) && b.equals(c); a.equals(c));"
99+
.formatted(recordDeclaration.getNameAsString()));
100+
101+
services.attachTypeSpec(clazz,
102+
"public static invariant_free (\\forall %s a,b; a.equals(b); a.hashCode() == b.hashCode());"
103+
.formatted(recordDeclaration.getNameAsString()));
104+
105+
services.attachTypeSpec(clazz,
106+
"public static invariant_free (\\forall %s a,b; a.hashCode() != b.hashCode(); !a.equals(b));"
107+
.formatted(recordDeclaration.getNameAsString()));
108+
}
109+
83110
createToString(recordDeclaration, clazz);
111+
84112
clazz.getMembers().addAll(recordDeclaration.getMembers());
85113
return clazz;
86114
}
87115

88-
private void createConstructor(RecordDeclaration recordDeclaration, ClassOrInterfaceDeclaration clazz) {
116+
private void createConstructor(RecordDeclaration recordDeclaration,
117+
ClassOrInterfaceDeclaration clazz) {
89118
String[] paramTypes = recordDeclaration.getParameters().stream()
90119
.map(it -> it.getType().asString())
91120
.toArray(String[]::new);
@@ -102,8 +131,21 @@ private void createConstructor(RecordDeclaration recordDeclaration, ClassOrInter
102131
for (var parameter : recordDeclaration.getParameters()) {
103132
final var p = parameter.clone();
104133
fullConstructor.addParameter(p);
105-
body.addStatement(assign(attributeThis(p.getNameAsString()), p.getNameAsExpression()));
134+
body.addStatement(
135+
assign(attributeThis(p.getNameAsString()), p.getNameAsExpression()));
106136
}
137+
138+
var fieldParamEqual = recordDeclaration.getParameters()
139+
.stream()
140+
.map(NodeWithSimpleName::getNameAsString)
141+
.map(it -> "ensures this.%s == %s;".formatted(it, it))
142+
.collect(Collectors.joining("\n"));
143+
services.attachSpec(fullConstructor, """
144+
public normal_behavior
145+
requires true;
146+
%s
147+
""".formatted(fieldParamEqual));
148+
107149
}
108150

109151
for (var compactConstructor : recordDeclaration.getCompactConstructors()) {
@@ -113,7 +155,8 @@ private void createConstructor(RecordDeclaration recordDeclaration, ClassOrInter
113155
clazz.addMember(fullConstructor);
114156
}
115157

116-
private void createToString(RecordDeclaration recordDeclaration, ClassOrInterfaceDeclaration clazz) {
158+
private void createToString(RecordDeclaration recordDeclaration,
159+
ClassOrInterfaceDeclaration clazz) {
117160
boolean hasNoToString = recordDeclaration.getMethodsBySignature("toString").isEmpty();
118161
if (hasNoToString) {
119162
MethodDeclaration toString = clazz.addMethod("toString", PUBLIC, FINAL, JML_NON_NULL);
@@ -136,7 +179,8 @@ private void createToString(RecordDeclaration recordDeclaration, ClassOrInterfac
136179
}
137180
}
138181

139-
private void createHashCode(RecordDeclaration recordDeclaration, ClassOrInterfaceDeclaration clazz) {
182+
private boolean createHashCode(RecordDeclaration recordDeclaration,
183+
ClassOrInterfaceDeclaration clazz) {
140184
boolean hasNoHashcode = recordDeclaration.getMethodsBySignature("hashCode").isEmpty();
141185
if (hasNoHashcode) {
142186
MethodDeclaration hashCode = clazz.addMethod("hashCode", PUBLIC, FINAL);
@@ -147,40 +191,91 @@ private void createHashCode(RecordDeclaration recordDeclaration, ClassOrInterfac
147191
.map(it -> (Expression) it)
148192
.toList();
149193
final Expression call = callObjects("hash", args);
150-
hashCode.getBody().get().addStatement(new ReturnStmt(call));
194+
// hashCode.getBody().get().addStatement(new ReturnStmt(call));
195+
hashCode.setBody(null);
151196
addGenerated(hashCode);
197+
198+
services.attachSpec(hashCode, """
199+
public normal_behavior
200+
ensures true; requires true;
201+
assignable \\strictly_nothing;
202+
""");
152203
}
204+
return hasNoHashcode;
153205
}
154206

155-
private void createEquals(RecordDeclaration recordDeclaration, ClassOrInterfaceDeclaration clazz) {
156-
boolean hasNoEquals = recordDeclaration.getMethodsBySignature("equals", "java.lang.Object").isEmpty()
157-
&& recordDeclaration.getMethodsBySignature("equals", "Object").isEmpty();
207+
private boolean createEquals(RecordDeclaration recordDeclaration,
208+
ClassOrInterfaceDeclaration clazz) {
209+
boolean hasNoEquals =
210+
recordDeclaration.getMethodsBySignature("equals", "java.lang.Object").isEmpty()
211+
&& recordDeclaration.getMethodsBySignature("equals", "Object").isEmpty();
158212
if (hasNoEquals) {
159213
MethodDeclaration equals = clazz.addMethod("equals", PUBLIC, FINAL);
160214
equals.addAnnotation(Override.class);
161215
equals.setType(Boolean.TYPE);
162216
Type tObject = StaticJavaParser.parseType("java.lang.Object");
163-
equals.getParameters().add(new Parameter(tObject, "o"));
164-
BlockStmt body = equals.getBody().get();
165-
body.addStatement(
166-
mark(services.parseStatement("if(this == o) return true;")));
167-
body.addStatement(
168-
mark(services.parseStatement("if(!(o instanceof %s)) return false;".formatted(clazz.getNameAsString()))));
169-
170-
body.addStatement(
171-
mark(services.parseStatement("final %s that = (%s) o;"
172-
.formatted(clazz.getNameAsString(), clazz.getNameAsString()))));
173-
174-
Expression equalFields = recordDeclaration.getParameters().stream()
175-
.map(it -> callObjects("equals", it.getNameAsExpression(),
176-
new FieldAccessExpr(new NameExpr("that"), it.getNameAsString())))
177-
.reduce((a, b) -> new BinaryExpr(a, b, BinaryExpr.Operator.AND))
178-
.orElse(new BooleanLiteralExpr(true));
179-
body.addStatement(mark(new ReturnStmt(equalFields)));
180-
181-
body.addStatement(mark(new ReturnStmt(new BooleanLiteralExpr(true))));
217+
var mods = new NodeList<>(new Modifier(JML_NULLABLE));
218+
equals.getParameters().add(new Parameter(mods, tObject, new SimpleName("o")));
219+
equals.setBody(null);
220+
221+
222+
services.attachSpec(equals, """
223+
public normal_behavior
224+
requires true;
225+
ensures (
226+
(this == o) //equality of identity
227+
|| (o instanceof %s && o != null && %s)
228+
|| (o instanceof %s && o != null && %s)
229+
) <==> \\result;
230+
ensures hashCode() != o.hashCode() ==> !\\result;
231+
ensures o == null ==> !\\result;
232+
assignable \\strictly_nothing;
233+
""".formatted(
234+
recordDeclaration.getNameAsString(),
235+
recordDeclaration.getParameters().stream()
236+
.map(NodeWithSimpleName::getNameAsString)
237+
.map(it -> "this.%s==((%s)o).%s".formatted(it,
238+
recordDeclaration.getNameAsString(), it))
239+
.collect(Collectors.joining(" && ")),
240+
recordDeclaration.getNameAsString(),
241+
recordDeclaration.getParameters().stream()
242+
.map(it -> {
243+
String pname = it.getNameAsString();
244+
if (it.getType() instanceof PrimitiveType)
245+
return "(%s == ((%s)o).%s)".formatted(pname,
246+
recordDeclaration.getNameAsString(), pname);
247+
else
248+
return "( this.%s != null ? (this.%s.equals(((%s)o).%s)) : (o.%s == null))"
249+
.formatted(pname, pname,
250+
recordDeclaration.getNameAsString(), pname, pname);
251+
})
252+
.collect(Collectors.joining(" && "))));
253+
254+
/*
255+
* Nobody only contract:
256+
* BlockStmt body = equals.getBody().get();
257+
* body.addStatement(
258+
* mark(services.parseStatement("if(this == o) return true;")));
259+
* body.addStatement(
260+
* mark(services.parseStatement("if(!(o instanceof %s)) return false;".formatted(clazz.
261+
* getNameAsString()))));
262+
*
263+
* body.addStatement(
264+
* mark(services.parseStatement("final %s that = (%s) o;"
265+
* .formatted(clazz.getNameAsString(), clazz.getNameAsString()))));
266+
*
267+
* Expression equalFields = recordDeclaration.getParameters().stream()
268+
* .map(it -> callObjects("equals", it.getNameAsExpression(),
269+
* new FieldAccessExpr(new NameExpr("that"), it.getNameAsString())))
270+
* .reduce((a, b) -> new BinaryExpr(a, b, BinaryExpr.Operator.AND))
271+
* .orElse(new BooleanLiteralExpr(true));
272+
* body.addStatement(mark(new ReturnStmt(equalFields)));
273+
*
274+
* body.addStatement(mark(new ReturnStmt(new BooleanLiteralExpr(true))));
275+
*/
182276
addGenerated(equals);
183277
}
278+
return hasNoEquals;
184279
}
185280

186281
private static @Nullable String equalFieldsEqualRecords(RecordDeclaration recordDeclaration) {
@@ -193,18 +288,18 @@ private void createEquals(RecordDeclaration recordDeclaration, ClassOrInterfaceD
193288
.map(it -> "a.%s == b.%s".formatted(it, it))
194289
.collect(Collectors.joining(" && "));
195290

196-
return "public axiom (\\forall %s a,b; %s; a.equals(b));".formatted(
197-
recordDeclaration.getNameAsString(),
198-
identityOfFields
199-
);
291+
return "public invariant_free (\\forall %s a,b; %s; a.equals(b));".formatted(
292+
recordDeclaration.getNameAsString(),
293+
identityOfFields);
200294
}
201295

202296
private Expression callObjects(String method, Expression... exprs) {
203297
return callObjects(method, Arrays.stream(exprs).toList());
204298
}
205299

206300
private Expression callObjects(String method, List<Expression> exprs) {
207-
var objects = new FieldAccessExpr(new FieldAccessExpr(new NameExpr("java"), "lang"), "Objects");
301+
var objects =
302+
new FieldAccessExpr(new FieldAccessExpr(new NameExpr("java"), "lang"), "Objects");
208303
return new MethodCallExpr(objects, method, new NodeList<>(exprs));
209304
}
210305

key.core/src/main/java/de/uka/ilkd/key/java/transformations/pipeline/TransformationPipelineServices.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,10 @@
1010
import de.uka.ilkd.key.java.transformations.ConstantExpressionEvaluator;
1111
import de.uka.ilkd.key.java.transformations.EvaluationException;
1212
import de.uka.ilkd.key.speclang.PositionedString;
13-
1413
import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLConstruct;
1514
import de.uka.ilkd.key.speclang.jml.pretranslation.TextualJMLLoopSpec;
1615
import de.uka.ilkd.key.speclang.njml.PreParser;
17-
import org.jspecify.annotations.NonNull;
16+
1817
import org.key_project.util.collection.ImmutableList;
1918

2019
import com.github.javaparser.JavaParser;
@@ -32,6 +31,7 @@
3231
import com.github.javaparser.resolution.model.SymbolReference;
3332
import com.github.javaparser.resolution.types.ResolvedReferenceType;
3433
import com.github.javaparser.resolution.types.ResolvedType;
34+
import org.jspecify.annotations.NonNull;
3535
import org.jspecify.annotations.NullMarked;
3636
import org.jspecify.annotations.Nullable;
3737
import org.slf4j.Logger;
@@ -260,6 +260,7 @@ public List<SymbolReference<? extends ResolvedValueDeclaration>> getUsages(
260260
/// i = j + 5;
261261
/// }
262262
/// ```
263+
///
263264
/// @param cd the TypeDeclaration of which the initializers have to be collected
264265
/// @return the list of copy assignments and method references realizing the initializers.`
265266
public NodeList<Statement> getInitializers(ClassOrInterfaceDeclaration cd) {
@@ -343,12 +344,22 @@ public Statement parseStatement(String code) {
343344
}
344345

345346
public void attachTypeSpec(TypeDeclaration<?> clazz, @Nullable String spec) {
346-
if(spec==null) return;
347+
if (spec == null)
348+
return;
347349
PreParser pp = getPreParser();
348350
ImmutableList<TextualJMLConstruct> specification = pp.parseClassLevel(spec);
349351
specification.forEach(it -> addClassSpec(clazz, it));
350352
}
351353

354+
public void attachSpec(Node node, @Nullable String spec) {
355+
if (spec == null)
356+
return;
357+
PreParser pp = getPreParser();
358+
ImmutableList<TextualJMLConstruct> specification = pp.parseClassLevel(spec);
359+
specification.forEach(it -> addSpec(node, it));
360+
LOGGER.info("Generated specification {} for {}", spec, node);
361+
}
362+
352363

353364
/**
354365
* Cache of important data. This is done mainly for performance reasons.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ private void readJava(EnvInput envInput, InitConfig initConfig) throws ProofInpu
271271
} catch (IOException e) {
272272
throw new ProofInputException("Failed to read file", e);
273273
} catch (BuildingExceptions e) {
274-
throw new ProofInputException("Failed to parse file: "+ javaPath, e);
274+
throw new ProofInputException("Failed to parse file: " + javaPath, e);
275275
}
276276
}
277277
Path initialFile = envInput.getInitialFile();

0 commit comments

Comments
 (0)