Class ValueTrackingAnalyzer
Maintains a constraint state per program point and manages branch splitting/joining as control flow constructs (if/else, switch, while, for, try/catch) are entered and exited. This is a single-pass AST walk analyzer that exploits EK9's perfectly nested SESE control flow (no break/continue/return/goto) for O(N) analysis.
Phase 1 tracks: Integer, Boolean, and String constants. Variables assigned from function calls or non-pure mutations become TOP (unknown).
The analyzer is scoped per function/method/operator. It is reset when entering a new callable scope and produces results at expression evaluation points within that scope.
-
Constructor Summary
ConstructorsConstructorDescriptionCreate a new analyzer (inactive until a callable scope is entered). -
Method Summary
Modifier and TypeMethodDescriptionvoidEnter a branching construct (if/switch).voidEnter a callable scope (function, method, operator).voidEnter a catch block.voidEnter a finally block.voidEnter a loop body.voidenterTry()Enter a try block.evaluateCondition(AbstractValue left, String operator, AbstractValue right) Evaluate a condition between two abstract values.evaluateIsEmpty(ISymbol symbol) Evaluate the empty state of a variable based on its tracked value.evaluateIsSet(ISymbol symbol) Evaluate the isSet state of a variable based on its tracked value.voidExit a branching construct.voidExit a branching construct where only the true branch existed (no else).voidExit a callable scope.voidexitLoop()Exit a loop body.voidExit a loop body that is GUARANTEED to run at least once - thedo ... whileform.voidExit a try/catch construct.getMayValues(ISymbol symbol) The constant values that reach this point on SOME path — the MAY side of the analysis.booleanisActive()Check if the analyzer is currently tracking within a callable scope.booleanCheck if currently inside a loop body.booleanCheck if the current program point is unreachable.voidMark the current state as unreachable (after a throw statement).voidnarrowForCondition(ISymbol symbol, String operator, long operandValue, boolean isTrueBranch) 🔑 Narrowing must reduce the MAY side too, or it goes stale. These paths write the MUST side DIRECTLY rather than throughrecordAssignment(ISymbol, AbstractValue), so without this the may-set keeps a value the narrowing has just ruled out — and a value that provably does NOT arrive would be reported as if it did.voidnarrowForIsSet(ISymbol symbol, boolean isTrueBranch) Narrow a variable's value based on an isSet check.voidrecordAssignment(ISymbol symbol, AbstractValue abstractValue) Record a variable assignment with a known constant value.voidrecordMayValues(ISymbol symbol, Set<Object> values) Record thatsymbol's value is not known exactly, but is one ofvalues.voidrecordUnknownAssignment(ISymbol symbol) voidregisterModuleConstant(ISymbol symbol, AbstractValue abstractValue) Register a module-level constant (from defines constant blocks).voidRecord the end of the current branch (e.g., end of if-body) and switch to the alternative branch state (e.g., else-body or else-if).
-
Constructor Details
-
ValueTrackingAnalyzer
public ValueTrackingAnalyzer()Create a new analyzer (inactive until a callable scope is entered).
-
-
Method Details
-
enterCallableScope
public void enterCallableScope()Enter a callable scope (function, method, operator). Resets the analyzer state for a fresh analysis. -
exitCallableScope
public void exitCallableScope()Exit a callable scope. -
isActive
public boolean isActive()Check if the analyzer is currently tracking within a callable scope. -
registerModuleConstant
Register a module-level constant (from defines constant blocks). These persist across callable scope resets and are available for value lookups.- Parameters:
symbol- the constant symbolabstractValue- the constant's value
-
recordAssignment
Record a variable assignment with a known constant value.- Parameters:
symbol- the variable being assignedabstractValue- the abstract value being assigned
-
recordMayValues
Record thatsymbol's value is not known exactly, but is one ofvalues.This is the shape an expression with ARMS produces — a ternary, or a switch/try/loop expression whose returning parameter is assigned differently per arm. The MUST side stays TOP because the arms disagree; the MAY side keeps all of them, which is exactly the information a "could this Panic?" question needs.
-
recordUnknownAssignment
-
getMayValues
The constant values that reach this point on SOME path — the MAY side of the analysis.getValue(ISymbol)answers "what is this definitely?", which joins to TOP when branches disagree. This answers "what could this be?", which unions instead. Use it when a SINGLE bad path is already a defect — a constrained construction Panics on the path that reaches it, whether or not the other branch would have been fine.Empty means nothing is provable, NOT that the variable is unconstrained.
-
getValue
-
evaluateCondition
Evaluate a condition between two abstract values.- Parameters:
left- the left operand valueoperator- the comparison operatorright- the right operand value- Returns:
- TRUE if always satisfied, FALSE if never, UNKNOWN if indeterminate
-
evaluateIsSet
Evaluate the isSet state of a variable based on its tracked value.A variable is provably SET if it holds a ConstantValue, RangeValue, or SetStateValue(SET). A variable is provably UNSET only if it holds SetStateValue(UNSET). Otherwise, the result is UNKNOWN.
- Parameters:
symbol- the variable to check- Returns:
- TRUE if provably SET, FALSE if provably UNSET, UNKNOWN otherwise
-
evaluateIsEmpty
Evaluate the empty state of a variable based on its tracked value.A variable is provably EMPTY if it holds a CollectionStateValue with isEmpty=true. A variable is provably NON-EMPTY if it holds a CollectionStateValue with isEmpty=false. Otherwise, the result is UNKNOWN.
- Parameters:
symbol- the variable to check- Returns:
- TRUE if provably EMPTY, FALSE if provably NON-EMPTY, UNKNOWN otherwise
-
narrowForIsSet
Narrow a variable's value based on an isSet check.On the true branch (isSet is true), records the variable as SET. On the false branch (isSet is false), records the variable as UNSET. If the variable already has a more precise value (ConstantValue, RangeValue), preserves the more precise value on the true branch.
- Parameters:
symbol- the variable being checkedisTrueBranch- true for the true/if branch, false for the else branch
-
narrowForCondition
public void narrowForCondition(ISymbol symbol, String operator, long operandValue, boolean isTrueBranch) 🔑 Narrowing must reduce the MAY side too, or it goes stale. These paths write the MUST side DIRECTLY rather than throughrecordAssignment(ISymbol, AbstractValue), so without this the may-set keeps a value the narrowing has just ruled out — and a value that provably does NOT arrive would be reported as if it did. Measured:size <- 20; if size == 15reported 20 as reaching a construction inside the branch, where the value is provably 15.The rule is: a narrowing that pins an EXACT constant sets the may-set to exactly that; every other narrowing (range, excludes, set-state) INVALIDATES it, because a range cannot be enumerated as constants and a stale entry is worse than none.
-
enterBranch
public void enterBranch()Enter a branching construct (if/switch). Pushes the current state as a snapshot for the false/else branch and creates a new scope for tracking branch exit states. -
switchToAlternateBranch
public void switchToAlternateBranch()Record the end of the current branch (e.g., end of if-body) and switch to the alternative branch state (e.g., else-body or else-if).Saves the current state as a branch exit state and restores the pre-branch snapshot for the alternative path. The pre-branch state remains on the stack for subsequent alternate branches (else-if chains).
-
exitBranch
public void exitBranch()Exit a branching construct. Joins the current branch state with all accumulated branch exit states from this nesting level.Call this after the final branch (else/default) has been processed.
-
exitBranchNoAlternate
public void exitBranchNoAlternate()Exit a branching construct where only the true branch existed (no else).When there is no else clause, the false path retains the pre-branch state. Join the true-branch exit with the pre-branch snapshot.
-
enterTry
public void enterTry()Enter a try block. Snapshots the pre-try state for catch block entry and creates a scope for tracking try/catch exit states. -
enterCatch
public void enterCatch()Enter a catch block. Restores the pre-try snapshot (conservative).The catch handler can be entered from any exception-throwing point in the try body, so the constraint state reverts to the pre-try snapshot. This is always sound: may miss optimizable cases but never false-positives.
-
exitTryCatch
public void exitTryCatch()Exit a try/catch construct. Joins the try-exit and catch-exit states. -
enterFinally
public void enterFinally()Enter a finally block. Joins the try and catch exit states FIRST, so the finally body runs on the joined state and its writes dominate every path out of the construct.This ordering is the whole point.
finallyis the last child of the try construct in the grammar, so without this its writes would land on the catch-exit state alone and the subsequent join would resurrect the very values it had just overwritten. -
isInsideLoop
public boolean isInsideLoop()Check if currently inside a loop body. Used to suppress tautology detection inside loops because widening has not yet been applied — variables still hold their pre-loop constant values during the first pass through the body. -
enterLoop
public void enterLoop()Enter a loop body. Snapshots the pre-loop state. -
exitLoop
public void exitLoop()Exit a loop body. Applies widening: any variable whose value changed during the loop body is widened to TOP.Uses bounded widening (2 passes max): if pre-loop value differs from post-body value, the variable is widened to TOP. This is sound for EK9 because loops have no break/continue — the body always fully executes or not at all.
-
exitLoopBodyAlwaysRuns
public void exitLoopBodyAlwaysRuns()Exit a loop body that is GUARANTEED to run at least once - thedo ... whileform.Same MUST-side widening as
exitLoop(), but the MAY side is taken from the body alone: the body definitely ran, so a definition it overwrote cannot reach the loop exit. Awhilebody may run zero times and so must keep unioning - that asymmetry is the entire difference between the two forms. -
markUnreachable
public void markUnreachable()Mark the current state as unreachable (after a throw statement). Subsequent code in the same block is dead. -
isUnreachable
public boolean isUnreachable()Check if the current program point is unreachable.
-