Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions phpunit/code/function-import-policy.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<?php
use function exec as SAFE;
function importedPolicyCall(): void { safe('not-executed'); }
27 changes: 27 additions & 0 deletions phpunit/code/function-import-resolution.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
namespace {
function route(): string { return 'global'; }
function size(string $value): int { return 99; }
}
namespace AliasLibrary {
function route(): string { return 'import'; }
function callback_target(): string { return 'import-callback'; }
}
namespace AliasConsumer {
use function AliasLibrary\route as ROUTE;
use function AliasLibrary\route as GET_CALLED_CLASS;
use function strlen as SIZE;
use function AliasLibrary\route as extract;
use function AliasLibrary\{route as GROUPED};
use function AliasLibrary\callback_target as CALLBACK_TARGET;

function exercise(): void {
echo route(), ':', RoUtE(), ':', get_called_class(), ':', sIzE('abc'), ':', grouped(), ':', extract(), ':', \route(), "\n";
$size = sIzE(...);
$callback = cAlLbAcK_tArGeT(...);
$route = rOuTe(...);
$dynamic = $size;
echo $size('abcd'), ':', $callback(), ':', $route(), ':', $dynamic('abc'), "\n";
}
}
namespace { function main(): void { \AliasConsumer\exercise(); } }
32 changes: 32 additions & 0 deletions phpunit/src/FunctionImportResolutionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

use TypePhp\CompilerTest;

final class FunctionImportResolutionTest extends BaseTest
{
public function testImportedFunctionsTakePrecedenceRegardlessOfAliasCase(): void
{
global $translator;
$compiler = CompilerTest::create(TYPEPHP_ROOT_PATH);
$translator = $compiler;
$source = TYPEPHP_ROOT_PATH . '/phpunit/code/function-import-resolution.php';
$compiler->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);
}
}
28 changes: 28 additions & 0 deletions phpunit/src/NanoCapabilityPolicyTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,34 @@ public function forgetBuildTimeFunction(string $name): void

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;
Expand Down
34 changes: 34 additions & 0 deletions tests/compiler/namespace/function-import-resolution.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
--TEST--
Function imports are case-insensitive and take precedence over global functions
--FILE--
<?php
namespace {
function route(): string { return 'global'; }
function size(string $value): int { return 99; }
}
namespace AliasLibrary {
function route(): string { return 'import'; }
function callback_target(): string { return 'import-callback'; }
}
namespace AliasConsumer {
use function AliasLibrary\route as ROUTE;
use function AliasLibrary\route as GET_CALLED_CLASS;
use function strlen as SIZE;
use function AliasLibrary\route as extract;
use function AliasLibrary\{route as GROUPED};
use function AliasLibrary\callback_target as CALLBACK_TARGET;

function exercise(): void {
echo route(), ':', RoUtE(), ':', get_called_class(), ':', sIzE('abc'), ':', grouped(), ':', extract(), ':', \route(), "\n";
$size = sIzE(...);
$callback = cAlLbAcK_tArGeT(...);
$route = rOuTe(...);
$dynamic = $size;
echo $size('abcd'), ':', $callback(), ':', $route(), ':', $dynamic('abc'), "\n";
}
}
namespace { function main(): void { \AliasConsumer\exercise(); } }
?>
--EXPECT--
import:import:import:3:import:import:global
4:import-callback:import:3
Loading