fix(compiler): 检查构造函数可见性以防止非法调用 #28
Merged
韩天峰
merged 2 commits from compiler-fix-ctor-visibility-check into master 1 month ago
16 changed files with 445 additions and 7 deletions
@ -0,0 +1,17 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class PrivateConstructorParent |
||||||
|
{ |
||||||
|
private function __construct() |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class PrivateConstructorChild extends PrivateConstructorParent |
||||||
|
{ |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new PrivateConstructorChild(); |
||||||
|
} |
||||||
@ -0,0 +1,6 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new Closure(); |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
namespace ConstructorVisibility; |
||||||
|
|
||||||
|
class Hidden |
||||||
|
{ |
||||||
|
private function __construct() |
||||||
|
{ |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new Hidden(); |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
private function __construct(){} |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass; |
||||||
|
} |
||||||
@ -0,0 +1,11 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
protected function __construct(){} |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass; |
||||||
|
} |
||||||
@ -0,0 +1,19 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
class Base |
||||||
|
{ |
||||||
|
protected function __construct(){} |
||||||
|
} |
||||||
|
|
||||||
|
class Other |
||||||
|
{ |
||||||
|
public static function make(): Base |
||||||
|
{ |
||||||
|
return new Base(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
Other::make(); |
||||||
|
} |
||||||
@ -0,0 +1,29 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
trait TraitA |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
echo "A\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
trait TraitB |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
echo "B\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
use TraitA, TraitB; |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass(); |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
trait TestTrait |
||||||
|
{ |
||||||
|
private function __construct() |
||||||
|
{ |
||||||
|
echo "trait ctor\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
use TestTrait; |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass(); |
||||||
|
} |
||||||
@ -0,0 +1,21 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
trait TestTrait |
||||||
|
{ |
||||||
|
protected function __construct() |
||||||
|
{ |
||||||
|
echo "trait ctor\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
use TestTrait; |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass(); |
||||||
|
} |
||||||
@ -0,0 +1,78 @@ |
|||||||
|
<?php |
||||||
|
|
||||||
|
use TypePhp\Exception\TestError; |
||||||
|
|
||||||
|
class ConstructorVisibilityTest extends BaseTest |
||||||
|
{ |
||||||
|
/** |
||||||
|
* 编译期错误在转换阶段直接以 TestError 抛出,而在桩文件生成阶段 |
||||||
|
* (gen_stub.php) 会被包成 RuntimeException,这里两者都要捕获。 |
||||||
|
*/ |
||||||
|
protected function exec(string $expected, string $file): void |
||||||
|
{ |
||||||
|
try { |
||||||
|
$this->compile($file); |
||||||
|
} catch (TestError|RuntimeException $exception) { |
||||||
|
$this->assertStringContainsString($expected, $exception->getMessage()); |
||||||
|
return; |
||||||
|
} |
||||||
|
$this->fail('Expected compile-time error was not thrown'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testPrivateConstructorCannotBeCalledFromOutside(): void |
||||||
|
{ |
||||||
|
// 私有构造器不能从类外部通过 `new` 调用 |
||||||
|
$this->exec('Cannot call private TestClass::__construct()', 'constructor_visibility_private.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testProtectedConstructorCannotBeCalledFromGlobalScope(): void |
||||||
|
{ |
||||||
|
// 保护构造器不能从全局作用域调用 |
||||||
|
$this->exec('Cannot call protected TestClass::__construct()', 'constructor_visibility_protected.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testProtectedConstructorCannotBeCalledFromNonSubclass(): void |
||||||
|
{ |
||||||
|
// 保护构造器不能从非子类的其它类内部调用 |
||||||
|
$this->exec('Cannot call protected Base::__construct()', 'constructor_visibility_protected_foreign_class.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testInheritedPrivateConstructorCannotBeCalledFromChildScope(): void |
||||||
|
{ |
||||||
|
$this->exec( |
||||||
|
'Cannot call private PrivateConstructorParent::__construct()', |
||||||
|
'constructor_visibility_inherited_private.php' |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public function testInternalPrivateConstructorCannotBeCalled(): void |
||||||
|
{ |
||||||
|
$this->exec('Cannot call private Closure::__construct()', 'constructor_visibility_internal_private.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testNamespacedConstructorUsesPhpClassNameInDiagnostic(): void |
||||||
|
{ |
||||||
|
$this->exec( |
||||||
|
'Cannot call private ConstructorVisibility\Hidden::__construct()', |
||||||
|
'constructor_visibility_namespaced.php' |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTraitPrivateConstructorCannotBeCalledFromGlobalScope(): void |
||||||
|
{ |
||||||
|
// trait 提供的私有构造器扁平化后等价于类的私有构造器 |
||||||
|
$this->exec('Cannot call private TestClass::__construct()', 'trait_constructor_private.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testTraitProtectedConstructorCannotBeCalledFromGlobalScope(): void |
||||||
|
{ |
||||||
|
// trait 提供的保护构造器扁平化后等价于类的保护构造器 |
||||||
|
$this->exec('Cannot call protected TestClass::__construct()', 'trait_constructor_protected.php'); |
||||||
|
} |
||||||
|
|
||||||
|
public function testConflictingTraitConstructorMustBeResolved(): void |
||||||
|
{ |
||||||
|
// 两个 trait 各自声明 __construct 时必须显式解决冲突 |
||||||
|
$this->exec('Trait `TraitB` method `__construct` already exists', 'trait_constructor_conflict.php'); |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,26 @@ |
|||||||
|
--TEST-- |
||||||
|
Constructor visibility - protected constructor accessible from subclass |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
class Base |
||||||
|
{ |
||||||
|
protected function __construct(){} |
||||||
|
} |
||||||
|
|
||||||
|
class Sub extends Base |
||||||
|
{ |
||||||
|
public static function make(): Base |
||||||
|
{ |
||||||
|
return new Base(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
$obj = Sub::make(); |
||||||
|
var_dump($obj instanceof Base); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
bool(true) |
||||||
@ -0,0 +1,27 @@ |
|||||||
|
--TEST-- |
||||||
|
Trait __construct is used by the composing class |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
trait TestTrait |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
echo "trait ctor\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
use TestTrait; |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass(); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
trait ctor |
||||||
@ -0,0 +1,32 @@ |
|||||||
|
--TEST-- |
||||||
|
Class __construct overrides the one provided by a trait |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
trait TestTrait |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
echo "trait ctor\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
use TestTrait; |
||||||
|
|
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
echo "class ctor\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass(); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
class ctor |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
--TEST-- |
||||||
|
Trait protected __construct is accessible from a subclass |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
trait TestTrait |
||||||
|
{ |
||||||
|
protected function __construct() |
||||||
|
{ |
||||||
|
echo "base ctor\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class BaseClass |
||||||
|
{ |
||||||
|
use TestTrait; |
||||||
|
} |
||||||
|
|
||||||
|
class SubClass extends BaseClass |
||||||
|
{ |
||||||
|
public function __construct() |
||||||
|
{ |
||||||
|
new BaseClass(); |
||||||
|
echo "sub ctor\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new SubClass(); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
base ctor |
||||||
|
sub ctor |
||||||
@ -0,0 +1,30 @@ |
|||||||
|
--TEST-- |
||||||
|
Trait __construct with arguments and $this property access |
||||||
|
--FILE-- |
||||||
|
<?php |
||||||
|
|
||||||
|
declare(strict_types=1); |
||||||
|
|
||||||
|
trait TestTrait |
||||||
|
{ |
||||||
|
private int $value = 0; |
||||||
|
|
||||||
|
public function __construct(int $value) |
||||||
|
{ |
||||||
|
$this->value = $value; |
||||||
|
echo "value=" . $this->value . "\n"; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
class TestClass |
||||||
|
{ |
||||||
|
use TestTrait; |
||||||
|
} |
||||||
|
|
||||||
|
function main() |
||||||
|
{ |
||||||
|
new TestClass(42); |
||||||
|
} |
||||||
|
?> |
||||||
|
--EXPECT-- |
||||||
|
value=42 |
||||||
Loading…
Reference in new issue