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.
 
 

120 lines
5.4 KiB

<?php
use TypePhp\CompilerBase;
use TypePhp\CompilerTest;
/**
* Lifecycle contract for persistent enum-case AST class constants: Zend's
* internal-class teardown (destroy_zend_class) tolerates them only after the
* module's MSHUTDOWN released them. The generated module must therefore
* (a) refuse to start as a MODULE_TEMPORARY (dl()-loaded) module before any
* class is registered, because module_destructor() destroys temporary-module
* classes before the shutdown callback runs, and (b) order every fallible
* MINIT step before the first class registration, so a FAILURE return (which
* suppresses MSHUTDOWN) can never leave a foreign AST in the class table.
*/
final class EnumCaseAstConstantLifecycleTest extends \BaseTest
{
public function testModuleTemporaryGuardPrecedesEveryRegistrationStep(): void
{
$minit = $this->generateMinitBody('enum-case-class-constant.php', 'ast_lifecycle_guard');
$guardPos = strpos($minit, 'if (type == MODULE_TEMPORARY) {');
self::assertIsInt($guardPos, 'MINIT must reject dl()-loaded temporary modules');
self::assertStringContainsString(
'registers enum-case class constants that must be released by MSHUTDOWN',
$minit,
);
self::assertStringContainsString('Load the extension from php.ini instead.', $minit);
$handlersPos = strpos($minit, 'typephp_install_reflection_attribute_handlers()');
$firstRegisterPos = strpos($minit, 'register_class_');
self::assertIsInt($handlersPos);
self::assertIsInt($firstRegisterPos);
self::assertLessThan($handlersPos, $guardPos, 'the lifecycle guard must be the first MINIT statement');
self::assertLessThan($firstRegisterPos, $guardPos, 'the lifecycle guard must precede every class registration');
}
public function testAstConstantRegistrationIsOrderedAfterEveryFallibleMinitStep(): void
{
$minit = $this->generateMinitBody('enum-case-class-constant.php', 'ast_lifecycle_order');
$firstRegisterPos = strpos($minit, 'register_class_');
$lastFailurePos = strrpos($minit, 'return FAILURE;');
self::assertIsInt($firstRegisterPos);
self::assertIsInt($lastFailurePos);
self::assertLessThan(
$firstRegisterPos,
$lastFailurePos,
'no MINIT step after the first class registration may return FAILURE: '
. 'a failed MINIT never reaches MSHUTDOWN, so the persistent class '
. 'table would keep an AST that destroy_zend_class() cannot handle',
);
self::assertGreaterThan(
strrpos($minit, 'register_class_'),
strpos($minit, 'return SUCCESS;'),
);
}
public function testMshutdownReleasesAstConstantsBeforeAnyOtherTeardown(): void
{
$extension = $this->generateExtension('enum-case-class-constant.php', 'ast_lifecycle_shutdown');
$mshutdown = $this->sliceFunction($extension, 'PHP_MSHUTDOWN_FUNCTION', 'THREAD_LOCAL zval globals_array');
$releasePos = strpos($mshutdown, 'typephp_release_ast_constants_enum_case_class_constant();');
self::assertIsInt($releasePos, 'MSHUTDOWN must release the persistent AST constants');
// The release must run before anything else so the class table is
// Zend-safe no matter what the rest of the teardown does.
$firstStatementPos = strpos($mshutdown, ';');
self::assertSame($firstStatementPos, $releasePos + strlen('typephp_release_ast_constants_enum_case_class_constant();') - 1);
}
public function testModulesWithoutAstConstantsCarryNeitherGuardNorRelease(): void
{
$extension = $this->generateExtension('class-constant-codegen.php', 'ast_lifecycle_none');
self::assertStringNotContainsString('MODULE_TEMPORARY', $extension);
self::assertStringNotContainsString('typephp_release_ast_constants_', $extension);
}
private function generateMinitBody(string $fixture, string $target): string
{
return $this->sliceFunction(
$this->generateExtension($fixture, $target),
'PHP_MINIT_FUNCTION',
'PHP_MSHUTDOWN_FUNCTION',
);
}
private function generateExtension(string $fixture, string $target): string
{
global $translator;
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$compiler->setBuildMode(CompilerBase::BUILD_MODE_EXT);
$compiler->setTargetName($target);
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/' . $fixture;
$compiler->addFiles([$source]);
$compiler->prepareFile($source);
$compiler->convertFile($source);
$extension = file_get_contents($compiler->genExtension());
self::assertIsString($extension);
return $extension;
}
/**
* The generated function bodies keep statements at column zero, so the
* closing brace is not recognizable; slice up to the next known emission
* instead.
*/
private function sliceFunction(string $extension, string $startMarker, string $endMarker): string
{
$start = strpos($extension, $startMarker);
self::assertIsInt($start, "generated extension must contain {$startMarker}");
$end = strpos($extension, $endMarker, $start + strlen($startMarker));
self::assertIsInt($end, "generated extension must contain {$endMarker}");
return substr($extension, $start, $end - $start);
}
}