Skip to content

Commit 20e7a45

Browse files
Compute the receiver's ghost states once and share the method lookup by name and arity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 343c7f6 commit 20e7a45

2 files changed

Lines changed: 31 additions & 33 deletions

File tree

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

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -797,12 +797,9 @@ private static void changeState(TypeChecker tc, VariableInstance vi, RefinedFunc
797797
Predicate expected = expectedStatesDisjunction;
798798
boolean checked = deciding.isEmpty() && states
799799
.map(r -> checkedElsewhere(tc, r, prevState, expected, instanceName, invocation)).orElse(false);
800-
ErrorCategory category = deciding
801-
.isPresent()
802-
? ErrorCategory.ARGUMENT_PICKS_THE_STATE : states
803-
.flatMap(r -> StateErrorCategories.categorize(r.machine(), r.current(), prevState,
804-
checked, StateMachineBuilder.ghostStatesOf(tc, r.className())))
805-
.orElse(null);
800+
ErrorCategory category = deciding.isPresent() ? ErrorCategory.ARGUMENT_PICKS_THE_STATE : states.flatMap(
801+
r -> StateErrorCategories.categorize(r.machine(), r.current(), prevState, checked, r.ghostStates()))
802+
.orElse(null);
806803
List<Hint> hints = deciding.isPresent()
807804
? receiverOf(invocation).map(r -> StateHints.decidingArgument(deciding.get(), r)).stream().toList()
808805
: states.map(r -> stateHints(r, function, category, checked, invocation)).orElse(List.of());
@@ -814,8 +811,11 @@ private static void changeState(TypeChecker tc, VariableInstance vi, RefinedFunc
814811
}
815812
}
816813

817-
/** The state machine of the receiver's class and the states the receiver can be in before a call */
818-
private record ReceiverStates(String className, StateMachine machine, Set<State> current) {
814+
/**
815+
* The state machine of the receiver's class, the states the receiver can be in before a call, and the ghost states
816+
* of the class and its supertypes
817+
*/
818+
private record ReceiverStates(StateMachine machine, Set<State> current, List<GhostState> ghostStates) {
819819
}
820820

821821
/**
@@ -831,7 +831,8 @@ private static Optional<ReceiverStates> receiverStates(TypeChecker tc, RefinedFu
831831
return Optional.empty();
832832
Set<State> current = new StateSolver(tc, invocation.getPosition()).possibleStates(machine.get().states(),
833833
state, receiver.getName());
834-
return Optional.of(new ReceiverStates(className, machine.get(), current));
834+
List<GhostState> ghostStates = StateMachineBuilder.ghostStatesOf(tc, className);
835+
return Optional.of(new ReceiverStates(machine.get(), current, ghostStates));
835836
} catch (RuntimeException e) {
836837
// a failure to analyse the states must not replace the error being reported
837838
DebugLog.warn("Could not compute the receiver's states: " + e.getMessage());
@@ -844,8 +845,7 @@ private static boolean checkedElsewhere(TypeChecker tc, ReceiverStates states, P
844845
String instanceName, CtElement invocation) {
845846
try {
846847
return StateErrorCategories.checkedElsewhere(states.machine(), states.current(), found,
847-
() -> tc.shownFound(found, expected), instanceName,
848-
StateMachineBuilder.ghostStatesOf(tc, states.className()),
848+
() -> tc.shownFound(found, expected), instanceName, states.ghostStates(),
849849
new StateSolver(tc, invocation.getPosition()));
850850
} catch (RuntimeException e) {
851851
DebugLog.warn("Could not tell whether the receiver's state was checked: " + e.getMessage());

‎liquidjava-verifier/src/main/java/liquidjava/processor/state_machine/StateMachineBuilder.java‎

Lines changed: 20 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import liquidjava.utils.constants.Keys;
2424
import spoon.reflect.declaration.CtClass;
2525
import spoon.reflect.declaration.CtElement;
26+
import spoon.reflect.declaration.CtMethod;
2627
import spoon.reflect.declaration.CtModifiable;
2728
import spoon.reflect.declaration.CtType;
2829
import spoon.reflect.reference.CtExecutableReference;
@@ -200,27 +201,24 @@ private StateMachine build(CtTypeReference<?> type, List<CtTypeReference<?>> sup
200201
if (!function.hasStateChange())
201202
continue;
202203
boolean constructor = CtExecutableReference.CONSTRUCTOR_NAME.equals(function.getName());
203-
if (className.equals(function.getTargetClass()) && constructor) {
204+
boolean own = className.equals(function.getTargetClass());
205+
if (own && constructor) {
204206
if (abstractType(type) || privateConstructor(function, type))
205207
continue; // cannot be suggested as a new object
206208
Optional<Set<State>> created = createdStates(function);
207209
created.ifPresent(s -> constructors.put(function, s));
208210
continue;
209211
}
210-
if (className.equals(function.getTargetClass()) && !appliesToOwnType(function, type, supertypes)) {
212+
if (!own && (constructor || !inherited.contains(function.getTargetClass())))
213+
continue;
214+
if (!own && overridden(function, className))
215+
continue; // the class's own refinement for it is the one in the machine
216+
boolean applies = own ? appliesToOwnType(function, type, supertypes)
217+
: resolvesTo(function, type, supertypes) && publiclyInherited(function);
218+
if (!applies) {
211219
complete = false;
212220
continue;
213221
}
214-
if (!className.equals(function.getTargetClass())) {
215-
if (constructor || !inherited.contains(function.getTargetClass()))
216-
continue;
217-
if (overridden(function, className))
218-
continue; // the class's own refinement for it is the one in the machine
219-
if (!resolvesTo(function, type, supertypes) || !publiclyInherited(function)) {
220-
complete = false;
221-
continue;
222-
}
223-
}
224222
Optional<List<Transition>> calls = transitionsOf(function);
225223
if (calls.isPresent()) {
226224
methods.add(function);
@@ -271,15 +269,11 @@ private static List<String> parameterTypes(RefinedFunction function) {
271269
* parameters, so an overload can hide an inherited method, which only leaves the machine incomplete.
272270
*/
273271
private boolean resolvesTo(RefinedFunction method, CtTypeReference<?> type, List<CtTypeReference<?>> supertypes) {
274-
String name = Utils.getSimpleName(method.getName());
275-
int arity = method.getArguments().size();
276272
for (CtTypeReference<?> t : Stream.concat(Stream.of(type), supertypes.stream()).toList()) {
277273
CtType<?> declaration = t.getTypeDeclaration();
278274
if (declaration == null)
279275
return false; // cannot tell which type declares it
280-
boolean declares = declaration.getMethods().stream()
281-
.anyMatch(m -> m.getSimpleName().equals(name) && m.getParameters().size() == arity);
282-
if (declares)
276+
if (declarationsOf(method, declaration).findAny().isPresent())
283277
return t.getQualifiedName().equals(method.getTargetClass());
284278
}
285279
return false;
@@ -288,18 +282,22 @@ private boolean resolvesTo(RefinedFunction method, CtTypeReference<?> type, List
288282
/**
289283
* Whether any caller can call {@code method} on an object of a subtype of the type it is specified for. The machine
290284
* is shared by every caller, so a method that only some callers can reach (private, package-private or protected)
291-
* is left out, which only leaves the machine incomplete. Methods are matched by name and number of parameters.
285+
* is left out, which only leaves the machine incomplete.
292286
*/
293287
private boolean publiclyInherited(RefinedFunction method) {
294288
CtType<?> declaring = tc.getFactory().Type().createReference(method.getTargetClass()).getTypeDeclaration();
295289
if (declaring == null)
296290
return false; // cannot tell whether it is public
291+
// interface methods are public unless declared private
292+
return declarationsOf(method, declaring).allMatch(m -> declaring.isInterface() ? !m.isPrivate() : m.isPublic());
293+
}
294+
295+
/** The methods of {@code type} that {@code method} may refer to: those with its name and number of parameters */
296+
private static Stream<CtMethod<?>> declarationsOf(RefinedFunction method, CtType<?> type) {
297297
String name = Utils.getSimpleName(method.getName());
298298
int arity = method.getArguments().size();
299-
// interface methods are public unless declared private
300-
return declaring.getMethods().stream()
301-
.filter(m -> m.getSimpleName().equals(name) && m.getParameters().size() == arity)
302-
.allMatch(m -> declaring.isInterface() ? !m.isPrivate() : m.isPublic());
299+
return type.getMethods().stream()
300+
.filter(m -> m.getSimpleName().equals(name) && m.getParameters().size() == arity);
303301
}
304302

305303
private static boolean abstractType(CtTypeReference<?> type) {

0 commit comments

Comments
 (0)