Skip to content

Conversation

@wadoon
Copy link
Member

@wadoon wadoon commented Jul 26, 2025

Related Issue

InfFlow and WD are functionalities that tear furrows through the key.core module. Meaning, that each of both functionalities are an instance-of in-place hack of the default implementations.
For example, consider the WhileInvariantRule in which WD and IF fall together. On WD, four sub-goals are generated, on IF two-subgoals are altered.

Isolating these functionalities and putting them into separate modules is a great improvement (a) for comprehensibility, (b) for modularity, and (c) a blueprint for "how to extend KeY".

Changes

  • IF
    • Information flow now happens with key.core.infflow incl. tests.
    • Only the specification (grammar + a few methods) remains in key.core,
      but the construction of contracts is in key.core.infflow. Loading JavaRedux w/o k.c.infflow results into a warning on stdout, giving you a notice that the IF module is missing and IF contracts are now loaded. This circumstance should only happen (a) on test cases, or (b) on library uses of key.core.
    • The required built-in rules for IF are now enforced by a profile. Use this profile if you want do IF.
    • testRunAllInfFlow is moved to key.core.infflow and should work.
  • WD happens now in key.core.wd
    • Introduction of key.core.wd:testRunAllWdProofs which activates the WD proofs from years ago. I do not guarantee their success.
    • WD requires a special SpecificationRepository which adds WD contracts when some other contracts are added.
    • Enforcement of Taclet options to enable WD by WD profile. wdChecks:on is not enforced by the profile.
    • Remove WD Taclets from standardRules.key set, and allow more standard Taclet files to be defined by Profiles. standardRules.key does not load wd anymore. Taclet bases are now an immutable list of RuleSource. It would be better if the taclet base classpath is not fix yet.
  • key.ui
    • Option selector in KeYFileChooser for enforcing a profile (required loading Java files directly)
    • Status line shows the Profile of the current proof
  • key.core
    • Some built-In rules are rewritten for extensibility by inheritance, ie., WhileInvariantRule and BlockInternalContract.
    • Removal WD and IF code and test cases.
    • TacletPOGenerator allows the generation of Taclets after the specifications are read. This interface is required to mimic void generateWdTaclets(InitConfig proofConfig) on a generic level.
    • ComplexJustificationable allows to mark BuiltinRule s.t. Java Profile generates for marked rules a ComplexRuleJustification entry in the rule justification map. Previously, this was a static IF query for four fixed built-in rules.

Todo

  • Bug, confirmed, 12.01.2026 UI Proof Tree is nearly empty after usage of the auto-infflow macro:

    image

    Exception is

Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: abstract tree node without parent: Proof Tree
	at de.uka.ilkd.key.gui.prooftree.GUIAbstractTreeNode.getParent(GUIAbstractTreeNode.java:49)
	at de.uka.ilkd.key.gui.prooftree.GUIAbstractTreeNode.getPath(GUIAbstractTreeNode.java:82)

This bug EXISTS also on the main branch!

Type of pull request

  • Refactoring (behaviour should not change or only minimally change)
  • New feature (non-breaking change which adds functionality)
    • New extension points in key.core for deep functionality manipulation

Ensuring quality

  • I made sure that introduced/changed code is well documented (javadoc and inline comments).
  • I made sure that new/changed end-user features are well documented (https://github.com/KeYProject/key-docs).
  • I added new test case(s) for new functionality.
  • I have tested the feature as follows: ci
  • I have checked that runtime performance has not deteriorated.
  • For new Gradle modules: I added the Gradle module to the test matrix in
    .github/workflows/tests.yml

Old Stuff

Related Issue

InfFlow (and WD) are functionalities that tear furrows through the key.core module. Following packages/classes are directly depending on the infflow classes:

image

Isolating these functionalities could be a great improvement (a) for comprehensibility and (b) for modularity.

Current state: A prototype for evaluating the possible strategies:

Strategy: Hooks

In WileInvariantRule, InfFlow manipulates the "bodyGoal", while WD adds a fourth goal. The new hook WhileInvariantHook allows both functionalities by supplying too many parameters.

    /// A hook for manipulating the execution of the [WhileInvariantRule]. Allows addition or
    /// manipulation of sub-goals.
    ///
    /// @author weigl
    public interface WhileInvariantHook {
        /// This hook allows you to add new sub-goals for the [WhileInvariantRule].
        /// You get too many parameters. If you return a non-null consumer, a goal is created on
        /// which
        /// the returned consumer is applied. This allows for dynamic selection whether an
        /// additional is required.
        ///
        /// @return null if no additional [Goal] should be created.
        @Nullable
        default Consumer<Goal> createGoal(RuleApp ruleApp, Instantiation inst, Services services,
                LoopInvariantBuiltInRuleApp loopRuleApp, JavaBlock guardJb,
                ImmutableSet<LocationVariable> localIns, ImmutableSet<LocationVariable> localOuts,
                ImmutableList<AnonUpdateData> anonUpdateDatas, JTerm anonUpdate,
                List<LocationVariable> heapContext, JTerm anonHeap, JTerm localAnonUpdate) {
            return null;
        }

        /// Allows to rewrite the goals `init`, `preserve`, `terminate` of the [WhileInvariantRule] directly.
        default void rewriteStandardGoals(Services services, Goal init, Goal preserve,
                Goal terminate, Instantiation inst,
                LoopInvariantBuiltInRuleApp loopRuleApp, JavaBlock guardJb,
                ImmutableSet<LocationVariable> localIns,
                ImmutableSet<LocationVariable> localOuts,
                ImmutableList<AnonUpdateData> anonUpdateDatas,
                JTerm anonUpdate) {
        }
    }

In the end, I would propose the following base class:

class WhileInvariantRule : BuiltInRule {
  val hooks = findHooks(WhileInvariantRuleProcessor.class);

  @Override fun ImmutableList<Goal> applyTo(...) = 
       new WhileInvariantRuleProcessor(hooks, ...).call()
}

class WhileInvariantRuleProcessor(val hooks:Hooks, val pio:PIO, val goal:Goal)
   implements Callable<IList<Goals>> {
  // instantiations as member variable 

  init {
    // init instantiation, find heaps, etc...
  }

  @Override fun ImmutableList<Goal> call() {
      hooks.afterInit(this);
      val subGoalsTransformers = listOf(this::init, this::preserve, this:terminate)
                 + hooks.map { it.createGoal(this) }.filterNotNull();
      val subgoals = goal.split(subGoalsTransformers);
      hooks.afterSubGoals(this);
      return subgoals; 
  }
}

Strategy 2: Inheritance + Profile

BlockContractInternalRule had an isolated decision for validity in InfFlow proofs. Here, using inheritance is a good option. The downside is that we now need a "Java InfFlow Profile", because we need to disable the traditional BlockContractInternalRule and activate InfFlowBlockContractInternalRule:

public class JavaInfFlowProfile extends JavaProfile {
    public static final String NAME = "Java InfFlow Profile";

    @Override
    protected ImmutableList<BuiltInRule> initBuiltInRules() {
        var rules = super.initBuiltInRules();
        return rules.map(it -> {
            if (it == BlockContractInternalRule.INSTANCE) {
                return InfFlowBlockContractInternalRule.INSTANCE;
            }
            return it;
        });
    }
}

But in many places, BlockContractInternalRule is directly used:

AuxiliaryContractBuilders.java
BlockContractInternalBuiltInRuleApp.java
BlockContractInternalCompletion.java
BlockContractInternalRule.java
BlockContractValidityTermLabelUpdate.java
CurrentGoalViewMenu.java
InfFlowBlockContractInternalRule.java
JavaInfFlowProfile.java
JavaProfile.java
RemoveInCheckBranchesTermLabelRefactoring.java
StaticFeatureCollection.java
SymbolicExecutionTermLabelUpdate.java

This might result in strange behavior, e.g., the context menu CurrentGoalViewMenu has a special treatment for which it checks for identity with BlockContractInternalRule.INSTANCE.

Discussion

  • Option 1:
    • + allows combining multiple extensions together. WD and Infflow can exist side-by-side in the same proof.
    • - increases the variant space of KeY. Extensions can be combined together, s.t. nobody has tested them.
    • - requires rather complex classes/types for handling the variance of built-in rules.
    • + keeps the number of Profiles low.
  • Option 2:
    • + is natural option for Java ecosystem.
    • - requires opening/rewriting of built-in rules. Also, comparison of identity (rule == XXX.INSTANCE) needs to be removed.
    • + is also a canonical way for KeY. Profile is already built-in and allows the manipulation of much more than built-in rules, e.g., Taclet Options, Taclet paths, etc.
    • - arbitrary combination of multiple variants (WD, Infflow) is excluded.

Side Observations

  • WD is globally in KeY, but it should rather be dependent on the proof settings.

      /**
       * This method checks, if well-definedness checks are generally turned on or off.
       *
       * @return true if on and false if off
       */
      public static boolean isOn() {
          final String setting =
              ProofSettings.DEFAULT_SETTINGS.getChoiceSettings().getDefaultChoices().get(OPTION);
          if (setting == null) {
              return false;
          }
          if (setting.equals(OPTION + ":on")) {
              return true;
          } else if (setting.equals(OPTION + ":off")) {
              return false;
          } else {
              throw new RuntimeException(
                  "The setting for the wdProofs-option is not valid: " + setting);
          }
      }
  • Using Profiles can tighten the standardRules.key to the base case.

Intended Change

Isolating InfFlow (and WD) inside core into their own package s.t. these functionalities are concentrated and do not pollute the system architecture. InfFlow/WD was integrated using the "instanceOf" and patch-style, hence, you can find hard dependencies in many Built-in Rules to these classes.

We might not get infflow/wd inside external modules (b/c of cycle dependency to key.core), but an encapsulation should be achievable.

This might also be a great opportunity to sharpen the modularity of key.core.

Plan

  • First step: Finding the right strategy

Type of pull request

  • Refactoring (behaviour should not change or only minimally change)
  • New feature (non-breaking change which adds functionality)
    • New extension points in key.core for deep functionality manipulation

Ensuring quality

  • I made sure that introduced/changed code is well documented (javadoc and inline comments).
  • I made sure that new/changed end-user features are well documented (https://github.com/KeYProject/key-docs).
  • I added new test case(s) for new functionality.
  • I have tested the feature as follows: ci
  • I have checked that runtime performance has not deteriorated.
  • For new Gradle modules: I added the Gradle module to the test matrix in
    .github/workflows/tests.yml

@wadoon wadoon requested a review from unp1 July 26, 2025 00:54
@wadoon wadoon self-assigned this Jul 26, 2025
@wadoon wadoon added the 🛠 Maintenance Code quality and related things w/o functional changes label Jul 26, 2025
@wadoon wadoon added this to the v2.12.4 milestone Jul 26, 2025
@wadoon wadoon force-pushed the weigl/infflow branch 2 times, most recently from 55807a9 to 9f24067 Compare November 22, 2025 00:17
@wadoon wadoon force-pushed the weigl/infflow branch 2 times, most recently from 564ba71 to c08f3e7 Compare January 4, 2026 16:11
@Drodt Drodt modified the milestones: v2.13.0, v3.0.0 Jan 7, 2026
@wadoon wadoon changed the title Tipping the foot into InfFlow refactoring Modularization: InfFlow and WD as separate modules Jan 8, 2026
@wadoon
Copy link
Member Author

wadoon commented Jan 9, 2026

Failing tests:

image

@wadoon
Copy link
Member Author

wadoon commented Jan 12, 2026

@Drodt @unp1 Here I try to avoid a ClassCastExceptions by not directly casting the instantiation to a Term. In WD world, it seems that an instantiation is also ImmutableArray (of term labels) sometimes.

I would get rid of the else case (sysout). The only useful meaning would be to check each entry of an ImmutableArray if this entry is a Term.

@wadoon wadoon marked this pull request as ready for review January 12, 2026 18:57
@wadoon
Copy link
Member Author

wadoon commented Jan 12, 2026

A lot of WD test cases assert the "unprovability". Besides these cases, the erroneous cases are limited:

image
  • How to deal with WD test cases? Who is willing to invest time?
  • I would like to deactivate the unprovable test cases. The provable test cases takes 6 min.; 47 min. is taken to demonstrate unsuccessful proof search.
  • Which rules should be applicable in the WD profile? For example, currently the loop scope taclet interfere with WDWhileInvariantRule (and I do not know how to suppress this, Proof settings does not help). The loopScope taclet does not provide a WD goal (@rindPHI). Should we integrate a goal?

@wadoon wadoon requested a review from flo2702 January 12, 2026 19:06
@wadoon
Copy link
Member Author

wadoon commented Jan 12, 2026

Added @flo2702 for checking the block contract world.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

🛠 Maintenance Code quality and related things w/o functional changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants