Skip to content

Commit 15c8287

Browse files
allow refence to return values in the to of state refinements for observer methods
1 parent 58349fa commit 15c8287

7 files changed

Lines changed: 178 additions & 3 deletions

File tree

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.*;
4+
5+
@StateSet({"maybeEmpty", "hasNextElem", "empty"})
6+
public class CorrectIteratorReturnHasNext {
7+
8+
int index;
9+
int size;
10+
11+
@StateRefinement(to = "maybeEmpty(this) && index() == 0 && size() == size")
12+
CorrectIteratorReturnHasNext(int size) {
13+
this.size = size;
14+
this.index = 0;
15+
}
16+
17+
@StateRefinement(to = "return ? hasNextElem(this) : empty(this)")
18+
boolean hasNext() {
19+
return index < size;
20+
}
21+
22+
@StateRefinement(from = "hasNextElem(this)", to = "maybeEmpty(this) && index() == index(old(this)) + 1 && size() == size(old(this))")
23+
int next() {
24+
index += 1;
25+
return index; // return index++; does not work
26+
}
27+
28+
int main1() {
29+
CorrectIteratorReturnHasNext it = new CorrectIteratorReturnHasNext(5);
30+
int sum = 0;
31+
while (true){
32+
if(it.hasNext()){
33+
sum += it.next();
34+
} else {
35+
break;
36+
}
37+
}
38+
return sum;
39+
}
40+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package testSuite;
2+
3+
import liquidjava.specification.*;
4+
5+
@StateSet({"maybeEmpty", "hasNextElem", "empty"})
6+
public class ErrorIteratorReturnHasNext {
7+
int index;
8+
int size;
9+
10+
@StateRefinement(to = "maybeEmpty(this) && index() == 0 && size() == size")
11+
ErrorIteratorReturnHasNext(int size) {
12+
this.size = size;
13+
this.index = 0;
14+
}
15+
16+
@StateRefinement(to = "return ? hasNextElem(this) : empty(this)")
17+
boolean hasNext() {
18+
return index < size;
19+
}
20+
21+
@StateRefinement(from = "hasNextElem(this)", to = "maybeEmpty(this) && index() == index(old(this)) + 1 && size() == size(old(this))")
22+
int next() {
23+
index += 1;
24+
return index; // return index++; does not work
25+
}
26+
27+
28+
void main1() {
29+
ErrorIteratorReturnHasNext it = new ErrorIteratorReturnHasNext(5);
30+
if(it.hasNext()){
31+
it.next();
32+
} else {
33+
it.next(); // State Refinement Error
34+
}
35+
}
36+
37+
void main2() {
38+
ErrorIteratorReturnHasNext it = new ErrorIteratorReturnHasNext(5);
39+
it.next(); // State Refinement Error
40+
}
41+
42+
int main3() {
43+
ErrorIteratorReturnHasNext it = new ErrorIteratorReturnHasNext(5);
44+
int sum = 0;
45+
while (true){
46+
if(!it.hasNext()){
47+
sum += it.next(); // State Refinement Error
48+
} else {
49+
break;
50+
}
51+
}
52+
return sum;
53+
}
54+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package testSuite.classes.image_params_so_error;
2+
3+
4+
5+
import java.util.Locale;
6+
import liquidjava.specification.*;
7+
import javax.imageio.ImageWriteParam;
8+
9+
@ExternalRefinementsFor("javax.imageio.ImageWriteParam")
10+
11+
@StateSet({"start", "acceptCompression", "compressionExplicit", "compressionSet"})
12+
@RefinementAlias("Ratio(float v) { 0 <= v && v <= 1.0 }")
13+
public interface ImageWriteParamsRefinements {
14+
15+
@StateRefinement(to="start(this)")
16+
void ImageWriteParam(Locale locale);
17+
18+
void setProgressiveMode(
19+
@Refinement("ImageWriteParam.MODE_DISABLED == mode || mode == ImageWriteParam.MODE_DEFAULT || mode == ImageWriteParam.MODE_COPY_FROM_METADATA")
20+
int mode
21+
);
22+
23+
@StateRefinement(from= "compressionExplicit() || compressionSet()",
24+
to = "mode == ImageWriteParam.MODE_EXPLICIT ? compressionExplicit(this) : start(this)")
25+
@StateRefinement(from="acceptCompression()",
26+
to = "mode == ImageWriteParam.MODE_EXPLICIT ? compressionExplicit(this) : acceptCompression(this)")
27+
void setCompressionMode(int mode);
28+
29+
@StateRefinement(from="compressionExplicit() || compressionSet()")
30+
void setCompressionQuality(@Refinement("Ratio(_)") float quality);
31+
32+
@StateRefinement(from = "start(this)",
33+
to = "return ? acceptCompression(this) : start(this)")
34+
boolean canWriteCompressed();
35+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package testSuite.classes.image_params_so_error;
2+
import java.util.Locale;
3+
4+
import javax.imageio.ImageWriteParam;
5+
6+
class JpegExporter {
7+
8+
// Adapted from https://stackoverflow.com/questions/72024965/how-to-compress-jpg-and-png-images-in-java
9+
ImageWriteParam setCompressionPreferences() {
10+
ImageWriteParam param = new ImageWriteParam(Locale.getDefault());
11+
if (param.canWriteCompressed()) {
12+
param.setCompressionMode(ImageWriteParam.MODE_DEFAULT);
13+
param.setCompressionQuality(0.85f); // State Refinement Error
14+
}
15+
return param;
16+
}
17+
}

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

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -289,14 +289,28 @@ private Map<String, String> checkInvocationRefinements(CtElement invocation, Lis
289289
return new HashMap<>();
290290
Map<String, String> map = mapInvocation(arguments, f);
291291

292+
// Stable return-value name so `_`/`return` in @StateRefinement to= matches the post-call VariableInstance.
293+
String returnViName = null;
294+
CtTypeReference<?> retType = f.getType();
295+
if (retType != null && !"void".equals(retType.toString())) {
296+
returnViName = String.format(Formats.INSTANCE, f.getName(), rtc.getContext().getCounter());
297+
invocation.putMetadata(Keys.RETURN_VAR_NAME, returnViName);
298+
if (f.allRefinementsTrue())
299+
rtc.getContext().addInstanceToContext(returnViName, retType, new Predicate(), invocation);
300+
}
301+
292302
if (target != null)
293303
AuxStateHandler.prepareInvocationTarget(rtc, target, invocation);
294304

295305
if (f.allRefinementsTrue()) {
296306
if (target != null)
297307
AuxStateHandler.checkTargetChanges(rtc, f, target, map, invocation);
298308

299-
invocation.putMetadata(Keys.REFINEMENT, new Predicate());
309+
// Expose `_ == returnViName` so the if-condition path variable ties to this return value.
310+
Predicate emptyRef = returnViName != null
311+
? Predicate.createEquals(Predicate.createVar(Keys.WILDCARD), Predicate.createVar(returnViName))
312+
: new Predicate();
313+
invocation.putMetadata(Keys.REFINEMENT, emptyRef);
300314
return map;
301315
}
302316

@@ -325,7 +339,8 @@ private Map<String, String> checkInvocationRefinements(CtElement invocation, Lis
325339
varName = v.getName();
326340
}
327341

328-
String viName = String.format(Formats.INSTANCE, f.getName(), rtc.getContext().getCounter());
342+
String viName = returnViName != null ? returnViName
343+
: String.format(Formats.INSTANCE, f.getName(), rtc.getContext().getCounter());
329344
VariableInstance vi = (VariableInstance) rtc.getContext().addInstanceToContext(viName, f.getType(),
330345
methodRef.substituteVariable(Keys.WILDCARD, viName), invocation); // TODO REVIEW!!
331346
if (varName != null && f.hasStateChange() && equalsThis)

liquidjava-verifier/src/main/java/liquidjava/processor/refinement_checker/object_checkers/AuxStateHandler.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,22 @@ private static Predicate createStatePredicate(String value, String targetClass,
209209
throw new InvalidRefinementError(position, "State refinement transition must be a boolean expression",
210210
value);
211211
}
212+
CtTypeReference<?> returnType = null;
213+
if (isTo && e instanceof CtMethod<?> method) {
214+
CtTypeReference<?> mt = method.getType();
215+
if (mt != null && !"void".equals(mt.toString())) {
216+
p = p.substituteVariable("return", Keys.WILDCARD);
217+
returnType = mt;
218+
}
219+
}
212220
CtTypeReference<?> r = tc.getFactory().Type().createReference(targetClass);
213221
String nameOld = String.format(Formats.INSTANCE, Keys.THIS, tc.getContext().getCounter());
214222
String name = String.format(Formats.INSTANCE, Keys.THIS, tc.getContext().getCounter());
215223
tc.getContext().addVarToContext(Keys.THIS, r, new Predicate(), e);
216224
tc.getContext().addVarToContext(name, r, new Predicate(), e);
217225
tc.getContext().addVarToContext(nameOld, r, new Predicate(), e);
226+
if (returnType != null && !tc.getContext().hasVariable(Keys.WILDCARD))
227+
tc.getContext().addVarToContext(Keys.WILDCARD, returnType, new Predicate(), e);
218228
// TODO REVIEW!!
219229
// what is it for?
220230
Predicate c1 = isTo ? getMissingStates(targetClass, tc, p) : p;
@@ -481,7 +491,10 @@ private static void changeState(TypeChecker tc, VariableInstance vi, List<Object
481491
found = tc.checkStateSMT(prevCheck, expectState, invocation.getPosition());
482492
if (found && stateChange.hasTo()) {
483493
String newInstanceName = String.format(Formats.INSTANCE, name, tc.getContext().getCounter());
484-
Predicate transitionedState = stateChange.getTo().substituteVariable(Keys.WILDCARD, newInstanceName)
494+
// Non-void: `_` is the return value; void: legacy alias for the new instance.
495+
String returnViName = (String) invocation.getMetadata(Keys.RETURN_VAR_NAME);
496+
String wildcardTarget = returnViName != null ? returnViName : newInstanceName;
497+
Predicate transitionedState = stateChange.getTo().substituteVariable(Keys.WILDCARD, wildcardTarget)
485498
.substituteVariable(Keys.THIS, newInstanceName);
486499
for (String s : map.keySet()) {
487500
transitionedState = transitionedState.substituteVariable(s, map.get(s));

liquidjava-verifier/src/main/java/liquidjava/utils/constants/Keys.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ public final class Keys {
44
public static final String REFINEMENT = "refinement";
55
public static final String REFINEMENT_SAT_CHECK = "refinement_sat_check";
66
public static final String TARGET = "target";
7+
public static final String RETURN_VAR_NAME = "return_var_name";
78
public static final String THIS = "this";
89
public static final String WILDCARD = "_";
910
public static final String OLD = "old";

0 commit comments

Comments
 (0)