packages/deriver/tests/Unit/Deriver/Evaluator/Execution/InstructionTransferTest.php
1<?php
2
3declare (strict_types=1);
4
5namespace Tests\Unit\Deriver\Evaluator\Execution;
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\Builtin\TypePredicates;
14use Deriver\Deriver\Evaluator\Call\BoundInstruction;
15use Deriver\Deriver\Evaluator\Call\Evaluators;
16use Deriver\Deriver\Evaluator\Call\Extensions;
17use Deriver\Deriver\Evaluator\Call\Provider\DispatchDecision;
18use Deriver\Deriver\Evaluator\Call\Registrations;
19use Deriver\Deriver\Evaluator\Call\State\StateSlot;
20use Deriver\Deriver\Evaluator\Call\StateRegistry;
21use Deriver\Deriver\Evaluator\EvaluatorConfig;
22use Deriver\Deriver\Evaluator\Evidence\Frontier;
23use Deriver\Deriver\Evaluator\Execution\Call\Allocation;
24use Deriver\Deriver\Evaluator\Execution\Call\ArgumentBinding;
25use Deriver\Deriver\Evaluator\Execution\Call\ArgumentOrder;
26use Deriver\Deriver\Evaluator\Execution\Call\CallableCheck;
27use Deriver\Deriver\Evaluator\Execution\Call\CallExecutor;
28use Deriver\Deriver\Evaluator\Execution\Call\CallResolution;
29use Deriver\Deriver\Evaluator\Execution\Call\Closure\Binding;
30use Deriver\Deriver\Evaluator\Execution\Call\Closure\Capture;
31use Deriver\Deriver\Evaluator\Execution\Call\Contract\Effects;
32use Deriver\Deriver\Evaluator\Execution\Call\Creation\Access;
33use Deriver\Deriver\Evaluator\Execution\Call\Member\Constants;
34use Deriver\Deriver\Evaluator\Execution\Call\Member\Invocation;
35use Deriver\Deriver\Evaluator\Execution\Call\MethodInvocation;
36use Deriver\Deriver\Evaluator\Execution\Call\Native\Invocation as NativeInvocation;
37use Deriver\Deriver\Evaluator\Execution\Call\Native\Properties;
38use Deriver\Deriver\Evaluator\Execution\Call\Native\Signatures;
39use Deriver\Deriver\Evaluator\Execution\Call\ParameterBinding;
40use Deriver\Deriver\Evaluator\Execution\Call\PassedArgument;
41use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Arguments;
42use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Creation;
43use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Methods;
44use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Modes;
45use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Resolution;
46use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Target;
47use Deriver\Deriver\Evaluator\Execution\Call\Preparation\Transfer;
48use Deriver\Deriver\Evaluator\Execution\Call\TypeBinding;
49use Deriver\Deriver\Evaluator\Execution\Call\TypeCheck;
50use Deriver\Deriver\Evaluator\Execution\Call\UnknownCall;
51use Deriver\Deriver\Evaluator\Execution\Completion\Alternative;
52use Deriver\Deriver\Evaluator\Execution\Completion\Assessment;
53use Deriver\Deriver\Evaluator\Execution\Completion\Completion;
54use Deriver\Deriver\Evaluator\Execution\Completion\CompletionKind;
55use Deriver\Deriver\Evaluator\Execution\Completion\StorageSnapshot;
56use Deriver\Deriver\Evaluator\Execution\Constant\ClassNames;
57use Deriver\Deriver\Evaluator\Execution\Constraints;
58use Deriver\Deriver\Evaluator\Execution\Context;
59use Deriver\Deriver\Evaluator\Execution\Control\ExceptionChain;
60use Deriver\Deriver\Evaluator\Execution\Control\ExceptionMatch;
61use Deriver\Deriver\Evaluator\Execution\Control\Handler;
62use Deriver\Deriver\Evaluator\Execution\Control\HandlerPhase;
63use Deriver\Deriver\Evaluator\Execution\Control\IterationStep;
64use Deriver\Deriver\Evaluator\Execution\Control\IteratorCursor;
65use Deriver\Deriver\Evaluator\Execution\Control\LoopConvergence;
66use Deriver\Deriver\Evaluator\Execution\Control\ObservationLimit;
67use Deriver\Deriver\Evaluator\Execution\Control\StateJoin;
68use Deriver\Deriver\Evaluator\Execution\Control\Unwinding;
69use Deriver\Deriver\Evaluator\Execution\Demand\Cell;
70use Deriver\Deriver\Evaluator\Execution\Demand\Components;
71use Deriver\Deriver\Evaluator\Execution\Demand\Discovery;
72use Deriver\Deriver\Evaluator\Execution\Demand\Key;
73use Deriver\Deriver\Evaluator\Execution\Demand\Table;
74use Deriver\Deriver\Evaluator\Execution\Dependencies;
75use Deriver\Deriver\Evaluator\Execution\Evaluator;
76use Deriver\Deriver\Evaluator\Execution\EvaluatorConfig as ExecutionEvaluatorConfig;
77use Deriver\Deriver\Evaluator\Execution\Havoc;
78use Deriver\Deriver\Evaluator\Execution\InstructionTransfer;
79use Deriver\Deriver\Evaluator\Execution\Memory\LiveArray;
80use Deriver\Deriver\Evaluator\Execution\Memory\Location;
81use Deriver\Deriver\Evaluator\Execution\Memory\Materialization;
82use Deriver\Deriver\Evaluator\Execution\Memory\Memory;
83use Deriver\Deriver\Evaluator\Execution\Memory\ReferenceConstraint;
84use Deriver\Deriver\Evaluator\Execution\Memory\Slots\SlotReference;
85use Deriver\Deriver\Evaluator\Execution\Memory\Slots\StateStorage;
86use Deriver\Deriver\Evaluator\Execution\Memory\StorageCapture;
87use Deriver\Deriver\Evaluator\Execution\ObservationCollector;
88use Deriver\Deriver\Evaluator\Execution\Offset\Address;
89use Deriver\Deriver\Evaluator\Execution\Offset\Path;
90use Deriver\Deriver\Evaluator\Execution\Offset\Protocol;
91use Deriver\Deriver\Evaluator\Execution\Offset\ProtocolAccess;
92use Deriver\Deriver\Evaluator\Execution\Offset\Reader;
93use Deriver\Deriver\Evaluator\Execution\Offset\Strings;
94use Deriver\Deriver\Evaluator\Execution\Offset\Transfer as OffsetTransfer;
95use Deriver\Deriver\Evaluator\Execution\Operation\Conversions;
96use Deriver\Deriver\Evaluator\Execution\Operation\ScalarErrors;
97use Deriver\Deriver\Evaluator\Execution\State;
98use Deriver\Deriver\Evaluator\Execution\Summary\CompletionRecord;
99use Deriver\Deriver\Evaluator\Execution\Summary\Evaluation;
100use Deriver\Deriver\Evaluator\Execution\Summary\Invocation as SummaryInvocation;
101use Deriver\Deriver\Evaluator\Execution\Summary\Isolation;
102use Deriver\Deriver\Evaluator\Execution\Trace\Derivation;
103use Deriver\Deriver\Evaluator\Execution\Transfer\CallableTransfer;
104use Deriver\Deriver\Evaluator\Execution\Transfer\CompoundAssignment;
105use Deriver\Deriver\Evaluator\Execution\Transfer\ConstantTransfer;
106use Deriver\Deriver\Evaluator\Execution\Transfer\MemoryStep;
107use Deriver\Deriver\Evaluator\Execution\Transfer\ObjectAccess;
108use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyAccessCheck;
109use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyLookup;
110use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyReference;
111use Deriver\Deriver\Evaluator\Execution\Transfer\PropertySlot;
112use Deriver\Deriver\Evaluator\Execution\Transfer\PropertyTransfer;
113use Deriver\Deriver\Evaluator\Execution\Transfer\PureStep;
114use Deriver\Deriver\Evaluator\Execution\Transfer\ReferenceAssignment;
115use Deriver\Deriver\Evaluator\Request\Budget;
116use Deriver\Deriver\Evaluator\Request\EntryPoint;
117use Deriver\Deriver\Evaluator\Request\QueryScope;
118use Deriver\Deriver\Evaluator\Request\ReturnQuery;
119use Deriver\Deriver\Evaluator\Scalar\Arithmetic;
120use Deriver\Deriver\Evaluator\Scalar\Arrays;
121use Deriver\Deriver\Evaluator\Scalar\Identity;
122use Deriver\Deriver\Evaluator\Scalar\Increment;
123use Deriver\Deriver\Evaluator\Scalar\NumericString;
124use Deriver\Deriver\Evaluator\Scalar\Operations;
125use Deriver\Deriver\Evaluator\TargetProfile;
126use Deriver\Deriver\Evaluator\Term;
127use Deriver\Deriver\Evaluator\TermKind;
128use Deriver\Deriver\Execution\DerivationResult;
129use Deriver\Deriver\Execution\QueryExecution;
130use Deriver\Deriver\Execution\ResultAssessment;
131use Deriver\Deriver\Identity\QueryEncoding;
132use Deriver\Deriver\Identity\ValueGraph;
133use Deriver\Deriver\Lowerer\AggregateLowering;
134use Deriver\Deriver\Lowerer\AssignmentLowering;
135use Deriver\Deriver\Lowerer\CallLowering;
136use Deriver\Deriver\Lowerer\Control\ConditionalLowering;
137use Deriver\Deriver\Lowerer\Control\DestructuringLowering;
138use Deriver\Deriver\Lowerer\Control\ExceptionLowering;
139use Deriver\Deriver\Lowerer\Control\LoopLowering;
140use Deriver\Deriver\Lowerer\Control\StaticLowering;
141use Deriver\Deriver\Lowerer\Declaration\CallableIdentity;
142use Deriver\Deriver\Lowerer\Declaration\CallableSource;
143use Deriver\Deriver\Lowerer\Declaration\ConstantSignatures;
144use Deriver\Deriver\Lowerer\Declaration\Index;
145use Deriver\Deriver\Lowerer\Declaration\Indexer;
146use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassConstant;
147use Deriver\Deriver\Lowerer\Declaration\Metadata\ClassDeclaration;
148use Deriver\Deriver\Lowerer\Declaration\Metadata\PropertyDeclaration;
149use Deriver\Deriver\Lowerer\Declaration\Traits\Composition;
150use Deriver\Deriver\Lowerer\EffectInspection;
151use Deriver\Deriver\Lowerer\ExpressionLowering;
152use Deriver\Deriver\Lowerer\Graph\Argument;
153use Deriver\Deriver\Lowerer\Graph\BasicBlock;
154use Deriver\Deriver\Lowerer\Graph\CallableGraph;
155use Deriver\Deriver\Lowerer\Graph\CatchTarget;
156use Deriver\Deriver\Lowerer\Graph\ExceptionRegion;
157use Deriver\Deriver\Lowerer\Graph\Instruction;
158use Deriver\Deriver\Lowerer\Graph\Parameter;
159use Deriver\Deriver\Lowerer\Graph\Terminator;
160use Deriver\Deriver\Lowerer\Hierarchy\Access as HierarchyAccess;
161use Deriver\Deriver\Lowerer\Hierarchy\Builtins;
162use Deriver\Deriver\Lowerer\Hierarchy\Dispatch;
163use Deriver\Deriver\Lowerer\InstructionEmitter;
164use Deriver\Deriver\Lowerer\Lowerer;
165use Deriver\Deriver\Lowerer\LoweringScope;
166use Deriver\Deriver\Lowerer\StatementLowering;
167use Deriver\Deriver\MicroEvaluatorConversion;
168use Deriver\Deriver\Parser\Node\File;
169use Deriver\Deriver\Parser\Parser;
170use Deriver\Deriver\Parser\ParserConfig;
171use Deriver\Deriver\Parser\TargetSyntax;
172use Deriver\Deriver\Providers\ProviderExpansion;
173use Deriver\Deriver\Resolver\AssignmentPatterns;
174use Deriver\Deriver\Resolver\ClassScope;
175use Deriver\Deriver\Resolver\ExpressionValidation;
176use Deriver\Deriver\Resolver\MagicContext;
177use Deriver\Deriver\Resolver\Resolver;
178use Deriver\Deriver\Selection\QueryValidation;
179use Deriver\Deriver\SnapshotIdentity;
180use Deriver\Deriver\Sources\LineMap;
181use Deriver\Deriver\Sources\Path as SourcesPath;
182use Deriver\Deriver\Sources\Source;
183use Deriver\Deriver\Sources\SourceRef;
184use Deriver\Deriver\Sources\Sources;
185use Deriver\Exception\ParserException;
186use Deriver\Exception\ResolutionException;
187use PHPUnit\Framework\Attributes\CoversClass;
188use PHPUnit\Framework\Attributes\DataProvider;
189use PHPUnit\Framework\Attributes\Small;
190use PHPUnit\Framework\Attributes\UsesClass;
191use PHPUnit\Framework\TestCase;
192
193#[UsesClass(Resolver::class)]
194#[UsesClass(ExpressionValidation::class)]
195#[UsesClass(ResolutionException::class)]
196#[UsesClass(ParserConfig::class)]
197#[UsesClass(ParserException::class)]
198#[CoversClass(InstructionTransfer::class)]
199#[UsesClass(QueryExecution::class)]
200#[UsesClass(QueryValidation::class)]
201#[UsesClass(ResultAssessment::class)]
202#[UsesClass(Deriver::class)]
203#[UsesClass(Constraints::class)]
204#[UsesClass(Argument::class)]
205#[UsesClass(BasicBlock::class)]
206#[UsesClass(CallableGraph::class)]
207#[UsesClass(CallableIdentity::class)]
208#[UsesClass(CatchTarget::class)]
209#[UsesClass(ClassConstant::class)]
210#[UsesClass(ClassDeclaration::class)]
211#[UsesClass(ExceptionRegion::class)]
212#[UsesClass(Instruction::class)]
213#[UsesClass(Parameter::class)]
214#[UsesClass(PropertyDeclaration::class)]
215#[UsesClass(Terminator::class)]
216#[UsesClass(Allocation::class)]
217#[UsesClass(ArgumentBinding::class)]
218#[UsesClass(ArgumentOrder::class)]
219#[UsesClass(CallExecutor::class)]
220#[UsesClass(CallResolution::class)]
221#[UsesClass(CallableCheck::class)]
222#[UsesClass(Binding::class)]
223#[UsesClass(Capture::class)]
224#[UsesClass(Access::class)]
225#[UsesClass(Builtins::class)]
226#[UsesClass(Dispatch::class)]
227#[UsesClass(HierarchyAccess::class)]
228#[UsesClass(Constants::class)]
229#[UsesClass(Invocation::class)]
230#[UsesClass(MethodInvocation::class)]
231#[UsesClass(NativeInvocation::class)]
232#[UsesClass(Properties::class)]
233#[UsesClass(Signatures::class)]
234#[UsesClass(ParameterBinding::class)]
235#[UsesClass(PassedArgument::class)]
236#[UsesClass(Arguments::class)]
237#[UsesClass(Creation::class)]
238#[UsesClass(Methods::class)]
239#[UsesClass(Modes::class)]
240#[UsesClass(Resolution::class)]
241#[UsesClass(Target::class)]
242#[UsesClass(Transfer::class)]
243#[UsesClass(TypeBinding::class)]
244#[UsesClass(TypeCheck::class)]
245#[UsesClass(UnknownCall::class)]
246#[UsesClass(Completion::class)]
247#[UsesClass(ClassNames::class)]
248#[UsesClass(Context::class)]
249#[UsesClass(ExceptionChain::class)]
250#[UsesClass(ExceptionMatch::class)]
251#[UsesClass(Handler::class)]
252#[UsesClass(IterationStep::class)]
253#[UsesClass(IteratorCursor::class)]
254#[UsesClass(LoopConvergence::class)]
255#[UsesClass(ObservationLimit::class)]
256#[UsesClass(StateJoin::class)]
257#[UsesClass(Unwinding::class)]
258#[UsesClass(Cell::class)]
259#[UsesClass(Components::class)]
260#[UsesClass(Discovery::class)]
261#[UsesClass(Key::class)]
262#[UsesClass(Table::class)]
263#[UsesClass(Dependencies::class)]
264#[UsesClass(Havoc::class)]
265#[UsesClass(Evaluator::class)]
266#[UsesClass(Effects::class)]
267#[UsesClass(SlotReference::class)]
268#[UsesClass(StateStorage::class)]
269#[UsesClass(ObservationCollector::class)]
270#[UsesClass(Address::class)]
271#[UsesClass(Path::class)]
272#[UsesClass(Protocol::class)]
273#[UsesClass(ProtocolAccess::class)]
274#[UsesClass(Reader::class)]
275#[UsesClass(Strings::class)]
276#[UsesClass(OffsetTransfer::class)]
277#[UsesClass(Conversions::class)]
278#[UsesClass(ScalarErrors::class)]
279#[UsesClass(State::class)]
280#[UsesClass(CompletionRecord::class)]
281#[UsesClass(Evaluation::class)]
282#[UsesClass(SummaryInvocation::class)]
283#[UsesClass(Isolation::class)]
284#[UsesClass(CallableTransfer::class)]
285#[UsesClass(CompoundAssignment::class)]
286#[UsesClass(ConstantTransfer::class)]
287#[UsesClass(MemoryStep::class)]
288#[UsesClass(ObjectAccess::class)]
289#[UsesClass(PropertyAccessCheck::class)]
290#[UsesClass(PropertyLookup::class)]
291#[UsesClass(PropertyReference::class)]
292#[UsesClass(PropertySlot::class)]
293#[UsesClass(PropertyTransfer::class)]
294#[UsesClass(PureStep::class)]
295#[UsesClass(ReferenceAssignment::class)]
296#[UsesClass(LiveArray::class)]
297#[UsesClass(Location::class)]
298#[UsesClass(Materialization::class)]
299#[UsesClass(Memory::class)]
300#[UsesClass(ReferenceConstraint::class)]
301#[UsesClass(StorageCapture::class)]
302#[UsesClass(TypePredicates::class)]
303#[UsesClass(DispatchDecision::class)]
304#[UsesClass(Extensions::class)]
305#[UsesClass(ProviderExpansion::class)]
306#[UsesClass(Evaluators::class)]
307#[UsesClass(StateRegistry::class)]
308#[UsesClass(StateSlot::class)]
309#[UsesClass(DeriverConfig::class)]
310#[UsesClass(EntryPoint::class)]
311#[UsesClass(Sources::class)]
312#[UsesClass(SourcesPath::class)]
313#[UsesClass(Snapshot::class)]
314#[UsesClass(Source::class)]
315#[UsesClass(TargetProfile::class)]
316#[UsesClass(Budget::class)]
317#[UsesClass(QueryScope::class)]
318#[UsesClass(ReturnQuery::class)]
319#[UsesClass(ResultRef::class)]
320#[UsesClass(SourceRef::class)]
321#[UsesClass(Alternative::class)]
322#[UsesClass(Assessment::class)]
323#[UsesClass(Derivation::class)]
324#[UsesClass(DerivationResult::class)]
325#[UsesClass(Frontier::class)]
326#[UsesClass(QueryEncoding::class)]
327#[UsesClass(ValueGraph::class)]
328#[UsesClass(Statistics::class)]
329#[UsesClass(StorageSnapshot::class)]
330#[UsesClass(Parser::class)]
331#[UsesClass(File::class)]
332#[UsesClass(AggregateLowering::class)]
333#[UsesClass(AssignmentLowering::class)]
334#[UsesClass(CallLowering::class)]
335#[UsesClass(Lowerer::class)]
336#[UsesClass(ConditionalLowering::class)]
337#[UsesClass(DestructuringLowering::class)]
338#[UsesClass(ExceptionLowering::class)]
339#[UsesClass(LoopLowering::class)]
340#[UsesClass(StaticLowering::class)]
341#[UsesClass(EffectInspection::class)]
342#[UsesClass(ExpressionLowering::class)]
343#[UsesClass(InstructionEmitter::class)]
344#[UsesClass(LoweringScope::class)]
345#[UsesClass(StatementLowering::class)]
346#[UsesClass(ConstantSignatures::class)]
347#[UsesClass(CallableSource::class)]
348#[UsesClass(Indexer::class)]
349#[UsesClass(Index::class)]
350#[UsesClass(Composition::class)]
351#[UsesClass(LineMap::class)]
352#[UsesClass(MagicContext::class)]
353#[UsesClass(AssignmentPatterns::class)]
354#[UsesClass(ClassScope::class)]
355#[UsesClass(TargetSyntax::class)]
356#[UsesClass(Arithmetic::class)]
357#[UsesClass(Arrays::class)]
358#[UsesClass(Identity::class)]
359#[UsesClass(Increment::class)]
360#[UsesClass(NumericString::class)]
361#[UsesClass(Operations::class)]
362#[UsesClass(Term::class)]
363#[Small]
364final class InstructionTransferTest extends TestCase
365{
366 public function testApplyPreservesTheSemanticContract(): void
367 {
368 $source = '<?php function target() { $x="before"; $x="after"; return $x . ":done"; }';
369 $symbol = 'target';
370 $input = new Sources([new Source('fixture.php', $source)]);
371 $configuration = new DeriverConfig();
372 $contributions = new Contributions();
373 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
374 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
375 $contributions1 = $providers->contributions;
376 $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);
377 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
378 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
379 $snapshotId = $identity->id;
380 $input1 = $providers->input;
381 $parser = new Parser(new ParserConfig($target->version));
382 $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));
383 $program = new Index($snapshotId, $trees);
384 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
385 self::assertSame('after:done', $result->normalOutcomes[0]->values['return']->native());
386 self::assertSame([], $result->frontiers);
387 self::assertSame([], $result->exceptionalOutcomes);
388 }
389 public function testOtherConvertsEvalIntoAnExplicitStateBoundary(): void
390 {
391 $source = '<?php function target(){return 1;}';
392 $budget = new Budget();
393 $configuration = new DeriverConfig();
394 $contributions = new Contributions();
395 $input = new Sources([new Source('fixture.php', $source)]);
396 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
397 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
398 $contributions1 = $providers->contributions;
399 $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);
400 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
401 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
402 $snapshotId = $identity->id;
403 $input1 = $providers->input;
404 $parser = new Parser(new ParserConfig($target->version));
405 $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));
406 $program1 = new Index($snapshotId, $trees);
407 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
408 $snapshotId1 = 'test';
409 $input2 = new Sources([new Source('fixture.php', $source)]);
410 $profile = $execution->configuration->target;
411 $parser1 = new Parser(new ParserConfig($profile->version));
412 $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));
413 $program = new Index($snapshotId1, $trees1);
414 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
415 $machine = new Evaluator($context);
416 $body = $context->program->callable('target');
417 self::assertNotNull($body);
418 $state = new State();
419 $state->memory->write($state->local('x'), Term::constant(1));
420 $value = (new InstructionTransfer($machine))->other($body, new BoundInstruction('eval', 'symbol-table-boundary', $body->source, 'result', name: 'UNSUPPORTED_LANGUAGE_FEATURE'), $state);
421 self::assertSame(TermKind::Opaque, $value->kind);
422 self::assertSame(TermKind::Opaque, $state->snapshot()['x']->kind);
423 }
424 public function testBoundaryKeepsBothCaughtAndNormallyCompletedUnknownCode(): void
425 {
426 $source = '<?php function target($code){try{eval($code);}catch(Throwable $e){return "caught";}return "normal";}';
427 $symbol = 'target';
428 $input = new Sources([new Source('fixture.php', $source)]);
429 $configuration = new DeriverConfig();
430 $contributions = new Contributions();
431 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
432 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
433 $contributions1 = $providers->contributions;
434 $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);
435 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
436 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
437 $snapshotId = $identity->id;
438 $input1 = $providers->input;
439 $parser = new Parser(new ParserConfig($target->version));
440 $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));
441 $program = new Index($snapshotId, $trees);
442 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
443 $values = array_column(array_column(array_column($result->normalOutcomes, 'values'), 'return'), 'literal');
444 sort($values);
445 self::assertSame(['caught', 'normal'], $values);
446 self::assertSame([], $result->exceptionalOutcomes);
447 self::assertNotEmpty($result->frontiers);
448 }
449 public function testStorageRoutesOffsetReadsThroughContainerSemantics(): void
450 {
451 $source = '<?php function target(){return 1;}';
452 $budget = new Budget();
453 $configuration = new DeriverConfig();
454 $contributions = new Contributions();
455 $input = new Sources([new Source('fixture.php', $source)]);
456 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
457 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
458 $contributions1 = $providers->contributions;
459 $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);
460 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
461 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
462 $snapshotId = $identity->id;
463 $input1 = $providers->input;
464 $parser = new Parser(new ParserConfig($target->version));
465 $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));
466 $program1 = new Index($snapshotId, $trees);
467 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
468 $snapshotId1 = 'test';
469 $input2 = new Sources([new Source('fixture.php', $source)]);
470 $profile = $execution->configuration->target;
471 $parser1 = new Parser(new ParserConfig($profile->version));
472 $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));
473 $program = new Index($snapshotId1, $trees1);
474 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
475 $body = $context->program->callable('target');
476 self::assertNotNull($body);
477 $instruction = new BoundInstruction('offset', 'write', $body->source, 'result', ['slot', 'rhs']);
478 $state = new State();
479 $state->addresses['base'] = $state->memory->allocate(Term::constant('abc'));
480 $state->offsets['slot'] = new Address('base', Term::constant(-1));
481 $read = new BoundInstruction('read', 'read', $body->source, 'result', ['slot']);
482 $paths = (new InstructionTransfer(new Evaluator($context)))->storage($body, $read, $state);
483 self::assertNotNull($paths);
484 self::assertSame('c', $paths[0]->value('result')->native());
485 }
486 public function testOffsetReadPreservesSilentAbsenceOnComputedArrays(): void
487 {
488 $source = '<?php class Box implements ArrayAccess {public mixed $value=0;function offsetGet($key){return $key;}function offsetExists($key){return $key === "yes";}function offsetSet($key,$value){$this->value=$value;}function offsetUnset($key){$this->value=null;}}function target(){}';
489 $budget = new Budget();
490 $configuration = new DeriverConfig();
491 $contributions = new Contributions();
492 $input = new Sources([new Source('fixture.php', $source)]);
493 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
494 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
495 $contributions1 = $providers->contributions;
496 $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);
497 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
498 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
499 $snapshotId = $identity->id;
500 $input1 = $providers->input;
501 $parser = new Parser(new ParserConfig($target->version));
502 $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));
503 $program1 = new Index($snapshotId, $trees);
504 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
505 $snapshotId1 = 'test';
506 $input2 = new Sources([new Source('fixture.php', $source)]);
507 $profile = $execution->configuration->target;
508 $parser1 = new Parser(new ParserConfig($profile->version));
509 $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));
510 $program = new Index($snapshotId1, $trees1);
511 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
512 $body = $context->program->callable('target');
513 self::assertNotNull($body);
514 $state = new State();
515 $state->memory->cells['box'] = Term::array(['value' => Term::constant(0)]);
516 $state->memory->classes['box'] = 'Box';
517 $receiver = new Term(TermKind::Object, 'box', attributes: ['class' => 'Box']);
518 $access = new ProtocolAccess($receiver, Term::constant('yes'));
519 $protocol = new Protocol(new Evaluator($context));
520 $state->registers['array'] = Term::array([]);
521 $state->registers['key'] = Term::constant('absent');
522 $instruction = new BoundInstruction('read', 'array-read', $body->source, 'result', ['array', 'key'], attributes: ['silent' => true]);
523 $paths = (new InstructionTransfer(new Evaluator($context)))->offsetRead($body, $instruction, $state);
524 self::assertSame(TermKind::Uninitialized, $paths[0]->value('result')->kind);
525 self::assertSame([], $context->frontiers);
526 }
527 public function testStorageRetainsUnknownReferenceEffectsAndExceptions(): void
528 {
529 $source = '<?php function target(){return 1;}';
530 $budget = new Budget();
531 $configuration = new DeriverConfig();
532 $contributions = new Contributions();
533 $input = new Sources([new Source('fixture.php', $source)]);
534 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
535 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
536 $contributions1 = $providers->contributions;
537 $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);
538 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
539 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
540 $snapshotId = $identity->id;
541 $input1 = $providers->input;
542 $parser = new Parser(new ParserConfig($target->version));
543 $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));
544 $program1 = new Index($snapshotId, $trees);
545 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
546 $snapshotId1 = 'test';
547 $input2 = new Sources([new Source('fixture.php', $source)]);
548 $profile = $execution->configuration->target;
549 $parser1 = new Parser(new ParserConfig($profile->version));
550 $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));
551 $program = new Index($snapshotId1, $trees1);
552 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
553 $body = $context->program->callable('target');
554 self::assertNotNull($body);
555 $machine = new Evaluator($context);
556 $state = new State();
557 $instruction = new BoundInstruction('arg', 'argument', $body->source, 'result', ['prepared', 'input']);
558 $state->addresses['unknown'] = new Location('unresolved', unknown: true);
559 $state->memory->write($state->local('value'), Term::constant(1));
560 $reference = new BoundInstruction('ref', 'reference', $body->source, 'result', ['unknown']);
561 $paths = (new InstructionTransfer($machine))->storage($body, $reference, $state);
562 self::assertNotNull($paths);
563 self::assertCount(2, $paths);
564 self::assertSame(TermKind::Opaque, $paths[0]->registers['result']->kind);
565 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
566 }
567 public function testPreparationLeavesOrdinaryOperationsToValueTransfer(): void
568 {
569 $source = '<?php function target(){return 1;}';
570 $budget = new Budget();
571 $configuration = new DeriverConfig();
572 $contributions = new Contributions();
573 $input = new Sources([new Source('fixture.php', $source)]);
574 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
575 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
576 $contributions1 = $providers->contributions;
577 $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);
578 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
579 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
580 $snapshotId = $identity->id;
581 $input1 = $providers->input;
582 $parser = new Parser(new ParserConfig($target->version));
583 $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));
584 $program1 = new Index($snapshotId, $trees);
585 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
586 $snapshotId1 = 'test';
587 $input2 = new Sources([new Source('fixture.php', $source)]);
588 $profile = $execution->configuration->target;
589 $parser1 = new Parser(new ParserConfig($profile->version));
590 $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));
591 $program = new Index($snapshotId1, $trees1);
592 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
593 $body = $context->program->callable('target');
594 self::assertNotNull($body);
595 $instruction = new BoundInstruction('value', 'constant', $body->source, constant: Term::constant(1));
596 self::assertNull((new InstructionTransfer(new Evaluator($context)))->preparation($body, $instruction, new State()));
597 }
598 /**
599 * @param Term $item Unpack operand
600 */
601 #[DataProvider('providerInvalidUnpacks')]
602 public function testUnpackRejectsNonIterableValues(Term $item, string $exception): void
603 {
604 $state = new State();
605 $state->registers = ['array' => Term::array([]), 'item' => $item];
606 $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
607 $source = '<?php function target(){return 1;}';
608 $budget = new Budget();
609 $configuration = new DeriverConfig();
610 $contributions = new Contributions();
611 $input = new Sources([new Source('fixture.php', $source)]);
612 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
613 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
614 $contributions1 = $providers->contributions;
615 $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);
616 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
617 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
618 $snapshotId = $identity->id;
619 $input1 = $providers->input;
620 $parser = new Parser(new ParserConfig($target->version));
621 $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));
622 $program1 = new Index($snapshotId, $trees);
623 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
624 $snapshotId1 = 'test';
625 $input2 = new Sources([new Source('fixture.php', $source)]);
626 $profile = $execution->configuration->target;
627 $parser1 = new Parser(new ParserConfig($profile->version));
628 $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));
629 $program = new Index($snapshotId1, $trees1);
630 $paths = (new InstructionTransfer(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators))))->unpack($instruction, $state);
631 self::assertCount(1, $paths);
632 self::assertSame(CompletionKind::Throw, $paths[0]->completion->kind);
633 self::assertNotNull($paths[0]->completion->value);
634 self::assertSame($exception, $paths[0]->completion->value->literal);
635 }
636 /**
637 * @return array<string,array{Term,string}>
638 */
639 public static function providerInvalidUnpacks(): array
640 {
641 return ['null' => [Term::constant(null), 'Error'], 'int' => [Term::constant(3), 'Error'], 'string' => [Term::constant('abc'), 'Error'], 'bool' => [Term::constant(false), 'Error'], 'closure' => [new Term(TermKind::Closure, 'body'), 'TypeError']];
642 }
643 public function testUnpackRetainsUnknownIterableEffectsAndBothExits(): void
644 {
645 $source = '<?php function target(){return 1;}';
646 $budget = new Budget();
647 $configuration = new DeriverConfig();
648 $contributions = new Contributions();
649 $input = new Sources([new Source('fixture.php', $source)]);
650 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
651 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
652 $contributions1 = $providers->contributions;
653 $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);
654 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
655 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
656 $snapshotId = $identity->id;
657 $input1 = $providers->input;
658 $parser = new Parser(new ParserConfig($target->version));
659 $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));
660 $program1 = new Index($snapshotId, $trees);
661 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
662 $snapshotId1 = 'test';
663 $input2 = new Sources([new Source('fixture.php', $source)]);
664 $profile = $execution->configuration->target;
665 $parser1 = new Parser(new ParserConfig($profile->version));
666 $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));
667 $program = new Index($snapshotId1, $trees1);
668 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
669 $state = new State();
670 $state->memory->cells['global:x'] = Term::constant(4);
671 $state->registers = ['array' => Term::array([]), 'item' => Term::parameter('input', 'iterable')];
672 $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
673 $paths = (new InstructionTransfer(new Evaluator($context)))->unpack($instruction, $state);
674 self::assertCount(2, $paths);
675 self::assertSame(TermKind::Opaque, $paths[0]->registers['result']->kind);
676 self::assertSame(TermKind::Opaque, $paths[0]->memory->cells['global:x']->kind);
677 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
678 self::assertSame(['UNSUPPORTED_LANGUAGE_FEATURE'], array_column(array_values($context->frontiers), 'code'));
679 }
680 public function testUnpackMergesClosedArraysInInsertionOrder(): void
681 {
682 $state = new State();
683 $state->registers = ['array' => Term::fromNative(['a' => 1, 0 => 'first']), 'item' => Term::fromNative(['a' => 2, 7 => 'last'])];
684 $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
685 $source = '<?php function target(){return 1;}';
686 $budget = new Budget();
687 $configuration = new DeriverConfig();
688 $contributions = new Contributions();
689 $input = new Sources([new Source('fixture.php', $source)]);
690 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
691 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
692 $contributions1 = $providers->contributions;
693 $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);
694 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
695 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
696 $snapshotId = $identity->id;
697 $input1 = $providers->input;
698 $parser = new Parser(new ParserConfig($target->version));
699 $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));
700 $program1 = new Index($snapshotId, $trees);
701 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
702 $snapshotId1 = 'test';
703 $input2 = new Sources([new Source('fixture.php', $source)]);
704 $profile = $execution->configuration->target;
705 $parser1 = new Parser(new ParserConfig($profile->version));
706 $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));
707 $program = new Index($snapshotId1, $trees1);
708 $paths = (new InstructionTransfer(new Evaluator(new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators))))->unpack($instruction, $state);
709 self::assertCount(1, $paths);
710 self::assertSame(['a' => 2, 0 => 'first', 1 => 'last'], $paths[0]->registers['result']->native());
711 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
712 }
713 /**
714 * @param string $source Captured PHP source
715 * @param mixed $expected Concrete returned state
716 */
717 #[DataProvider('providerInstructionPrograms')]
718 public function testApplyDispatchesEffectsAndReturnsTheFinalState(string $source, mixed $expected): void
719 {
720 $symbol = 'target';
721 $input = new Sources([new Source('fixture.php', $source)]);
722 $configuration = new DeriverConfig();
723 $contributions = new Contributions();
724 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
725 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
726 $contributions1 = $providers->contributions;
727 $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);
728 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
729 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
730 $snapshotId = $identity->id;
731 $input1 = $providers->input;
732 $parser = new Parser(new ParserConfig($target->version));
733 $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));
734 $program = new Index($snapshotId, $trees);
735 $result = (new QueryExecution($program, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program->diagnostics()))))->derive(new ReturnQuery($symbol));
736 self::assertCount(1, $result->normalOutcomes);
737 self::assertSame($expected, $result->normalOutcomes[0]->values['return']->native());
738 self::assertSame([], $result->exceptionalOutcomes);
739 self::assertSame([], $result->frontiers);
740 }
741 /**
742 * @return iterable<string, array{string, mixed}>
743 */
744 public static function providerInstructionPrograms(): iterable
745 {
746 yield 'array explicit and append' => ['<?php function target(){$a=[4=>"a"]; $a["key"]="b"; $a[]="c";return $a;}', [4 => 'a', 'key' => 'b', 5 => 'c']];
747 yield 'array unpack' => ['<?php function target(){return [1,...[9=>2,"x"=>3],4];}', [1, 2, 'x' => 3, 4]];
748 yield 'computed offset' => ['<?php function target(){return (["x"=>12])["x"];} ', 12];
749 yield 'unary conversion' => ['<?php function target(){return [(int)"17",(bool)[],!1,-4];}', [17, false, false, -4]];
750 yield 'string conversion method' => ['<?php class Text{function __toString(){return "value";}} function target(){return (string)new Text;}', 'value'];
751 yield 'compound assignment' => ['<?php function target(){$a=["x"=>4];$v=($a["x"]+=3);return [$a,$v];}', [['x' => 7], 7]];
752 yield 'constant fetch' => ['<?php const X=12;function target(){return X;}', 12];
753 yield 'class constant' => ['<?php class A{const X=13;}function target(){return A::X;}', 13];
754 yield 'instanceof' => ['<?php class A{}function target(){return (new A) instanceof A;}', true];
755 yield 'static call' => ['<?php class A{static function value($x){return $x+1;}}function target(){return A::value(4);}', 5];
756 yield 'method call' => ['<?php class A{public $x=4;function value(){return $this->x;}}function target(){return (new A)->value();}', 4];
757 yield 'clone' => ['<?php class A{public $x=4;}function target(){$a=new A;$b=clone $a;$b->x=7;return [$a->x,$b->x];}', [4, 7]];
758 yield 'closure' => ['<?php function target(){$x=4;$f=function($v)use($x){return $x+$v;};return $f(3);}', 7];
759 yield 'first class function' => ['<?php function value($v){return $v+2;}function target(){$f=value(...);return $f(3);}', 5];
760 yield 'first class method' => ['<?php class A{function value($v){return $v+2;}}function target(){$a=new A;$f=$a->value(...);return $f(3);}', 5];
761 yield 'reference array source' => ['<?php function target(){$a=["x"=>1];$r=&$a["x"];$r=7;return $a;}', ['x' => 7]];
762 yield 'reference property source' => ['<?php class A{public int $x=1;}function target(){$a=new A;$r=&$a->x;$r=7;return $a->x;}', 7];
763 yield 'property write' => ['<?php class A{public int $x=1;}function target(){$a=new A;$a->x=4;return $a->x;}', 4];
764 yield 'property increment' => ['<?php class A{public int $x=1;}function target(){$a=new A;$v=$a->x++;return [$v,$a->x];}', [1, 2]];
765 yield 'property unset' => ['<?php class A{public int $x=1;}function target(){$a=new A;unset($a->x);return isset($a->x);}', false];
766 yield 'typed reference assignment' => ['<?php class A{public int $x=1;}function target(){$a=new A;$r=&$a->x;$r="12";return [$r,$a->x];}', [12, 12]];
767 yield 'typed reference error preserves state' => ['<?php class A{public int $x=1;}function target(){$a=new A;$r=&$a->x;try{$r=[];}catch(TypeError $e){}return [$r,$a->x];}', [1, 1]];
768 yield 'array offset increment' => ['<?php function target(){$a=[4];$v=$a[0]++;return [$v,$a];}', [4, [5]]];
769 yield 'array offset unset' => ['<?php function target(){$a=["x"=>4,"y"=>5];unset($a["x"]);return $a;}', ['y' => 5]];
770 yield 'by reference loop' => ['<?php function target(){$a=[1,2];foreach($a as &$v){$v+=2;}unset($v);return $a;}', [3, 4]];
771 yield 'by value loop' => ['<?php function target(){$a=[];foreach(["x"=>3,"y"=>4] as $k=>$v){$a[$k]=$v+1;}return $a;}', ['x' => 4, 'y' => 5]];
772 yield 'static local' => ['<?php function counter(){static $x=0;return ++$x;}function target(){return [counter(),counter()];}', [1, 2]];
773 yield 'global alias' => ['<?php function target(){global $x;$x=4;$r=&$x;$r=8;return $x;}', 8];
774 yield 'throw instruction' => ['<?php function target(){try{throw new RuntimeException;}catch(RuntimeException $e){return 9;}}', 9];
775 yield 'ArrayAccess computed read' => ['<?php class Bag implements ArrayAccess{function offsetGet($k):mixed{return $k."!";}function offsetExists($k):bool{return true;}function offsetSet($k,$v):void{}function offsetUnset($k):void{}}function target(){return (new Bag)["x"];}', 'x!'];
776 yield 'ArrayAccess silent read' => ['<?php class Bag implements ArrayAccess{function offsetExists($k):bool{return false;}function offsetGet($k):mixed{throw new Error;}function offsetSet($k,$v):void{}function offsetUnset($k):void{}}function target(){return (new Bag)["x"]??8;}', 8];
777 }
778 public function testApplyArrayConstructionPreservesSharedReferenceCells(): void
779 {
780 $source = '<?php function target(){return 1;}';
781 $budget = new Budget();
782 $configuration = new DeriverConfig();
783 $contributions = new Contributions();
784 $input = new Sources([new Source('fixture.php', $source)]);
785 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
786 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
787 $contributions1 = $providers->contributions;
788 $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);
789 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
790 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
791 $snapshotId = $identity->id;
792 $input1 = $providers->input;
793 $parser = new Parser(new ParserConfig($target->version));
794 $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));
795 $program1 = new Index($snapshotId, $trees);
796 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
797 $snapshotId1 = 'test';
798 $input2 = new Sources([new Source('fixture.php', $source)]);
799 $profile = $execution->configuration->target;
800 $parser1 = new Parser(new ParserConfig($profile->version));
801 $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));
802 $program = new Index($snapshotId1, $trees1);
803 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
804 $body = $context->program->callable('target');
805 self::assertNotNull($body);
806 $state = new State();
807 $cell = $state->memory->allocate(Term::constant(4));
808 $reference = new Term(TermKind::Cell, $cell->root);
809 $state->registers = ['array' => Term::array([]), 'key' => Term::constant('shared'), 'value' => $reference];
810 $paths = (new InstructionTransfer(new Evaluator($context)))->apply($body, new BoundInstruction('set', 'array-set', $body->source, 'result', ['array', 'key', 'value']), $state);
811 self::assertCount(1, $paths);
812 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
813 self::assertSame($reference, $paths[0]->registers['result']->operands['shared']);
814 $paths[0]->memory->write($cell, Term::constant(9));
815 self::assertSame(9, $paths[0]->memory->element($paths[0]->registers['result'], 'shared')->native());
816 }
817 public function testApplyArrayConstructionRetainsAnUncomputedElement(): void
818 {
819 $source = '<?php function target(){return 1;}';
820 $budget = new Budget();
821 $configuration = new DeriverConfig();
822 $contributions = new Contributions();
823 $input = new Sources([new Source('fixture.php', $source)]);
824 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
825 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
826 $contributions1 = $providers->contributions;
827 $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);
828 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
829 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
830 $snapshotId = $identity->id;
831 $input1 = $providers->input;
832 $parser = new Parser(new ParserConfig($target->version));
833 $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));
834 $program1 = new Index($snapshotId, $trees);
835 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
836 $snapshotId1 = 'test';
837 $input2 = new Sources([new Source('fixture.php', $source)]);
838 $profile = $execution->configuration->target;
839 $parser1 = new Parser(new ParserConfig($profile->version));
840 $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));
841 $program = new Index($snapshotId1, $trees1);
842 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
843 $body = $context->program->callable('target');
844 self::assertNotNull($body);
845 $state = new State();
846 $state->registers['array'] = Term::array([]);
847 $paths = (new InstructionTransfer(new Evaluator($context)))->apply($body, new BoundInstruction('set', 'array-set', $body->source, 'result', ['array', '', 'absent']), $state);
848 self::assertCount(1, $paths);
849 self::assertSame('UNCOMPUTED_REGISTER', $paths[0]->registers['result']->operands[0]->literal);
850 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
851 }
852 public function testUnpackOpenArraysKeepsBothContinuationsAndKnownDependencies(): void
853 {
854 $source = '<?php function target(){return 1;}';
855 $budget = new Budget();
856 $configuration = new DeriverConfig();
857 $contributions = new Contributions();
858 $input = new Sources([new Source('fixture.php', $source)]);
859 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
860 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
861 $contributions1 = $providers->contributions;
862 $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);
863 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
864 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
865 $snapshotId = $identity->id;
866 $input1 = $providers->input;
867 $parser = new Parser(new ParserConfig($target->version));
868 $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));
869 $program1 = new Index($snapshotId, $trees);
870 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
871 $snapshotId1 = 'test';
872 $input2 = new Sources([new Source('fixture.php', $source)]);
873 $profile = $execution->configuration->target;
874 $parser1 = new Parser(new ParserConfig($profile->version));
875 $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));
876 $program = new Index($snapshotId1, $trees1);
877 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
878 $state = new State();
879 $array = Term::array(['known' => Term::constant(4), PHP_INT_MAX - 1 => Term::constant(5)]);
880 $item = Term::array(['item' => Term::constant(8)], open: true);
881 $state->registers = ['array' => $array, 'item' => $item];
882 $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
883 $paths = (new InstructionTransfer(new Evaluator($context)))->unpack($instruction, $state);
884 self::assertCount(2, $paths);
885 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
886 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
887 self::assertSame('Error', $paths[1]->completion->value?->literal);
888 self::assertSame(TermKind::ArrayMerge, $paths[0]->registers['result']->kind);
889 $frontier = array_values($context->frontiers)[0];
890 self::assertSame('WIDENED', $frontier->code);
891 self::assertSame('symbolic-unpack-append', $frontier->operation);
892 self::assertSame([$array, $item], $frontier->residual?->operands);
893 }
894 public function testUnpackIntoAnAppendableArrayCannotReachTheOccupiedMaximumIndex(): void
895 {
896 $source = '<?php function target(){return 1;}';
897 $budget = new Budget();
898 $configuration = new DeriverConfig();
899 $contributions = new Contributions();
900 $input = new Sources([new Source('fixture.php', $source)]);
901 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
902 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
903 $contributions1 = $providers->contributions;
904 $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);
905 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
906 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
907 $snapshotId = $identity->id;
908 $input1 = $providers->input;
909 $parser = new Parser(new ParserConfig($target->version));
910 $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));
911 $program1 = new Index($snapshotId, $trees);
912 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
913 $snapshotId1 = 'test';
914 $input2 = new Sources([new Source('fixture.php', $source)]);
915 $profile = $execution->configuration->target;
916 $parser1 = new Parser(new ParserConfig($profile->version));
917 $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));
918 $program = new Index($snapshotId1, $trees1);
919 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
920 $state = new State();
921 $state->registers = ['array' => Term::array(['known' => Term::constant(4), 7 => Term::constant(5)]), 'item' => Term::parameter('input', 'array')];
922 $instruction = new BoundInstruction('unpack', 'array-unpack', new SourceRef('test', 'fixture.php', 0, 1), 'result', ['array', '', 'item']);
923 $paths = (new InstructionTransfer(new Evaluator($context)))->unpack($instruction, $state);
924 self::assertCount(1, $paths);
925 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
926 self::assertSame(TermKind::ArrayMerge, $paths[0]->registers['result']->kind);
927 self::assertSame([], $context->frontiers);
928 }
929 /**
930 * @param string $operation Boundary instruction
931 * @param string $name Descriptive or diagnostic name
932 * @param string $reason Expected frontier
933 */
934 #[DataProvider('providerBoundaries')]
935 public function testBoundaryInvalidatesStateAndKeepsIndependentExitStates(string $operation, string $name, string $reason): void
936 {
937 $source = '<?php function target(){return 1;}';
938 $budget = new Budget();
939 $configuration = new DeriverConfig();
940 $contributions = new Contributions();
941 $input = new Sources([new Source('fixture.php', $source)]);
942 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
943 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
944 $contributions1 = $providers->contributions;
945 $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);
946 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
947 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
948 $snapshotId = $identity->id;
949 $input1 = $providers->input;
950 $parser = new Parser(new ParserConfig($target->version));
951 $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));
952 $program1 = new Index($snapshotId, $trees);
953 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
954 $snapshotId1 = 'test';
955 $input2 = new Sources([new Source('fixture.php', $source)]);
956 $profile = $execution->configuration->target;
957 $parser1 = new Parser(new ParserConfig($profile->version));
958 $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));
959 $program = new Index($snapshotId1, $trees1);
960 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
961 $body = $context->program->callable('target');
962 self::assertNotNull($body);
963 $state = new State();
964 $state->memory->write($state->local('value'), Term::constant(7));
965 $instruction = new BoundInstruction('boundary', $operation, $body->source, 'result', name: $name);
966 $paths = (new InstructionTransfer(new Evaluator($context)))->apply($body, $instruction, $state);
967 self::assertCount(2, $paths);
968 self::assertNotSame($paths[0], $paths[1]);
969 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
970 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
971 self::assertSame('Throwable', $paths[1]->completion->value?->literal);
972 self::assertSame(true, $paths[1]->completion->value->attributes['uncertain']);
973 self::assertSame($reason, $paths[0]->registers['result']->literal);
974 self::assertSame($reason, $paths[0]->snapshot()['value']->literal);
975 self::assertSame([$reason], array_column($context->frontiers, 'code'));
976 self::assertSame([$name], array_column($context->frontiers, 'operation'));
977 $paths[0]->memory->write($paths[0]->local('value'), Term::constant(9));
978 self::assertSame($reason, $paths[1]->snapshot()['value']->literal);
979 }
980 /**
981 * @return iterable<string,array{string,string,string}>
982 */
983 public static function providerBoundaries(): iterable
984 {
985 yield 'unsupported' => ['unsupported', 'unknown-syntax', 'UNSUPPORTED_LANGUAGE_FEATURE'];
986 yield 'symbol table' => ['symbol-table-boundary', 'DYNAMIC_SYMBOL_TABLE', 'DYNAMIC_SYMBOL_TABLE'];
987 yield 'order' => ['uncertain-order', 'multiple-effects', 'UNSPECIFIED_EVALUATION_ORDER'];
988 }
989 public function testOtherPushesTheSelectedExceptionRegionOntoExistingHandlers(): void
990 {
991 $source = '<?php function target(){try{return 1;}finally{$x=2;}}';
992 $budget = new Budget();
993 $configuration = new DeriverConfig();
994 $contributions = new Contributions();
995 $input = new Sources([new Source('fixture.php', $source)]);
996 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
997 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
998 $contributions1 = $providers->contributions;
999 $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);
1000 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1001 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1002 $snapshotId = $identity->id;
1003 $input1 = $providers->input;
1004 $parser = new Parser(new ParserConfig($target->version));
1005 $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));
1006 $program1 = new Index($snapshotId, $trees);
1007 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1008 $snapshotId1 = 'test';
1009 $input2 = new Sources([new Source('fixture.php', $source)]);
1010 $profile = $execution->configuration->target;
1011 $parser1 = new Parser(new ParserConfig($profile->version));
1012 $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));
1013 $program = new Index($snapshotId1, $trees1);
1014 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1015 $body = $context->program->callable('target');
1016 self::assertNotNull($body);
1017 $state = new State();
1018 $outer = new Handler(new ExceptionRegion([], null, 99));
1019 $state->handlers = [$outer];
1020 $state = $state->fork();
1021 $result = (new InstructionTransfer(new Evaluator($context)))->other($body, new BoundInstruction('enter', 'enter-try', $body->source, attributes: ['region' => 0]), $state);
1022 self::assertNull($result->native());
1023 self::assertCount(2, $state->handlers);
1024 $handlers = $state->handlers;
1025 self::assertSame($outer, $handlers[0]);
1026 self::assertSame($body->regions[0], $handlers[1]->region);
1027 self::assertSame(HandlerPhase::Try, $handlers[1]->phase);
1028 }
1029 public function testOtherThrowRetainsTheEvaluatedThrowableIdentity(): void
1030 {
1031 $source = '<?php function target(){return 1;}';
1032 $budget = new Budget();
1033 $configuration = new DeriverConfig();
1034 $contributions = new Contributions();
1035 $input = new Sources([new Source('fixture.php', $source)]);
1036 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1037 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1038 $contributions1 = $providers->contributions;
1039 $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);
1040 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1041 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1042 $snapshotId = $identity->id;
1043 $input1 = $providers->input;
1044 $parser = new Parser(new ParserConfig($target->version));
1045 $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));
1046 $program1 = new Index($snapshotId, $trees);
1047 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1048 $snapshotId1 = 'test';
1049 $input2 = new Sources([new Source('fixture.php', $source)]);
1050 $profile = $execution->configuration->target;
1051 $parser1 = new Parser(new ParserConfig($profile->version));
1052 $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));
1053 $program = new Index($snapshotId1, $trees1);
1054 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1055 $body = $context->program->callable('target');
1056 self::assertNotNull($body);
1057 $state = new State();
1058 $value = new Term(TermKind::Object, 'exception', attributes: ['class' => 'RuntimeException']);
1059 $state->registers['exception'] = $value;
1060 $result = (new InstructionTransfer(new Evaluator($context)))->other($body, new BoundInstruction('throw', 'throw', $body->source, 'result', ['exception']), $state);
1061 self::assertSame($value, $result);
1062 self::assertSame($value, $state->completion->value);
1063 self::assertSame(CompletionKind::Throw, $state->completion->kind);
1064 }
1065 public function testPreparationResolvesRegisteredModelStateStorage(): void
1066 {
1067 $source = '<?php function target(){return 1;}';
1068 $budget = new Budget();
1069 $configuration = new DeriverConfig();
1070 $contributions = new Contributions(stateSlots: [new StateSlot('example.slot', 'int')]);
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 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1091 $body = $context->program->callable('target');
1092 self::assertNotNull($body);
1093 $state = new State();
1094 $state->registers['receiver'] = new Term(TermKind::Object, 'external');
1095 $paths = (new InstructionTransfer(new Evaluator($context)))->preparation($body, new BoundInstruction('slot', 'evaluator-state-address', $body->source, 'result', ['receiver'], 'example.slot'), $state);
1096 self::assertNotNull($paths);
1097 self::assertCount(1, $paths);
1098 self::assertSame(TermKind::Location, $paths[0]->registers['result']->kind);
1099 self::assertSame('evaluator:external', $paths[0]->addresses['result']->root);
1100 self::assertSame(['example.slot'], $paths[0]->addresses['result']->path);
1101 self::assertSame(TermKind::StateInput, $paths[0]->memory->read($paths[0]->addresses['result'])->kind);
1102 self::assertSame([], $paths[0]->properties);
1103 }
1104 public function testPreparationAppliesExplicitModelWriteFootprintsBeforeContinuing(): void
1105 {
1106 $source = '<?php function target(){return 1;}';
1107 $budget = new Budget();
1108 $configuration = new DeriverConfig();
1109 $contributions = new Contributions();
1110 $input = new Sources([new Source('fixture.php', $source)]);
1111 $target = new TargetProfile($configuration->phpVersion, $configuration->integerBits, $configuration->floatPrecision);
1112 $providers = new ProviderExpansion($input, array_map(Term::fromNative(...), $configuration->environment), $contributions);
1113 $contributions1 = $providers->contributions;
1114 $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);
1115 $settings = new EvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators, array_map((new MicroEvaluatorConversion())->convert(...), $configuration->evaluators));
1116 $identity = new SnapshotIdentity($providers->input, $settings, $evaluators, $providers->manifest);
1117 $snapshotId = $identity->id;
1118 $input1 = $providers->input;
1119 $parser = new Parser(new ParserConfig($target->version));
1120 $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));
1121 $program1 = new Index($snapshotId, $trees);
1122 $execution = new QueryExecution($program1, new ExecutionEvaluatorConfig($target, $providers->environment, $configuration->standardEvaluators), $evaluators, $identity->snapshot(array_map(Frontier::fromDiagnostic(...), $program1->diagnostics())));
1123 $snapshotId1 = 'test';
1124 $input2 = new Sources([new Source('fixture.php', $source)]);
1125 $profile = $execution->configuration->target;
1126 $parser1 = new Parser(new ParserConfig($profile->version));
1127 $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));
1128 $program = new Index($snapshotId1, $trees1);
1129 $context = new Context($program, new ReturnQuery('target', budget: $budget), $execution->configuration, $execution->evaluators);
1130 $body = $context->program->callable('target');
1131 self::assertNotNull($body);
1132 $state = new State();
1133 $state->addresses['written'] = $state->memory->allocate(Term::constant(7));
1134 $state->memory->write($state->local('unrelated'), Term::constant(9));
1135 $paths = (new InstructionTransfer(new Evaluator($context)))->preparation($body, new BoundInstruction('havoc', 'evaluator-havoc', $body->source, 'result', ['written'], 'external-write'), $state);
1136 self::assertNotNull($paths);
1137 self::assertCount(2, $paths);
1138 self::assertSame('UNSUPPORTED_EVALUATOR_CASE', $paths[0]->registers['result']->literal);
1139 self::assertSame(TermKind::Opaque, $paths[0]->memory->read($paths[0]->addresses['written'])->kind);
1140 self::assertSame(9, $paths[0]->snapshot()['unrelated']->native());
1141 self::assertSame(CompletionKind::Normal, $paths[0]->completion->kind);
1142 self::assertSame(CompletionKind::Throw, $paths[1]->completion->kind);
1143 }
1144}
1145