packages/deriver/src/Deriver/Evaluator/Execution/State.php
1<?php
2
3declare(strict_types=1);
4
5namespace Deriver\Deriver\Evaluator\Execution;
6
7use Deriver\Deriver\Evaluator\Execution\Control\Handler;
8use Deriver\Deriver\Evaluator\Execution\Control\IteratorCursor;
9use Deriver\Deriver\Evaluator\Execution\Memory\Location;
10use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
11
12/**
13 * One correlated execution path; forks copy state without sharing mutable memory.
14 * @visibility namespace
15 * @visibility Deriver\Deriver\Execution
16 */
17final class State
18{
19 /**
20 * @var array<string, \Deriver\Deriver\Evaluator\Term> SSA expression results.
21 */
22 public array $registers = [];
23 /**
24 * @var array<string, string> Register definitions in the derivation graph.
25 */
26 public array $producers = [];
27 /**
28 * @var array<string, Location> Address registers.
29 */
30 public array $addresses = [];
31 /**
32 * @var array<string, Offset\Address> Raw offset chains retained until their storage operation.
33 */
34 public array $offsets = [];
35 /**
36 * @var array<string, Call\Preparation\Target> Signatures captured before evaluating arguments.
37 */
38 public array $callTargets = [];
39 /**
40 * @var array<string, Transfer\PropertySlot> Property metadata by address register.
41 */
42 public array $properties = [];
43 /**
44 * @var array<string, Location> Local variable bindings.
45 */
46 public array $locals = [];
47 /**
48 * @var array<string, bool> Correlated branch decisions.
49 */
50 public array $guard = [];
51 /**
52 * @var array<string, array{min: int|float|null, max: int|float|null, equal: \Deriver\Deriver\Evaluator\Term|null, excluded: list<\Deriver\Deriver\Evaluator\Term>}> Input constraints.
53 */
54 public array $constraints = [];
55 /**
56 * @var list<Handler> Active exception regions.
57 */
58 public array $handlers = [];
59 /**
60 * @var array<string, IteratorCursor> Active foreach cursors.
61 */
62 public array $iterators = [];
63 /**
64 * @var array<int, int> Loop header visits.
65 */
66 public array $visits = [];
67 /**
68 * @var list<string> Derivation node identifiers.
69 */
70 public array $evidence = [];
71 /**
72 * @var list<string> Definitions that determine this path's reachability.
73 */
74 public array $controls = [];
75 /**
76 * @var array<int, array<string, \Deriver\Deriver\Evaluator\Term>> Loop entry storage.
77 */
78 public array $loopEntries = [];
79 /**
80 * Current CFG block.
81 */
82 public int $block = 0;
83 /**
84 * Previously completed block for Phi selection.
85 */
86 public int $previous = -1;
87 /**
88 * Current completion.
89 */
90 public Completion\Completion $completion;
91 /**
92 * Whether this execution path has reached the query's observation point.
93 */
94 public bool $observed = false;
95 /**
96 * @var array<int, true> Batch observations already reached on this execution path
97 */
98 public array $observedQueries = [];
99 /**
100 * Cause of unknown writes to this frame's symbol table, including future bindings.
101 */
102 public ?string $unknownLocals = null;
103 /**
104 * Late static binding class.
105 */
106 public string $lateStaticClass = '';
107
108 /**
109 * @var array<int, array<string, \Deriver\Deriver\Evaluator\Term>> Widened loop header approximations.
110 */
111 public array $approximations = [];
112 /**
113 * @var array<int, string> Alias and iterator structure at a widened header.
114 */
115 public array $loopStructures = [];
116 /**
117 * @var array<int, array<string, bool>> Guards established outside each loop.
118 */
119 public array $loopGuards = [];
120 /**
121 * Loop header whose body has reached a checked post-fixpoint.
122 */
123 public ?int $stableHeader = null;
124
125 /**
126 * @param Memory $memory Path-specific memory
127 */
128 public function __construct(public Memory $memory = new Memory())
129 {
130 $this->completion = new Completion\Completion();
131 }
132
133 /**
134 * Forks a correlated path with independent subsequent writes.
135 * @return self Forked state
136 */
137 public function fork(): self
138 {
139 $fork = clone $this;
140 $fork->memory = clone $this->memory;
141 return $fork;
142 }
143
144 /**
145 * Resolves a local binding, allocating an uninitialized cell when needed.
146 * @param string $name Variable name
147 * @return Location Address with its local binding identity
148 */
149 public function local(string $name): Location
150 {
151 if (!isset($this->locals[$name])) {
152 $initial = in_array($name, ['_GET', '_POST', '_COOKIE', '_SERVER', '_ENV', '_REQUEST', '_FILES', '_SESSION'], true) ? new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::External, 'superglobal:' . $name, attributes: ['type' => 'array', 'stability' => 'request', 'elementType' => in_array($name, ['_GET', '_POST', '_COOKIE', '_REQUEST'], true) ? 'string|array|null' : 'mixed']) : new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::Uninitialized);
153 if ($this->unknownLocals !== null) {
154 $initial = \Deriver\Deriver\Evaluator\Term::opaque($this->unknownLocals);
155 }
156 $this->locals[$name] = $this->memory->allocate($initial);
157 }
158 $location = $this->locals[$name];
159 return new Location($location->root, $location->path, $name, $location->unknown);
160 }
161
162 /**
163 * Rebinds a PHP reference without retargeting other aliases of the old cell.
164 * @param Location $destination Left address
165 * @param Location $source Right address
166 */
167 public function alias(Location $destination, Location $source): void
168 {
169 if ($source->unknown) {
170 $this->unknownAlias($destination, $source);
171 return;
172 }
173 $cell = $this->memory->reference($source);
174 if ($destination->local !== '' && $destination->path === []) {
175 if (str_starts_with($destination->root, 'global:') && $destination->root !== $cell) {
176 $this->memory->writePath($destination->root, [], new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::Cell, $cell), true);
177 }
178 $this->locals[$destination->local] = new Location($cell);
179 return;
180 }
181 $this->memory->writePath($destination->root, $destination->path, new \Deriver\Deriver\Evaluator\Term(\Deriver\Deriver\Evaluator\TermKind::Cell, $cell), true);
182 }
183
184 /**
185 * Binds a reference to an unknown slot of known storage so that writes through either side invalidate that storage.
186 * @param Location $destination Left address
187 * @param Location $source Unknown slot, whose root is the storage it may belong to
188 */
189 public function unknownAlias(Location $destination, Location $source): void
190 {
191 if ($destination->local !== '' && $destination->path === [] && !str_starts_with($destination->root, 'global:')) {
192 $this->locals[$destination->local] = new Location($source->root, $source->path, $destination->local, true);
193 return;
194 }
195 $this->memory->write($source, \Deriver\Deriver\Evaluator\Term::opaque('UNKNOWN_REFERENCE'));
196 $this->memory->write($destination, \Deriver\Deriver\Evaluator\Term::opaque('UNKNOWN_REFERENCE'));
197 }
198
199 /**
200 * Returns a register while keeping absent computation distinct from null.
201 * @param string $register SSA register
202 * @return \Deriver\Deriver\Evaluator\Term Available value or an explicit residual
203 */
204 public function value(string $register): \Deriver\Deriver\Evaluator\Term
205 {
206 return $this->memory->dereference($this->registers[$register] ?? \Deriver\Deriver\Evaluator\Term::opaque('UNCOMPUTED_REGISTER'));
207 }
208
209 /**
210 * Produces the observable local state.
211 * @return array<string, \Deriver\Deriver\Evaluator\Term> Materialized local values
212 */
213 public function snapshot(): array
214 {
215 $values = [];
216 foreach ($this->locals as $name => $location) {
217 $values[$name] = $this->memory->materialize($this->memory->read($location));
218 }
219 ksort($values);
220 return $values;
221 }
222}
223