diff --git a/phpunit/code/function-import-policy.php b/phpunit/code/function-import-policy.php new file mode 100644 index 00000000..7fd09b24 --- /dev/null +++ b/phpunit/code/function-import-policy.php @@ -0,0 +1,3 @@ +addFiles([$source]); + $compiler->prepareFile($source); + $code = file_get_contents($compiler->convertFile($source)); + preg_match('/void php_aliasconsumer__exercise\(\) \{(.*?)\n\}/s', $code, $match); + self::assertCount(2, $match); + self::assertSame(5, substr_count($match[1], 'php_aliaslibrary__route(')); + self::assertSame(1, substr_count($match[1], 'php_route(')); + // The three first-class callables are materialized through + // Closure::fromCallable(); ordinary aliases above must stay direct. + self::assertSame(3, substr_count($match[1], 'php::call(')); + self::assertStringNotContainsString('ZEND_STRL("sIzE")', $match[1]); + self::assertStringNotContainsString('ZEND_STRL("cAlLbAcK_tArGeT")', $match[1]); + + $literalStrings = (new \ReflectionProperty($compiler, 'literalStrings'))->getValue($compiler); + self::assertArrayHasKey('strlen', $literalStrings); + self::assertArrayHasKey('AliasLibrary\\callback_target', $literalStrings); + self::assertArrayNotHasKey('sIzE', $literalStrings); + self::assertArrayNotHasKey('cAlLbAcK_tArGeT', $literalStrings); + } +} diff --git a/phpunit/src/NanoCapabilityPolicyTest.php b/phpunit/src/NanoCapabilityPolicyTest.php index 3659c5d2..7ac4ca4f 100644 --- a/phpunit/src/NanoCapabilityPolicyTest.php +++ b/phpunit/src/NanoCapabilityPolicyTest.php @@ -49,6 +49,34 @@ final class NanoCapabilityPolicyCompiler extends CompilerTest final class NanoCapabilityPolicyTest extends BaseTest { + public function testFunctionImportCannotBypassNanoPolicy(): void + { + $this->assertImportedFunctionRejected(false); + } + + public function testFunctionImportCannotBypassWasiPolicy(): void + { + $this->assertImportedFunctionRejected(true); + } + + private function assertImportedFunctionRejected(bool $wasi): void + { + global $translator; + $compiler = new NanoCapabilityPolicyCompiler(TYPEPHP_ROOT_PATH); + if ($wasi) { + $compiler->enableWasiForTest(); + } else { + $compiler->enableNanoForTest(); + } + $translator = $compiler; + $source = __DIR__ . '/../code/function-import-policy.php'; + $compiler->addFiles([$source]); + $compiler->prepareFile($source); + $this->expectException(TestError::class); + $this->expectExceptionMessage('Function `exec` is not supported'); + $compiler->convertFile($source); + } + public function testRejectsForbiddenDirectCallMissingFromBuildTimePhp(): void { global $translator; diff --git a/tests/compiler/namespace/function-import-resolution.phpt b/tests/compiler/namespace/function-import-resolution.phpt new file mode 100644 index 00000000..10b7d76e --- /dev/null +++ b/tests/compiler/namespace/function-import-resolution.phpt @@ -0,0 +1,34 @@ +--TEST-- +Function imports are case-insensitive and take precedence over global functions +--FILE-- + +--EXPECT-- +import:import:import:3:import:import:global +4:import-callback:import:3