TypePHP 编译器 https://swoole.com/aot/
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

142 lines
5.9 KiB

<?php
namespace TypePhpTest\Transform;
use PhpParser\Node;
use PhpParser\NodeTraverser;
use PhpParser\NodeVisitor\NameResolver;
use PhpParser\ParserFactory;
use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
use RuntimeException;
use TypePhp\Transform\NanoSyntaxValidationVisitor;
final class NanoSyntaxValidationVisitorTest extends TestCase
{
/** @return iterable<string, array{string, string}> */
public static function unsupportedSyntax(): iterable
{
yield 'eval' => ['<?php function run(): void { eval("return 1;"); }', '`eval`'];
yield 'include' => ['<?php function run(): void { include "a.php"; }', '`include`'];
yield 'include once' => ['<?php function run(): void { include_once "a.php"; }', '`include_once`'];
yield 'require' => ['<?php function run(): void { require "a.php"; }', '`require`'];
yield 'require once' => ['<?php function run(): void { require_once "a.php"; }', '`require_once`'];
yield 'backticks' => ['<?php function run(): string { return `uname`; }', 'Backtick shell execution'];
yield 'anonymous class' => ['<?php function run(): object { return new class {}; }', 'Anonymous classes'];
yield 'yield' => ['<?php function values(): iterable { yield 1; }', 'Fiber and Generator'];
yield 'yield from' => ['<?php function values(): iterable { yield from [1]; }', 'Fiber and Generator'];
yield 'generator closure' => [
'<?php function values(): Closure { return function (): iterable { yield 1; }; }',
'Fiber and Generator',
];
yield 'generator arrow function' => [
'<?php function values(): Closure { return fn (): iterable => yield 1; }',
'Fiber and Generator',
];
yield 'Fiber class' => [
'<?php function run(): object { return new Fiber(static function (): void {}); }',
'Class `Fiber` is not supported',
];
yield 'Generator type' => [
'<?php function values(): Generator { throw new RuntimeException(); }',
'Class `Generator` is not supported',
];
yield 'ReflectionGenerator class' => [
'<?php function inspect(object $value): object { return new ReflectionGenerator($value); }',
'Class `ReflectionGenerator` is not supported',
];
}
#[DataProvider('unsupportedSyntax')]
public function testRejectsRuntimeCompiledSyntax(string $source, string $expected): void
{
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse($source);
self::assertNotNull($nodes);
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['replaceNodes' => false]));
$traverser->addVisitor(new NanoSyntaxValidationVisitor(
static function (Node $node, string $message): never {
throw new RuntimeException($message . ':' . $node->getStartLine());
},
));
$this->expectException(RuntimeException::class);
$this->expectExceptionMessage($expected);
$traverser->traverse($nodes);
}
public function testAcceptsNamedClassesAndOrdinaryObjectConstruction(): void
{
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse(
'<?php class Value {} function run(): object { return new Value(); }',
);
self::assertNotNull($nodes);
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['replaceNodes' => false]));
$traverser->addVisitor(new NanoSyntaxValidationVisitor(
static function (Node $node, string $message): never {
throw new RuntimeException($message . ':' . $node->getStartLine());
},
));
self::assertCount(2, $traverser->traverse($nodes));
}
public function testAcceptsNamespacedUserClassNamedFiber(): void
{
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$nodes = $parser->parse(
'<?php namespace App; class Fiber {} function run(): Fiber { return new Fiber(); }',
);
self::assertNotNull($nodes);
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['replaceNodes' => false]));
$traverser->addVisitor(new NanoSyntaxValidationVisitor(
static function (Node $node, string $message): never {
throw new RuntimeException($message . ':' . $node->getStartLine());
},
));
self::assertCount(1, $traverser->traverse($nodes));
}
public function testFullRuntimeNanoPolicyKeepsGeneratorsButRejectsVmEntrySyntax(): void
{
$parser = (new ParserFactory())->createForNewestSupportedVersion();
$accepted = $parser->parse(
'<?php function values(): Generator { yield 1; }',
);
self::assertNotNull($accepted);
$traverser = new NodeTraverser();
$traverser->addVisitor(new NameResolver(null, ['replaceNodes' => false]));
$traverser->addVisitor(new NanoSyntaxValidationVisitor(
static function (Node $node, string $message): never {
throw new RuntimeException($message . ':' . $node->getStartLine());
},
false,
));
self::assertCount(1, $traverser->traverse($accepted));
foreach ([
'<?php eval("return 1;");',
'<?php require "a.php";',
'<?php `uname`;',
'<?php $value = new class {};',
] as $source) {
$nodes = $parser->parse($source);
self::assertNotNull($nodes);
try {
$traverser->traverse($nodes);
self::fail("Nano policy accepted unsupported source: {$source}");
} catch (RuntimeException) {
self::addToAssertionCount(1);
}
}
}
}