Skip to content

Commit 26ea1c8

Browse files
Verify static and default interface method bodies (#385)
Fixes #343. The verifier assumed every return and `this` access belonged to a class, so static and default interface methods crashed before verification. Return checking now uses the nearest method and its declaring type, resolves overloads by parameter types, and keeps lambda returns separate from an enclosing method. Interface method invocations also use their registered contracts. New passing cases cover the reported `String.length()` call, annotated static/default methods, `return this`, same-arity overloads, and lambda returns inside interface bodies. New failing cases require errors for invalid static/default returns and a bad interface method argument. Validation: `mvn test` passed all 391 tests, and the four new fixtures produced their expected outcomes when run directly. This change does not extend interface supertype contract inheritance.
1 parent 80bdd78 commit 26ea1c8

6 files changed

Lines changed: 97 additions & 12 deletions

File tree

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface CorrectInterfaceMethodBodies {
6+
static int size(String s) {
7+
int n = s.length();
8+
return n;
9+
}
10+
11+
@Refinement("_ > 0")
12+
static int positive() {
13+
return 1;
14+
}
15+
16+
static void checkStaticContract() {
17+
@Refinement("_ > 0")
18+
int n = positive();
19+
}
20+
21+
@Refinement("_ > 0")
22+
default int defaultPositive() {
23+
return 1;
24+
}
25+
26+
default CorrectInterfaceMethodBodies self() {
27+
return this;
28+
}
29+
30+
@Refinement("_ > 0")
31+
default int lambdaReturnIsNotMethodReturn() {
32+
java.util.function.IntSupplier supplier = () -> { return -1; };
33+
return 1;
34+
}
35+
36+
java.util.function.IntSupplier FIELD_SUPPLIER = () -> { return -1; };
37+
38+
@Refinement("_ > 0")
39+
static int overloaded(int value) { return 1; }
40+
41+
@Refinement("_ < 0")
42+
static int overloaded(boolean value) { return -1; }
43+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface ErrorInterfaceDefaultReturn {
6+
@Refinement("_ > 0")
7+
default int positive() {
8+
return -1; // Expect: Refinement Error
9+
}
10+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface ErrorInterfaceMethodCall {
6+
static void requirePositive(@Refinement("_ > 0") int value) {}
7+
8+
static void badCall() {
9+
requirePositive(-1); // Expect: Refinement Error
10+
}
11+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.Refinement;
4+
5+
public interface ErrorInterfaceStaticReturn {
6+
@Refinement("_ > 0")
7+
static int positive() {
8+
return -1; // Expect: Refinement Error
9+
}
10+
}

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/RefinementTypeChecker.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -214,7 +214,9 @@ public <T> void visitCtNewArray(CtNewArray<T> newArray) {
214214
@Override
215215
public <T> void visitCtThisAccess(CtThisAccess<T> thisAccess) {
216216
super.visitCtThisAccess(thisAccess);
217-
CtClass<?> c = thisAccess.getParent(CtClass.class);
217+
CtType<?> c = thisAccess.getParent(CtType.class);
218+
if (c == null)
219+
return;
218220
String s = c.getSimpleName();
219221
if (thisAccess.getParent() instanceof CtReturn) {
220222
String thisName = String.format(Formats.THIS, s);

‎liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/general_checkers/MethodsFunctionsChecker.java‎

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import spoon.reflect.declaration.CtInterface;
3434
import spoon.reflect.declaration.CtMethod;
3535
import spoon.reflect.declaration.CtParameter;
36+
import spoon.reflect.declaration.CtType;
3637
import spoon.reflect.reference.CtExecutableReference;
3738
import spoon.reflect.reference.CtTypeReference;
3839

@@ -189,23 +190,31 @@ private Predicate handleFunctionRefinements(RefinedFunction f, CtElement method,
189190
}
190191

191192
public <R> void getReturnRefinements(CtReturn<R> ret) throws LJError {
192-
CtClass<?> c = ret.getParent(CtClass.class);
193-
String className = c.getSimpleName();
194193
if (ret.getReturnedExpression() == null) {
195194
return;
196195
}
197196

197+
// A return in a lambda belongs to that lambda, even when it is nested in a method.
198+
// Looking for any enclosing CtMethod would check it against the outer method's contract.
199+
CtExecutable<?> executable = ret.getParent(CtExecutable.class);
200+
if (!(executable instanceof CtMethod<?> method))
201+
return;
202+
CtType<?> owner = method.getDeclaringType();
203+
if (owner == null)
204+
return;
205+
198206
// check if there are refinements
199207
if (rtc.getRefinement(ret.getReturnedExpression()) == null)
200208
ret.getReturnedExpression().putMetadata(Keys.REFINEMENT, new Predicate());
201-
CtMethod<?> method = ret.getParent(CtMethod.class);
202-
203209
// check if method has refinements
204-
if (rtc.getRefinement(method) == null || !(method.getParent() instanceof CtClass))
210+
if (rtc.getRefinement(method) == null)
205211
return;
206212

207-
RefinedFunction fi = rtc.getContext().getFunction(method.getSimpleName(),
208-
((CtClass<?>) method.getParent()).getQualifiedName(), method.getParameters().size());
213+
List<CtTypeReference<?>> parameterTypes = new ArrayList<>();
214+
for (CtParameter<?> parameter : method.getParameters())
215+
parameterTypes.add(parameter.getType());
216+
RefinedFunction fi = rtc.getContext().getFunction(method.getSimpleName(), owner.getQualifiedName(),
217+
parameterTypes);
209218
if (fi == null)
210219
return;
211220

@@ -215,8 +224,8 @@ public <R> void getReturnRefinements(CtReturn<R> ret) throws LJError {
215224
}
216225

217226
// Both return and the method have metadata
218-
String thisName = String.format(Formats.THIS, className);
219-
rtc.getContext().addInstanceToContext(thisName, c.getReference(), new Predicate(), ret);
227+
String thisName = String.format(Formats.THIS, owner.getSimpleName());
228+
rtc.getContext().addInstanceToContext(thisName, owner.getReference(), new Predicate(), ret);
220229

221230
String returnVarName = String.format(Formats.RET, rtc.getContext().getCounter());
222231
Predicate cretRef = rtc.getRefinement(ret.getReturnedExpression())
@@ -242,8 +251,8 @@ public <R> void getInvocationRefinements(CtInvocation<R> invocation) throws LJEr
242251
if (cte != null)
243252
searchMethodInLibrary(cte, invocation);
244253

245-
} else if (method.getParent() instanceof CtClass) {
246-
String ctype = ((CtClass<?>) method.getParent()).getQualifiedName();
254+
} else if (method.getParent()instanceof CtType<?> owner) {
255+
String ctype = owner.getQualifiedName();
247256
List<CtTypeReference<?>> paramTypes = invocation.getExecutable().getParameters();
248257
RefinedFunction f = rtc.getContext().getFunction(method.getSimpleName(), ctype, paramTypes);
249258
if (f != null) { // inside rtc.context

0 commit comments

Comments
 (0)