packages/deriver/tests/Unit/Deriver/Evaluator/Execution/Transfer/MemoryStepTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution\Transfer;
6
7use Deriver\Deriver;
8use Deriver\Deriver\Candidate\ResultRef;
9use Deriver\Deriver\Candidate\Snapshot;
10use Deriver\Deriver\Candidate\Statistics;
11use Deriver\Deriver\Contributions;
12use Deriver\Deriver\DeriverConfig;
13use Deriver\Deriver\Evaluator\Call\BoundInstruction;
14use Deriver\Deriver\Evaluator\Call\Evaluators;
15use Deriver\Deriver\Evaluator\Call\Extensions;
16use Deriver\Deriver\Evaluator\Call\Registrations;
17use Deriver\Deriver\Evaluator\Call\StateRegistry;
18use Deriver\Deriver\Evaluator\EvaluatorConfig;
19use Deriver\Deriver\Evaluator\Evidence\Frontier;
20use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
21use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
22use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
23use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
24use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
25use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
26use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
27use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
28use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
29use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
30use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
31use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
32use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
33use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
34use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
35use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
36use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
37use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
38use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
39use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
40use Deriver\Deriver\Evaluator\Execution\Constraints;
41use Deriver\Deriver\Evaluator\Execution\Context;
42use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
43use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
44use Deriver\Deriver\Evaluator\Execution\Control\Handler;
45use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
46use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
47use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
48use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
49use Deriver\Deriver\Evaluator\Execution\Dependencies;
50use Deriver\Deriver\Evaluator\Execution\Evaluator;
51use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
52use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
53use Deriver\Deriver\Evaluator\Execution\Memory\Location;
54use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
55use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
56use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
57use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
58use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
59use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
60use Deriver\Deriver\Evaluator\Execution\Offset\Address;
61use Deriver\Deriver\Evaluator\Execution\Offset\Path;
62use Deriver\Deriver\Evaluator\Execution\Offset\Transfer as OffsetTransfer;
63use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
64use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
65use Deriver\Deriver\Evaluator\Execution\State;
66use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
67use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
68use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
69use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
70use Deriver\Deriver\Evaluator\Execution\Transfer\PropertySlot;
71use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
72use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
73use Deriver\Deriver\Evaluator\Request\Budget;
74use Deriver\Deriver\Evaluator\Request\EntryPoint;
75use Deriver\Deriver\Evaluator\Request\QueryScope;
76use Deriver\Deriver\Evaluator\Request\ReturnQuery;
77use Deriver\Deriver\Evaluator\Scalar\Arithmetic;
78use Deriver\Deriver\Evaluator\Scalar\Arrays;
79use Deriver\Deriver\Evaluator\Scalar\Identity;
80use Deriver\Deriver\Evaluator\Scalar\Increment;
81use Deriver\Deriver\Evaluator\Scalar\Operations;
82use Deriver\Deriver\Evaluator\TargetProfile;
83use Deriver\Deriver\Evaluator\Term;
84use Deriver\Deriver\Evaluator\TermKind;
85use Deriver\Deriver\Execution\DerivationResult;
86use Deriver\Deriver\Execution\QueryExecution;
87use Deriver\Deriver\Execution\ResultAssessment;
88use Deriver\Deriver\Identity\QueryEncoding;
89use Deriver\Deriver\Identity\ValueGraph;
90use Deriver\Deriver\Lowerer\AggregateLowering;
91use Deriver\Deriver\Lowerer\AssignmentLowering;
92use Deriver\Deriver\Lowerer\CallLowering;
93use Deriver\Deriver\Lowerer\Control\ConditionalLowering;
94use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
95use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
96use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
97use Deriver\Deriver\Lowerer\Declaration\CallableSource;
98use Deriver\Deriver\Lowerer\Declaration\Index;
99use Deriver\Deriver\Lowerer\Declaration\Indexer;
100use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
101use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
102use Deriver\Deriver\Lowerer\ExpressionLowering;
103use Deriver\Deriver\Lowerer\Graph\Argument;
104use Deriver\Deriver\Lowerer\Graph\BasicBlock;
105use Deriver\Deriver\Lowerer\Graph\CallableGraph;
106use Deriver\Deriver\Lowerer\Graph\CatchTarget;
107use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
108use Deriver\Deriver\Lowerer\Graph\Instruction;
109use Deriver\Deriver\Lowerer\Graph\Parameter;
110use Deriver\Deriver\Lowerer\Graph\Terminator;
111use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
112use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
113use Deriver\Deriver\Lowerer\InstructionEmitter;
114use Deriver\Deriver\Lowerer\Lowerer;
115use Deriver\Deriver\Lowerer\LoweringScope;
116use Deriver\Deriver\Lowerer\StatementLowering;
117use Deriver\Deriver\MicroEvaluatorConversion;
118use Deriver\Deriver\Parser\Node\File;
119use Deriver\Deriver\Parser\Parser;
120use Deriver\Deriver\Parser\ParserConfig;
121use Deriver\Deriver\Parser\TargetSyntax;
122use Deriver\Deriver\Providers\ProviderExpansion;
123use Deriver\Deriver\Resolver\AssignmentPatterns;
124use Deriver\Deriver\Resolver\ClassScope;
125use Deriver\Deriver\Resolver\ExpressionValidation;
126use Deriver\Deriver\Resolver\MagicContext;
127use Deriver\Deriver\Resolver\Resolver;
128use Deriver\Deriver\Selection\QueryValidation;
129use Deriver\Deriver\SnapshotIdentity;
130use Deriver\Deriver\Sources\LineMap;
131use Deriver\Deriver\Sources\Path as SourcesPath;
132use Deriver\Deriver\Sources\Source;
133use Deriver\Deriver\Sources\SourceRef;
134use Deriver\Deriver\Sources\Sources;
135use Deriver\Exception\ParserException;
136use Deriver\Exception\ResolutionException;
137use PHPUnit\Framework\Attributes\CoversClass;
138use PHPUnit\Framework\Attributes\DataProvider;
139use PHPUnit\Framework\Attributes\Small;
140use PHPUnit\Framework\Attributes\UsesClass;
141use PHPUnit\Framework\TestCase;
142
143#[UsesClass(Resolver::class)]
144#[UsesClass(ExpressionValidation::class)]
145#[UsesClass(ResolutionException::class)]
146#[UsesClass(ParserConfig::class)]
147#[UsesClass(ParserException::class)]
148#[CoversClass(MemoryStep::class)]
149#[UsesClass(QueryExecution::class)]
150#[UsesClass(QueryValidation::class)]
151#[UsesClass(ResultAssessment::class)]
152#[UsesClass(Deriver::class)]
153#[UsesClass(Constraints::class)]
154#[UsesClass(Argument::class)]
155#[UsesClass(BasicBlock::class)]
156#[UsesClass(CallableGraph::class)]
157#[UsesClass(CallableIdentity::class)]
158#[UsesClass(CatchTarget::class)]
159#[UsesClass(ExceptionRegion::class)]
160#[UsesClass(Instruction::class)]
161#[UsesClass(Parameter::class)]
162#[UsesClass(PropertyDeclaration::class)]
163#[UsesClass(Terminator::class)]
164#[UsesClass(ArgumentBinding::class)]
165#[UsesClass(ArgumentOrder::class)]
166#[UsesClass(CallExecutor::class)]
167#[UsesClass(CallResolution::class)]
168#[UsesClass(CallableCheck::class)]
169#[UsesClass(Builtins::class)]
170#[UsesClass(Dispatch::class)]
171#[UsesClass(Properties::class)]
172#[UsesClass(Signatures::class)]
173#[UsesClass(ParameterBinding::class)]
174#[UsesClass(PassedArgument::class)]
175#[UsesClass(Arguments::class)]
176#[UsesClass(Modes::class)]
177#[UsesClass(Resolution::class)]
178#[UsesClass(Target::class)]
179#[UsesClass(Transfer::class)]
180#[UsesClass(TypeBinding::class)]
181#[UsesClass(TypeCheck::class)]
182#[UsesClass(Completion::class)]
183#[UsesClass(Context::class)]
184#[UsesClass(ExceptionChain::class)]
185#[UsesClass(ExceptionMatch::class)]
186#[UsesClass(Handler::class)]
187#[UsesClass(ObservationLimit::class)]
188#[UsesClass(StateJoin::class)]
189#[UsesClass(Unwinding::class)]
190#[UsesClass(Discovery::class)]
191#[UsesClass(Dependencies::class)]
192#[UsesClass(InstructionTransfer::class)]
193#[UsesClass(Evaluator::class)]
194#[UsesClass(SlotReference::class)]
195#[UsesClass(ObservationCollector::class)]
196#[UsesClass(Address::class)]
197#[UsesClass(Path::class)]
198#[UsesClass(OffsetTransfer::class)]
199#[UsesClass(Conversions::class)]
200#[UsesClass(ScalarErrors::class)]
201#[UsesClass(State::class)]
202#[UsesClass(Evaluation::class)]
203#[UsesClass(Isolation::class)]
204#[UsesClass(PropertySlot::class)]
205#[UsesClass(PureStep::class)]
206#[UsesClass(ReferenceAssignment::class)]
207#[UsesClass(Location::class)]
208#[UsesClass(Materialization::class)]
209#[UsesClass(Memory::class)]
210#[UsesClass(ReferenceConstraint::class)]
211#[UsesClass(StorageCapture::class)]
212#[UsesClass(Extensions::class)]
213#[UsesClass(ProviderExpansion::class)]
214#[UsesClass(Evaluators::class)]
215#[UsesClass(StateRegistry::class)]
216#[UsesClass(DeriverConfig::class)]
217#[UsesClass(EntryPoint::class)]
218#[UsesClass(Sources::class)]
219#[UsesClass(SourcesPath::class)]
220#[UsesClass(Snapshot::class)]
221#[UsesClass(Source::class)]
222#[UsesClass(TargetProfile::class)]
223#[UsesClass(Budget::class)]
224#[UsesClass(QueryScope::class)]
225#[UsesClass(ReturnQuery::class)]
226#[UsesClass(ResultRef::class)]
227#[UsesClass(SourceRef::class)]
228#[UsesClass(Alternative::class)]
229#[UsesClass(Assessment::class)]
230#[UsesClass(Derivation::class)]
231#[UsesClass(DerivationResult::class)]
232#[UsesClass(Frontier::class)]
233#[UsesClass(QueryEncoding::class)]
234#[UsesClass(ValueGraph::class)]
235#[UsesClass(Statistics::class)]
236#[UsesClass(StorageSnapshot::class)]
237#[UsesClass(Parser::class)]
238#[UsesClass(File::class)]
239#[UsesClass(AggregateLowering::class)]
240#[UsesClass(AssignmentLowering::class)]
241#[UsesClass(CallLowering::class)]
242#[UsesClass(Lowerer::class)]
243#[UsesClass(ConditionalLowering::class)]
244#[UsesClass(DestructuringLowering::class)]
245#[UsesClass(ExceptionLowering::class)]
246#[UsesClass(ExpressionLowering::class)]
247#[UsesClass(InstructionEmitter::class)]
248#[UsesClass(LoweringScope::class)]
249#[UsesClass(StatementLowering::class)]
250#[UsesClass(CallableSource::class)]
251#[UsesClass(Indexer::class)]
252#[UsesClass(Index::class)]
253#[UsesClass(Composition::class)]
254#[UsesClass(LineMap::class)]
255#[UsesClass(MagicContext::class)]
256#[UsesClass(AssignmentPatterns::class)]
257#[UsesClass(ClassScope::class)]
258#[UsesClass(TargetSyntax::class)]
259#[UsesClass(Arithmetic::class)]
260#[UsesClass(Arrays::class)]
261#[UsesClass(Identity::class)]
262#[UsesClass(Increment::class)]
263#[UsesClass(Operations::class)]
264#[UsesClass(Term::class)]
265#[Small]
266final class MemoryStepTest extends TestCase
267{
268 public function testEvaluatePreservesTheSemanticContract(): void
269 {
270 $source = '<?php function replace(string &$x) {$x="changed";} function target() {$x="original";replace($x);return $x;}';
271 $symbol = 'target';
272 $input = new Sources([new Source('fixture.php', $source)]);
273 $configuration = new DeriverConfig();
274 $contributions = new Contributions();
275 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
276 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
277 $contributions1 = $providers->contributions;
278 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
279 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
280 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
281 $snapshotId = $identity->id;
282 $input1 = $providers->input;
283 $parser = new Parser(new ParserConfig($target->version));
284 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
285 $program = new Index($snapshotId, $trees);
286 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
287 self::assertSame('changed', $result->normalOutcomes[0]->values['return']->native());
288 self::assertSame([], $result->frontiers);
289 self::assertSame([], $result->exceptionalOutcomes);
290 }
291 public function testAddressKeepsTheActualVariableBinding(): void
292 {
293 $state = new State();
294 $instruction = new BoundInstruction('local', 'local', new SourceRef('test', 'fixture.php', 0, 1), 'result', name: 'x');
295 $source = '<?php function target(){return 1;}';
296 $budget = new Budget();
297 $configuration = new DeriverConfig();
298 $contributions = new Contributions();
299 $input = new Sources([new Source('fixture.php', $source)]);
300 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
301 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
302 $contributions1 = $providers->contributions;
303 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
304 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
305 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
306 $snapshotId = $identity->id;
307 $input1 = $providers->input;
308 $parser = new Parser(new ParserConfig($target->version));
309 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
310 $program1 = new Index($snapshotId, $trees);
311 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
312 $snapshotId1 = 'test';
313 $input2 = new Sources([new Source('fixture.php', $source)]);
314 $profile = $execution->configuration->target;
315 $parser1 = new Parser(new ParserConfig($profile->version));
316 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
317 $program = new Index($snapshotId1, $trees1);
318 $location = (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->address($instruction, $state);
319 self::assertSame($state->local('x')->root, $location->root);
320 self::assertSame('x', $location->local);
321 }
322 public function testUninitializedRetainsAWarningForAnUndefinedVariable(): void
323 {
324 $source = '<?php function target(){return 1;}';
325 $budget = new Budget();
326 $configuration = new DeriverConfig();
327 $contributions = new Contributions();
328 $input = new Sources([new Source('fixture.php', $source)]);
329 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
330 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
331 $contributions1 = $providers->contributions;
332 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
333 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
334 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
335 $snapshotId = $identity->id;
336 $input1 = $providers->input;
337 $parser = new Parser(new ParserConfig($target->version));
338 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
339 $program1 = new Index($snapshotId, $trees);
340 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
341 $snapshotId1 = 'test';
342 $input2 = new Sources([new Source('fixture.php', $source)]);
343 $profile = $execution->configuration->target;
344 $parser1 = new Parser(new ParserConfig($profile->version));
345 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
346 $program = new Index($snapshotId1, $trees1);
347 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
348 $state = new State();
349 $instruction = new BoundInstruction('read', 'read', new SourceRef('test', 'fixture.php', 0, 1), 'result');
350 $value = (new MemoryStep($context))->uninitialized($instruction, $state->local('x'), $state);
351 self::assertNull($value->native());
352 self::assertSame('PHP_WARNING', array_values($context->frontiers)[0]->code);
353 }
354 public function testBindingUnsetsOnlyOneLocalAlias(): void
355 {
356 $source = '<?php function target(){$x=1;$y=&$x;unset($x);$y=2;return [isset($x),$y];}';
357 $symbol = 'target';
358 $input = new Sources([new Source('fixture.php', $source)]);
359 $configuration = new DeriverConfig();
360 $contributions = new Contributions();
361 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
362 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
363 $contributions1 = $providers->contributions;
364 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
365 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
366 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
367 $snapshotId = $identity->id;
368 $input1 = $providers->input;
369 $parser = new Parser(new ParserConfig($target->version));
370 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
371 $program = new Index($snapshotId, $trees);
372 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
373 self::assertSame([], $result->frontiers);
374 self::assertSame([], $result->exceptionalOutcomes);
375 self::assertCount(1, $result->normalOutcomes);
376 self::assertSame([false, 2], $result->normalOutcomes[0]->values['return']->native());
377 }
378 public function testIncrementedRetainsNoEffectValuesAndRecordsTargetWarnings(): void
379 {
380 $source = '<?php function target(){return 1;}';
381 $budget = new Budget();
382 $configuration = new DeriverConfig();
383 $contributions = new Contributions();
384 $input = new Sources([new Source('fixture.php', $source)]);
385 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
386 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
387 $contributions1 = $providers->contributions;
388 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
389 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
390 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
391 $snapshotId = $identity->id;
392 $input1 = $providers->input;
393 $parser = new Parser(new ParserConfig($target->version));
394 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
395 $program1 = new Index($snapshotId, $trees);
396 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
397 $snapshotId1 = 'test';
398 $input2 = new Sources([new Source('fixture.php', $source)]);
399 $profile = $execution->configuration->target;
400 $parser1 = new Parser(new ParserConfig($profile->version));
401 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
402 $program = new Index($snapshotId1, $trees1);
403 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
404 $instruction = new BoundInstruction('increment', 'increment', new SourceRef('s', 'x.php', 0, 1), attributes: ['delta' => 1]);
405 $value = (new MemoryStep($context))->incremented(Term::constant(true), $instruction);
406 self::assertTrue($value->literal);
407 self::assertSame('PHP_WARNING', array_values($context->frontiers)[0]->code);
408 }
409 public function testPrepareAddressRejectsAnOccupiedMaximumAppendIndexWithoutOverwritingIt(): void
410 {
411 $source = '<?php function target(){$a=[9223372036854775807=>1];try{$a[]=2;}catch(Error $e){return $a;}return 999;}';
412 $symbol = 'target';
413 $input = new Sources([new Source('fixture.php', $source)]);
414 $configuration = new DeriverConfig();
415 $contributions = new Contributions();
416 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
417 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
418 $contributions1 = $providers->contributions;
419 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
420 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
421 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
422 $snapshotId = $identity->id;
423 $input1 = $providers->input;
424 $parser = new Parser(new ParserConfig($target->version));
425 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
426 $program = new Index($snapshotId, $trees);
427 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
428 self::assertSame([PHP_INT_MAX => 1], $result->normalOutcomes[0]->values['return']->native());
429 self::assertSame([], $result->frontiers);
430 }
431 public function testEvaluateBindsScriptLocalsToSharedGlobalStorage(): void
432 {
433 $source = new SourceRef('test', 'fixture.php', 0, 1);
434 $state = new State();
435 $state->memory->write($state->local('x'), Term::constant(3));
436 $instruction = new BoundInstruction('local', 'local', $source, 'address', name: 'x');
437 $source1 = '<?php function target(){return 1;}';
438 $budget = new Budget();
439 $configuration = new DeriverConfig();
440 $contributions = new Contributions();
441 $input = new Sources([new Source('fixture.php', $source1)]);
442 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
443 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
444 $contributions1 = $providers->contributions;
445 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
446 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
447 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
448 $snapshotId = $identity->id;
449 $input1 = $providers->input;
450 $parser = new Parser(new ParserConfig($target->version));
451 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
452 $program1 = new Index($snapshotId, $trees);
453 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
454 $snapshotId1 = 'test';
455 $input2 = new Sources([new Source('fixture.php', $source1)]);
456 $profile = $execution->configuration->target;
457 $parser1 = new Parser(new ParserConfig($profile->version));
458 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
459 $program = new Index($snapshotId1, $trees1);
460 $value = (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->evaluate(new CallableGraph('script:fixture.php', [], [], $source), $instruction, $state);
461 self::assertSame(TermKind::Location, $value->kind);
462 self::assertSame('global:x', $value->literal);
463 self::assertSame('global:x', $state->locals['x']->root);
464 self::assertSame(3, $state->memory->cells['global:x']->native());
465 self::assertSame('global:x', $state->addresses['address']->root);
466 }
467 public function testEvaluateWritesAndReadsTheResolvedAddress(): void
468 {
469 $source = new SourceRef('test', 'fixture.php', 0, 1);
470 $body = new CallableGraph('target', [], [], $source);
471 $state = new State();
472 $state->addresses['address'] = $state->memory->allocate(Term::constant(1));
473 $value = Term::constant(7);
474 $state->registers['value'] = $value;
475 $source1 = '<?php function target(){return 1;}';
476 $budget = new Budget();
477 $configuration = new DeriverConfig();
478 $contributions = new Contributions();
479 $input = new Sources([new Source('fixture.php', $source1)]);
480 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
481 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
482 $contributions1 = $providers->contributions;
483 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
484 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
485 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
486 $snapshotId = $identity->id;
487 $input1 = $providers->input;
488 $parser = new Parser(new ParserConfig($target->version));
489 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
490 $program1 = new Index($snapshotId, $trees);
491 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
492 $snapshotId1 = 'test';
493 $input2 = new Sources([new Source('fixture.php', $source1)]);
494 $profile = $execution->configuration->target;
495 $parser1 = new Parser(new ParserConfig($profile->version));
496 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
497 $program = new Index($snapshotId1, $trees1);
498 $step = new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
499 self::assertSame($value, $step->evaluate($body, new BoundInstruction('write', 'write', $source, 'result', ['address', 'value']), $state));
500 self::assertSame($value, $step->evaluate($body, new BoundInstruction('read', 'read', $source, 'result', ['address']), $state));
501 self::assertSame($value, $state->memory->read($state->addresses['address']));
502 }
503 public function testEvaluateSilentReadsPreserveAbsenceWithoutWarnings(): void
504 {
505 $source1 = '<?php function target(){return 1;}';
506 $budget = new Budget();
507 $configuration = new DeriverConfig();
508 $contributions = new Contributions();
509 $input = new Sources([new Source('fixture.php', $source1)]);
510 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
511 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
512 $contributions1 = $providers->contributions;
513 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
514 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
515 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
516 $snapshotId = $identity->id;
517 $input1 = $providers->input;
518 $parser = new Parser(new ParserConfig($target->version));
519 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
520 $program1 = new Index($snapshotId, $trees);
521 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
522 $snapshotId1 = 'test';
523 $input2 = new Sources([new Source('fixture.php', $source1)]);
524 $profile = $execution->configuration->target;
525 $parser1 = new Parser(new ParserConfig($profile->version));
526 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
527 $program = new Index($snapshotId1, $trees1);
528 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
529 $source = new SourceRef('test', 'fixture.php', 0, 1);
530 $body = new CallableGraph('target', [], [], $source);
531 $state = new State();
532 $state->addresses['address'] = $state->local('x');
533 $step = new MemoryStep($context);
534 self::assertSame(TermKind::Uninitialized, $step->evaluate($body, new BoundInstruction('read', 'read-silent', $source, 'result', ['address']), $state)->kind);
535 $beforeCount = count($context->frontiers);
536 self::assertNull($step->evaluate($body, new BoundInstruction('read', 'read', $source, 'result', ['address']), $state)->native());
537 self::assertSame(['PHP_WARNING'], array_column(array_values($context->frontiers), 'code'));
538 self::assertSame(0, $beforeCount);
539 }
540 public function testEvaluateReferencesAndAliasesShareWrites(): void
541 {
542 $source = new SourceRef('test', 'fixture.php', 0, 1);
543 $body = new CallableGraph('target', [], [], $source);
544 $state = new State();
545 $state->addresses['x'] = $state->local('x');
546 $state->addresses['y'] = $state->local('y');
547 $state->memory->write($state->addresses['y'], Term::constant(4));
548 $source1 = '<?php function target(){return 1;}';
549 $budget = new Budget();
550 $configuration = new DeriverConfig();
551 $contributions = new Contributions();
552 $input = new Sources([new Source('fixture.php', $source1)]);
553 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
554 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
555 $contributions1 = $providers->contributions;
556 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
557 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
558 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
559 $snapshotId = $identity->id;
560 $input1 = $providers->input;
561 $parser = new Parser(new ParserConfig($target->version));
562 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
563 $program1 = new Index($snapshotId, $trees);
564 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
565 $snapshotId1 = 'test';
566 $input2 = new Sources([new Source('fixture.php', $source1)]);
567 $profile = $execution->configuration->target;
568 $parser1 = new Parser(new ParserConfig($profile->version));
569 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
570 $program = new Index($snapshotId1, $trees1);
571 $step = new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
572 self::assertSame(4, $step->evaluate($body, new BoundInstruction('alias', 'alias', $source, 'result', ['x', 'y']), $state)->native());
573 $reference = $step->evaluate($body, new BoundInstruction('reference', 'reference', $source, 'result', ['y']), $state);
574 self::assertSame(TermKind::Cell, $reference->kind);
575 self::assertSame($state->memory->reference($state->local('x')), $reference->literal);
576 }
577 #[DataProvider('providerIncrements')]
578 public function testEvaluateIncrementKeepsPreAndPostValuesSeparate(int $delta, bool $post, int $expected, int $after): void
579 {
580 $source = new SourceRef('test', 'fixture.php', 0, 1);
581 $state = new State();
582 $state->addresses['x'] = $state->memory->allocate(Term::constant(3));
583 $instruction = new BoundInstruction('increment', 'increment', $source, 'result', ['x'], attributes: ['delta' => $delta, 'post' => $post]);
584 $source1 = '<?php function target(){return 1;}';
585 $budget = new Budget();
586 $configuration = new DeriverConfig();
587 $contributions = new Contributions();
588 $input = new Sources([new Source('fixture.php', $source1)]);
589 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
590 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
591 $contributions1 = $providers->contributions;
592 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
593 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
594 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
595 $snapshotId = $identity->id;
596 $input1 = $providers->input;
597 $parser = new Parser(new ParserConfig($target->version));
598 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
599 $program1 = new Index($snapshotId, $trees);
600 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
601 $snapshotId1 = 'test';
602 $input2 = new Sources([new Source('fixture.php', $source1)]);
603 $profile = $execution->configuration->target;
604 $parser1 = new Parser(new ParserConfig($profile->version));
605 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
606 $program = new Index($snapshotId1, $trees1);
607 $result = (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->evaluate(new CallableGraph('target', [], [], $source), $instruction, $state);
608 self::assertSame($expected, $result->native());
609 self::assertSame($after, $state->memory->read($state->addresses['x'])->native());
610 }
611 /**
612 * @return array<string,array{int,bool,int,int}>
613 */
614 public static function providerIncrements(): array
615 {
616 return ['pre increment' => [1, false, 4, 4], 'post increment' => [1, true, 3, 4], 'pre decrement' => [-1, false, 2, 2], 'post decrement' => [-1, true, 3, 2]];
617 }
618 public function testEvaluateFailedIncrementCannotWriteTheThrowableIntoStorage(): void
619 {
620 $source = new SourceRef('test', 'fixture.php', 0, 1);
621 $state = new State();
622 $value = Term::array([]);
623 $state->addresses['x'] = $state->memory->allocate($value);
624 $instruction = new BoundInstruction('increment', 'increment', $source, 'result', ['x']);
625 $source1 = '<?php function target(){return 1;}';
626 $budget = new Budget();
627 $configuration = new DeriverConfig();
628 $contributions = new Contributions();
629 $input = new Sources([new Source('fixture.php', $source1)]);
630 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
631 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
632 $contributions1 = $providers->contributions;
633 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
634 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
635 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
636 $snapshotId = $identity->id;
637 $input1 = $providers->input;
638 $parser = new Parser(new ParserConfig($target->version));
639 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
640 $program1 = new Index($snapshotId, $trees);
641 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
642 $snapshotId1 = 'test';
643 $input2 = new Sources([new Source('fixture.php', $source1)]);
644 $profile = $execution->configuration->target;
645 $parser1 = new Parser(new ParserConfig($profile->version));
646 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
647 $program = new Index($snapshotId1, $trees1);
648 $result = (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->evaluate(new CallableGraph('target', [], [], $source), $instruction, $state);
649 self::assertSame('TypeError', $result->literal);
650 self::assertSame($value, $state->memory->read($state->addresses['x']));
651 }
652 public function testAddressResolvesReturnedCellsAndRejectsValues(): void
653 {
654 $source = new SourceRef('test', 'fixture.php', 0, 1);
655 $source1 = '<?php function target(){return 1;}';
656 $budget = new Budget();
657 $configuration = new DeriverConfig();
658 $contributions = new Contributions();
659 $input = new Sources([new Source('fixture.php', $source1)]);
660 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
661 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
662 $contributions1 = $providers->contributions;
663 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
664 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
665 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
666 $snapshotId = $identity->id;
667 $input1 = $providers->input;
668 $parser = new Parser(new ParserConfig($target->version));
669 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
670 $program1 = new Index($snapshotId, $trees);
671 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
672 $snapshotId1 = 'test';
673 $input2 = new Sources([new Source('fixture.php', $source1)]);
674 $profile = $execution->configuration->target;
675 $parser1 = new Parser(new ParserConfig($profile->version));
676 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
677 $program = new Index($snapshotId1, $trees1);
678 $step = new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
679 $state = new State();
680 $state->registers = ['cell' => new Term(TermKind::Cell, 'shared'), 'value' => Term::constant(1)];
681 self::assertSame('shared', $step->address(new BoundInstruction('address', 'returned-address', $source, 'result', ['cell']), $state)->root);
682 self::assertTrue($step->address(new BoundInstruction('address', 'returned-address', $source, 'result', ['value']), $state)->unknown);
683 self::assertTrue($step->address(new BoundInstruction('address', 'returned-address', $source), $state)->unknown);
684 }
685 public function testPrepareAddressRetainsOriginalOffsetAndPropertyMetadata(): void
686 {
687 $state = new State();
688 $source = new SourceRef('test', 'fixture.php', 0, 1);
689 $parent = $state->memory->allocate(Term::fromNative(['field' => [1]]));
690 $state->addresses['base'] = new Location($parent->root, ['field']);
691 $key = Term::constant('0');
692 $state->registers['key'] = $key;
693 $property = new PropertySlot(new Term(TermKind::Object, 'box'), 'field', 'Box', null);
694 $state->properties['base'] = $property;
695 $instruction = new BoundInstruction('address', 'element-address', $source, 'result', ['base', 'key']);
696 $source1 = '<?php function target(){return 1;}';
697 $budget = new Budget();
698 $configuration = new DeriverConfig();
699 $contributions = new Contributions();
700 $input = new Sources([new Source('fixture.php', $source1)]);
701 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
702 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
703 $contributions1 = $providers->contributions;
704 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
705 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
706 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
707 $snapshotId = $identity->id;
708 $input1 = $providers->input;
709 $parser = new Parser(new ParserConfig($target->version));
710 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
711 $program1 = new Index($snapshotId, $trees);
712 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
713 $snapshotId1 = 'test';
714 $input2 = new Sources([new Source('fixture.php', $source1)]);
715 $profile = $execution->configuration->target;
716 $parser1 = new Parser(new ParserConfig($profile->version));
717 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
718 $program = new Index($snapshotId1, $trees1);
719 $step = new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
720 $value = $step->prepareAddress($instruction, $state);
721 self::assertSame(TermKind::Location, $value->kind);
722 self::assertSame($parent->root, $value->literal);
723 self::assertSame(['field', 0], $state->addresses['result']->path);
724 self::assertSame($property, $state->properties['result']);
725 self::assertSame($key, $state->offsets['result']->key);
726 self::assertSame('base', $state->offsets['result']->parent);
727 }
728 public function testAddressKeepsUnknownKeysAtTheParentLocation(): void
729 {
730 $state = new State();
731 $state->addresses['base'] = new Location('array', ['field']);
732 $state->registers['key'] = Term::parameter('key', 'string');
733 $instruction = new BoundInstruction('address', 'element-address', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['base', 'key']);
734 $source = '<?php function target(){return 1;}';
735 $budget = new Budget();
736 $configuration = new DeriverConfig();
737 $contributions = new Contributions();
738 $input = new Sources([new Source('fixture.php', $source)]);
739 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
740 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
741 $contributions1 = $providers->contributions;
742 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
743 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
744 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
745 $snapshotId = $identity->id;
746 $input1 = $providers->input;
747 $parser = new Parser(new ParserConfig($target->version));
748 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
749 $program1 = new Index($snapshotId, $trees);
750 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
751 $snapshotId1 = 'test';
752 $input2 = new Sources([new Source('fixture.php', $source)]);
753 $profile = $execution->configuration->target;
754 $parser1 = new Parser(new ParserConfig($profile->version));
755 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
756 $program = new Index($snapshotId1, $trees1);
757 $address = (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->address($instruction, $state);
758 self::assertSame('array', $address->root);
759 self::assertSame(['field'], $address->path);
760 self::assertTrue($address->unknown);
761 }
762 public function testPrepareAddressAppendsUsingTheStoredCounter(): void
763 {
764 $state = new State();
765 $state->addresses['base'] = $state->memory->allocate(Term::fromNative([4 => 'a']));
766 $instruction = new BoundInstruction('address', 'element-address', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['base', '']);
767 $source = '<?php function target(){return 1;}';
768 $budget = new Budget();
769 $configuration = new DeriverConfig();
770 $contributions = new Contributions();
771 $input = new Sources([new Source('fixture.php', $source)]);
772 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
773 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
774 $contributions1 = $providers->contributions;
775 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
776 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
777 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
778 $snapshotId = $identity->id;
779 $input1 = $providers->input;
780 $parser = new Parser(new ParserConfig($target->version));
781 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
782 $program1 = new Index($snapshotId, $trees);
783 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
784 $snapshotId1 = 'test';
785 $input2 = new Sources([new Source('fixture.php', $source)]);
786 $profile = $execution->configuration->target;
787 $parser1 = new Parser(new ParserConfig($profile->version));
788 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
789 $program = new Index($snapshotId1, $trees1);
790 (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->prepareAddress($instruction, $state);
791 self::assertSame([5], $state->addresses['result']->path);
792 self::assertNull($state->offsets['result']->key);
793 }
794 public function testUninitializedTypedPropertiesAndThisProduceErrorsWithoutWarnings(): void
795 {
796 $source = '<?php function target(){return 1;}';
797 $budget = new Budget();
798 $configuration = new DeriverConfig();
799 $contributions = new Contributions();
800 $input = new Sources([new Source('fixture.php', $source)]);
801 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
802 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
803 $contributions1 = $providers->contributions;
804 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
805 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
806 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
807 $snapshotId = $identity->id;
808 $input1 = $providers->input;
809 $parser = new Parser(new ParserConfig($target->version));
810 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
811 $program1 = new Index($snapshotId, $trees);
812 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
813 $snapshotId1 = 'test';
814 $input2 = new Sources([new Source('fixture.php', $source)]);
815 $profile = $execution->configuration->target;
816 $parser1 = new Parser(new ParserConfig($profile->version));
817 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
818 $program = new Index($snapshotId1, $trees1);
819 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
820 $state = new State();
821 $state->memory->cells['typed'] = new Term(TermKind::Uninitialized, attributes: ['type' => 'int']);
822 $instruction = new BoundInstruction('read', 'read', new SourceRef('test', 'fixture.php', 0, 1));
823 $step = new MemoryStep($context);
824 self::assertSame('Error', $step->uninitialized($instruction, new Location('typed'), $state)->literal);
825 self::assertSame('Error', $step->uninitialized($instruction, new Location('missing', local: 'this'), $state)->literal);
826 self::assertSame([], $context->frontiers);
827 }
828 public function testBindingUsesExistingGlobalAndStaticStorageWithoutReinitializing(): void
829 {
830 $source = new SourceRef('test', 'fixture.php', 0, 1);
831 $body = new CallableGraph('target', [], [], $source);
832 $state = new State();
833 $state->memory->cells = ['global:x' => Term::constant(7), 'static:target:y' => Term::constant(8)];
834 $state->registers['initializer'] = Term::constant(99);
835 $source1 = '<?php function target(){return 1;}';
836 $budget = new Budget();
837 $configuration = new DeriverConfig();
838 $contributions = new Contributions();
839 $input = new Sources([new Source('fixture.php', $source1)]);
840 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
841 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
842 $contributions1 = $providers->contributions;
843 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
844 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
845 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
846 $snapshotId = $identity->id;
847 $input1 = $providers->input;
848 $parser = new Parser(new ParserConfig($target->version));
849 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
850 $program1 = new Index($snapshotId, $trees);
851 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
852 $snapshotId1 = 'test';
853 $input2 = new Sources([new Source('fixture.php', $source1)]);
854 $profile = $execution->configuration->target;
855 $parser1 = new Parser(new ParserConfig($profile->version));
856 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
857 $program = new Index($snapshotId1, $trees1);
858 $step = new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
859 self::assertNull($step->binding($body, new BoundInstruction('global', 'global', $source), $state, new Location('local-x', local: 'x'))->native());
860 self::assertNull($step->binding($body, new BoundInstruction('static', 'static-local', $source, operands: ['address', 'initializer']), $state, new Location('local-y', local: 'y'))->native());
861 self::assertSame(7, $state->memory->read($state->local('x'))->native());
862 self::assertSame(8, $state->memory->read($state->local('y'))->native());
863 self::assertSame('global:x', $state->local('x')->root);
864 self::assertSame('static:target:y', $state->local('y')->root);
865 }
866 public function testBindingInitializesUnseenStaticsAndKeepsUnknownSharedState(): void
867 {
868 $source = new SourceRef('test', 'fixture.php', 0, 1);
869 $body = new CallableGraph('target', [], [], $source);
870 $state = new State();
871 $location = $state->local('x');
872 $source1 = '<?php function target(){return 1;}';
873 $budget = new Budget();
874 $configuration = new DeriverConfig();
875 $contributions = new Contributions();
876 $input = new Sources([new Source('fixture.php', $source1)]);
877 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
878 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
879 $contributions1 = $providers->contributions;
880 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
881 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
882 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
883 $snapshotId = $identity->id;
884 $input1 = $providers->input;
885 $parser = new Parser(new ParserConfig($target->version));
886 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
887 $program1 = new Index($snapshotId, $trees);
888 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
889 $snapshotId1 = 'test';
890 $input2 = new Sources([new Source('fixture.php', $source1)]);
891 $profile = $execution->configuration->target;
892 $parser1 = new Parser(new ParserConfig($profile->version));
893 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
894 $program = new Index($snapshotId1, $trees1);
895 $step = new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
896 $check = new BoundInstruction('check', 'static-initialized', $source);
897 self::assertFalse($step->binding($body, $check, $state, $location)->native());
898 $state->registers['initializer'] = Term::constant(4);
899 $step->binding($body, new BoundInstruction('static', 'static-local', $source, operands: ['address', 'initializer']), $state, $location);
900 self::assertTrue($step->binding($body, $check, $state, $location)->native());
901 self::assertSame(4, $state->memory->read($state->local('x'))->native());
902 $state->memory->unknownShared = 'UNKNOWN_CALL';
903 self::assertTrue($step->binding($body, $check, $state, new Location('unseen', local: 'y'))->native());
904 self::assertSame('UNKNOWN_CALL', $state->memory->read($state->local('y'))->literal);
905 }
906 public function testBindingCreatesExternalGlobalsAndRemovesOnlySelectedArrayElements(): void
907 {
908 $source = new SourceRef('test', 'fixture.php', 0, 1);
909 $body = new CallableGraph('target', [], [], $source);
910 $state = new State();
911 $source1 = '<?php function target(){return 1;}';
912 $budget = new Budget();
913 $configuration = new DeriverConfig();
914 $contributions = new Contributions();
915 $input = new Sources([new Source('fixture.php', $source1)]);
916 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
917 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
918 $contributions1 = $providers->contributions;
919 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
920 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
921 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
922 $snapshotId = $identity->id;
923 $input1 = $providers->input;
924 $parser = new Parser(new ParserConfig($target->version));
925 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
926 $program1 = new Index($snapshotId, $trees);
927 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
928 $snapshotId1 = 'test';
929 $input2 = new Sources([new Source('fixture.php', $source1)]);
930 $profile = $execution->configuration->target;
931 $parser1 = new Parser(new ParserConfig($profile->version));
932 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
933 $program = new Index($snapshotId1, $trees1);
934 $step = new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators));
935 $step->binding($body, new BoundInstruction('global', 'global', $source), $state, new Location('unseen', local: 'x'));
936 self::assertSame(TermKind::External, $state->memory->read($state->local('x'))->kind);
937 self::assertSame('global:x', $state->memory->read($state->local('x'))->literal);
938 $array = $state->memory->allocate(Term::fromNative(['a' => 1, 'b' => 2]));
939 $step->binding($body, new BoundInstruction('unset', 'unset', $source), $state, new Location($array->root, ['a'], local: 'array'));
940 self::assertSame(['b' => 2], $state->memory->read($array)->native());
941 }
942 #[DataProvider('providerTemporaryReferenceDiagnostics')]
943 public function testReturnedAddressAnchorsTemporaryValuesAndReportsOnlyRequiredReferenceNotices(bool $diagnostic): void
944 {
945 $source1 = '<?php function target(){return 1;}';
946 $budget = new Budget();
947 $configuration = new DeriverConfig();
948 $contributions = new Contributions();
949 $input = new Sources([new Source('fixture.php', $source1)]);
950 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
951 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
952 $contributions1 = $providers->contributions;
953 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
954 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
955 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
956 $snapshotId = $identity->id;
957 $input1 = $providers->input;
958 $parser = new Parser(new ParserConfig($target->version));
959 $trees = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input1, false));
960 $program1 = new Index($snapshotId, $trees);
961 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
962 $snapshotId1 = 'test';
963 $input2 = new Sources([new Source('fixture.php', $source1)]);
964 $profile = $execution->configuration->target;
965 $parser1 = new Parser(new ParserConfig($profile->version));
966 $trees1 = array_map(static fn (Source $source3): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source3->path), $source3->contents, $source3->declarationsOnly))), iterator_to_array($input2, false));
967 $program = new Index($snapshotId1, $trees1);
968 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
969 $state = new State();
970 $value = Term::fromNative([7]);
971 $state->registers['returned'] = $value;
972 $source = new SourceRef('test', 'a.php', 1, 9);
973 $instruction = new BoundInstruction('i', 'returned-address', $source, 'address', ['returned'], attributes: ['temporary-reference' => true, 'temporary-warning' => $diagnostic]);
974 $address = (new MemoryStep($context))->returnedAddress($instruction, $state);
975 self::assertFalse($address->unknown);
976 self::assertSame($value, $state->memory->read($address));
977 self::assertSame($diagnostic ? ['PHP_WARNING'] : [], array_column($context->frontiers, 'code'));
978 self::assertSame($diagnostic ? [$source] : [], array_column($context->frontiers, 'at'));
979 $state->memory->write($address, Term::constant(8));
980 self::assertSame($value, $state->registers['returned']);
981 }
982 /**
983 * @return iterable<string,array{bool}>
984 */
985 public static function providerTemporaryReferenceDiagnostics(): iterable
986 {
987 yield 'assignment from ordinary return' => [true];
988 yield 'foreach temporary' => [false];
989 }
990 public function testReturnedAddressRetainsAnExistingReturnedCellWithoutAReferenceNotice(): void
991 {
992 $source = '<?php function target(){return 1;}';
993 $budget = new Budget();
994 $configuration = new DeriverConfig();
995 $contributions = new Contributions();
996 $input = new Sources([new Source('fixture.php', $source)]);
997 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
998 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
999 $contributions1 = $providers->contributions;
1000 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
1001 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1002 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1003 $snapshotId = $identity->id;
1004 $input1 = $providers->input;
1005 $parser = new Parser(new ParserConfig($target->version));
1006 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1007 $program1 = new Index($snapshotId, $trees);
1008 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1009 $snapshotId1 = 'test';
1010 $input2 = new Sources([new Source('fixture.php', $source)]);
1011 $profile = $execution->configuration->target;
1012 $parser1 = new Parser(new ParserConfig($profile->version));
1013 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1014 $program = new Index($snapshotId1, $trees1);
1015 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1016 $state = new State();
1017 $original = $state->memory->allocate(Term::fromNative([7]));
1018 $state->registers['returned'] = new Term(TermKind::Cell, $original->root);
1019 $instruction = new BoundInstruction('i', 'returned-address', new SourceRef('test', 'a.php', 1, 9), 'address', ['returned'], attributes: ['temporary-reference' => true]);
1020 $address = (new MemoryStep($context))->returnedAddress($instruction, $state);
1021 self::assertSame($original->root, $address->root);
1022 self::assertFalse($address->unknown);
1023 self::assertSame([], $context->frontiers);
1024 $state->memory->write($address, Term::constant(8));
1025 self::assertSame(8, $state->memory->read($original)->literal);
1026 }
1027 public function testPrepareStorageResolvesAConcreteDynamicLocal(): void
1028 {
1029 $source = '<?php function target(){return 1;}';
1030 $budget = new Budget();
1031 $configuration = new DeriverConfig();
1032 $contributions = new Contributions();
1033 $input = new Sources([new Source('fixture.php', $source)]);
1034 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1035 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1036 $contributions1 = $providers->contributions;
1037 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
1038 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1039 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1040 $snapshotId = $identity->id;
1041 $input1 = $providers->input;
1042 $parser = new Parser(new ParserConfig($target->version));
1043 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1044 $program1 = new Index($snapshotId, $trees);
1045 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1046 $snapshotId1 = 'test';
1047 $input2 = new Sources([new Source('fixture.php', $source)]);
1048 $profile = $execution->configuration->target;
1049 $parser1 = new Parser(new ParserConfig($profile->version));
1050 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1051 $program = new Index($snapshotId1, $trees1);
1052 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1053 $body = $context->program->callable('target');
1054 self::assertNotNull($body);
1055 $state = new State();
1056 $state->registers['name'] = Term::constant('sql');
1057 (new MemoryStep($context))->prepareStorage($body, new BoundInstruction('i', 'dynamic-local', $body->source, 'address', ['name']), $state);
1058 self::assertSame('sql', $state->addresses['address']->local);
1059 }
1060 /**
1061 * @param Location $address Read storage
1062 * @param list<string> $expected Named variable
1063 */
1064 #[DataProvider('providerVariables')]
1065 public function testVariableNamesGlobalAndLocalStorageOnly(Location $address, array $expected): void
1066 {
1067 $source = '<?php function target(){return 1;}';
1068 $budget = new Budget();
1069 $configuration = new DeriverConfig();
1070 $contributions = new Contributions();
1071 $input = new Sources([new Source('fixture.php', $source)]);
1072 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1073 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1074 $contributions1 = $providers->contributions;
1075 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
1076 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1077 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1078 $snapshotId = $identity->id;
1079 $input1 = $providers->input;
1080 $parser = new Parser(new ParserConfig($target->version));
1081 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1082 $program1 = new Index($snapshotId, $trees);
1083 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1084 $snapshotId1 = 'test';
1085 $input2 = new Sources([new Source('fixture.php', $source)]);
1086 $profile = $execution->configuration->target;
1087 $parser1 = new Parser(new ParserConfig($profile->version));
1088 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1089 $program = new Index($snapshotId1, $trees1);
1090 self::assertSame($expected, (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->variable($address));
1091 }
1092 /**
1093 * @return iterable<string,array{Location,list<string>}>
1094 */
1095 public static function providerVariables(): iterable
1096 {
1097 yield 'local' => [new Location('cell:0', local: 'sql'), ['variable:sql']];
1098 yield 'global' => [new Location('global:sql', local: 'sql'), ['global:sql']];
1099 yield 'element' => [new Location('global:sql', ['k'], 'sql'), []];
1100 yield 'anonymous cell' => [new Location('cell:0'), []];
1101 yield 'unknown' => [new Location('global:sql', local: 'sql', unknown: true), []];
1102 }
1103 public function testUninitializedNamesTheReadVariable(): void
1104 {
1105 $source = '<?php function target(){return 1;}';
1106 $budget = new Budget();
1107 $configuration = new DeriverConfig();
1108 $contributions = new Contributions();
1109 $input = new Sources([new Source('fixture.php', $source)]);
1110 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1111 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1112 $contributions1 = $providers->contributions;
1113 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
1114 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1115 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1116 $snapshotId = $identity->id;
1117 $input1 = $providers->input;
1118 $parser = new Parser(new ParserConfig($target->version));
1119 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1120 $program1 = new Index($snapshotId, $trees);
1121 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1122 $snapshotId1 = 'test';
1123 $input2 = new Sources([new Source('fixture.php', $source)]);
1124 $profile = $execution->configuration->target;
1125 $parser1 = new Parser(new ParserConfig($profile->version));
1126 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1127 $program = new Index($snapshotId1, $trees1);
1128 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1129 $state = new State();
1130 $instruction = new BoundInstruction('read', 'read', new SourceRef('test', 'fixture.php', 0, 1), 'result');
1131 (new MemoryStep($context))->uninitialized($instruction, $state->local('x'), $state);
1132 self::assertSame(['variable:x'], array_values($context->frontiers)[0]->knownDependencies);
1133 }
1134 public function testReadReportsAnUnconfiguredGlobal(): void
1135 {
1136 $source = '<?php function target(){return 1;}';
1137 $budget = new Budget();
1138 $configuration = new DeriverConfig();
1139 $contributions = new Contributions();
1140 $input = new Sources([new Source('fixture.php', $source)]);
1141 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1142 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1143 $contributions1 = $providers->contributions;
1144 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
1145 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1146 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1147 $snapshotId = $identity->id;
1148 $input1 = $providers->input;
1149 $parser = new Parser(new ParserConfig($target->version));
1150 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1151 $program1 = new Index($snapshotId, $trees);
1152 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1153 $snapshotId1 = 'test';
1154 $input2 = new Sources([new Source('fixture.php', $source)]);
1155 $profile = $execution->configuration->target;
1156 $parser1 = new Parser(new ParserConfig($profile->version));
1157 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1158 $program = new Index($snapshotId1, $trees1);
1159 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1160 $state = new State();
1161 $state->memory->cells['global:wpdb'] = new Term(TermKind::External, 'global:wpdb', attributes: ['type' => 'mixed']);
1162 $state->locals['wpdb'] = new Location('global:wpdb');
1163 $state->addresses['a'] = $state->local('wpdb');
1164 $value = (new MemoryStep($context))->evaluate(new CallableGraph('target', [], [], new SourceRef('test', 'fixture.php', 0, 1)), new BoundInstruction('read', 'read', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['a']), $state);
1165 self::assertSame('global:wpdb', $value->literal);
1166 $frontier = array_values($context->frontiers)[0];
1167 self::assertSame(['EXTERNAL_INPUT', 'global-read', ['global:wpdb']], [$frontier->code, $frontier->operation, $frontier->knownDependencies]);
1168 }
1169 #[DataProvider('providerUnsetScopes')]
1170 public function testUnsetKeepsAVariableUndefinedAcrossUnknownSymbolTables(string $symbol, ?string $unknown, ?string $expected): void
1171 {
1172 $state = new State();
1173 $state->unknownLocals = $unknown;
1174 $state->memory->cells['global:x'] = Term::constant(1);
1175 $state->locals['x'] = new Location('global:x');
1176 $source = '<?php function target(){return 1;}';
1177 $budget = new Budget();
1178 $configuration = new DeriverConfig();
1179 $contributions = new Contributions();
1180 $input = new Sources([new Source('fixture.php', $source)]);
1181 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1182 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1183 $contributions1 = $providers->contributions;
1184 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
1185 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1186 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1187 $snapshotId = $identity->id;
1188 $input1 = $providers->input;
1189 $parser = new Parser(new ParserConfig($target->version));
1190 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1191 $program1 = new Index($snapshotId, $trees);
1192 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1193 $snapshotId1 = 'test';
1194 $input2 = new Sources([new Source('fixture.php', $source)]);
1195 $profile = $execution->configuration->target;
1196 $parser1 = new Parser(new ParserConfig($profile->version));
1197 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1198 $program = new Index($snapshotId1, $trees1);
1199 (new MemoryStep(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators)))->unset(new CallableGraph($symbol, [], [], new SourceRef('test', 'fixture.php', 0, 1)), $state, $state->local('x'));
1200 self::assertSame($expected, isset($state->locals['x']) ? $state->memory->read($state->locals['x'])->kind->value : null);
1201 self::assertSame(str_starts_with($symbol, 'script:') ? 'uninitialized' : 'constant', $state->memory->cells['global:x']->kind->value);
1202 }
1203 /**
1204 * @return iterable<string,array{string,string|null,string|null}>
1205 */
1206 public static function providerUnsetScopes(): iterable
1207 {
1208 yield 'function' => ['target', null, null];
1209 yield 'function after boundary' => ['target', 'INCLUDE_SEMANTICS_UNSUPPORTED', 'uninitialized'];
1210 yield 'script' => ['script:fixture.php', null, null];
1211 }
1212 public function testUnknownSharedMakesALazilyCreatedGlobalUnknown(): void
1213 {
1214 $source = '<?php function target(){return 1;}';
1215 $budget = new Budget();
1216 $configuration = new DeriverConfig();
1217 $contributions = new Contributions();
1218 $input = new Sources([new Source('fixture.php', $source)]);
1219 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1220 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1221 $contributions1 = $providers->contributions;
1222 $evaluators = new Evaluators(new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision), new Registrations($contributions1->evaluators, $contributions1->providers, $contributions1->domains, $contributions1->intrinsics, $contributions1->stateSlots), $configuration->standardEvaluators);
1223 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1224 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1225 $snapshotId = $identity->id;
1226 $input1 = $providers->input;
1227 $parser = new Parser(new ParserConfig($target->version));
1228 $trees = array_map(static fn (Source $source1): File => (new Resolver())->resolve($parser->parse(new Source(SourcesPath::normalize($source1->path), $source1->contents, $source1->declarationsOnly))), iterator_to_array($input1, false));
1229 $program1 = new Index($snapshotId, $trees);
1230 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1231 $snapshotId1 = 'test';
1232 $input2 = new Sources([new Source('fixture.php', $source)]);
1233 $profile = $execution->configuration->target;
1234 $parser1 = new Parser(new ParserConfig($profile->version));
1235 $trees1 = array_map(static fn (Source $source2): File => (new Resolver())->resolve($parser1->parse(new Source(SourcesPath::normalize($source2->path), $source2->contents, $source2->declarationsOnly))), iterator_to_array($input2, false));
1236 $program = new Index($snapshotId1, $trees1);
1237 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1238 $state = new State();
1239 $step = new MemoryStep($context);
1240 self::assertNull($step->unknownShared(new State()));
1241 $state->memory->unknownShared = 'MISSING_CALL_MODEL';
1242 $value = $step->unknownShared($state);
1243 self::assertNotNull($value);
1244 self::assertSame(['opaque', 'MISSING_CALL_MODEL', true], [$value->kind->value, $value->literal, $value->attributes['maybeUninitialized'] ?? null]);
1245 $state->addresses['g'] = new Location('global:g', local: 'g');
1246 $step->binding(new CallableGraph('target', [], [], new SourceRef('test', 'fixture.php', 0, 1)), new BoundInstruction('g', 'global', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['g']), $state, $state->addresses['g']);
1247 self::assertSame(TermKind::Opaque, $state->memory->cells['global:g']->kind);
1248 }
1249}
1250