Skip to content

Commit 43e2892

Browse files
committed
Deal with NULL constructor properly
1 parent eeccadd commit 43e2892

File tree

2 files changed

+90
-12
lines changed

2 files changed

+90
-12
lines changed

Zend/zend_API.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1855,10 +1855,21 @@ ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *c
18551855
}
18561856
zend_object *obj = Z_OBJ_P(arg);
18571857
zend_function *constructor = obj->handlers->get_constructor(obj);
1858-
if (UNEXPECTED(constructor == NULL)) {
1859-
zval_ptr_dtor(arg);
1860-
ZVAL_UNDEF(arg);
1861-
return FAILURE;
1858+
if (constructor == NULL) {
1859+
/* The constructor can be NULL for 2 different reasons:
1860+
* - It is not defined
1861+
* - We are not allowed to call the constructor (e.g. private, or internal opaque class)
1862+
* and an exception has been thrown
1863+
* in the former case, we are done and the object is initialized,
1864+
* in the latter we need to destroy the object as initialization failed
1865+
*/
1866+
if (UNEXPECTED(EG(exception))) {
1867+
zval_ptr_dtor(arg);
1868+
ZVAL_UNDEF(arg);
1869+
return FAILURE;
1870+
} else {
1871+
return SUCCESS;
1872+
}
18621873
}
18631874
/* A constructor should not return a value, however if an exception is thrown
18641875
* zend_call_known_function() will set the retval to IS_UNDEF */

ext/zend_test/tests/zend_object_init_with_constructor.phpt

Lines changed: 75 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ abstract class AbstractClass {
3333
}
3434
}
3535

36-
class TestUser {
36+
class TestUserWithConstructorArgs {
3737
public function __construct(int $int_param, string $string_param) {
3838
return new stdClass();
3939
}
@@ -42,70 +42,121 @@ class TestUser {
4242
}
4343
}
4444

45+
class TestUserWithConstructorNoParams {
46+
public function __construct() {
47+
return new stdClass();
48+
}
49+
public function __destruct() {
50+
echo 'Destructor for ', __CLASS__, PHP_EOL;
51+
}
52+
}
53+
54+
class TestUserWithoutConstructor {
55+
public function __destruct() {
56+
echo 'Destructor for ', __CLASS__, PHP_EOL;
57+
}
58+
}
59+
4560
echo "Testing impossible initializations\n";
4661
try {
4762
$o = zend_object_init_with_constructor("_ZendTestInterface");
4863
var_dump($o);
64+
unset($o);
4965
} catch (\Throwable $e) {
5066
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
5167
}
5268
try {
5369
$o = zend_object_init_with_constructor("_ZendTestTrait");
5470
var_dump($o);
71+
unset($o);
5572
} catch (\Throwable $e) {
5673
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
5774
}
5875
try {
5976
$o = zend_object_init_with_constructor("ZendTestUnitEnum");
6077
var_dump($o);
78+
unset($o);
6179
} catch (\Throwable $e) {
6280
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
6381
}
6482
try {
6583
$o = zend_object_init_with_constructor("AbstractClass");
6684
var_dump($o);
85+
unset($o);
6786
} catch (\Throwable $e) {
6887
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
6988
}
7089
try {
7190
$o = zend_object_init_with_constructor("SysvMessageQueue");
7291
var_dump($o);
92+
unset($o);
7393
} catch (\Throwable $e) {
7494
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
7595
}
7696
try {
7797
$o = zend_object_init_with_constructor("PrivateUser");
7898
var_dump($o);
99+
unset($o);
79100
} catch (\Throwable $e) {
80101
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
81102
}
82103
try {
83104
$o = zend_object_init_with_constructor("ThrowingUser");
84105
var_dump($o);
106+
unset($o);
85107
} catch (\Throwable $e) {
86108
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
87109
}
88110

89111
echo "Testing param passing\n";
90112
try {
91-
$o = zend_object_init_with_constructor("TestUser");
113+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs");
92114
var_dump($o);
115+
unset($o);
93116
} catch (\Throwable $e) {
94117
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
95118
}
96119
try {
97-
$o = zend_object_init_with_constructor("TestUser", "str", 5);
120+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", "str", 5);
98121
var_dump($o);
122+
unset($o);
99123
} catch (\Throwable $e) {
100124
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
101125
}
102126
try {
103-
$o = zend_object_init_with_constructor("TestUser", 5, string_param: "str");
127+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, string_param: "str", unused_param: 15.3);
104128
var_dump($o);
129+
unset($o);
105130
} catch (\Throwable $e) {
106131
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
107132
}
108133

134+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, string_param: "str");
135+
var_dump($o);
136+
unset($o);
137+
138+
echo "Passing too many args to constructor\n";
139+
$o = zend_object_init_with_constructor("TestUserWithConstructorArgs", 5, "str", 'unused_param');
140+
var_dump($o);
141+
unset($o);
142+
143+
echo "Testing class with defined constructor and no params\n";
144+
$o = zend_object_init_with_constructor("TestUserWithConstructorNoParams");
145+
var_dump($o);
146+
unset($o);
147+
148+
echo "Testing class without defined constructor\n";
149+
try {
150+
$o = zend_object_init_with_constructor("TestUserWithoutConstructor", 5, string_param: "str");
151+
var_dump($o);
152+
unset($o);
153+
} catch (\Throwable $e) {
154+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
155+
}
156+
$o = zend_object_init_with_constructor("TestUserWithoutConstructor");
157+
var_dump($o);
158+
unset($o);
159+
109160
?>
110161
--EXPECT--
111162
Testing impossible initializations
@@ -118,8 +169,24 @@ Destructor for PrivateUser
118169
Error: Call to private PrivateUser::__construct() from global scope
119170
Exception: Don't construct
120171
Testing param passing
121-
ArgumentCountError: Too few arguments to function TestUser::__construct(), 0 passed and exactly 2 expected
122-
TypeError: TestUser::__construct(): Argument #1 ($int_param) must be of type int, string given
123-
object(TestUser)#3 (0) {
172+
ArgumentCountError: Too few arguments to function TestUserWithConstructorArgs::__construct(), 0 passed and exactly 2 expected
173+
TypeError: TestUserWithConstructorArgs::__construct(): Argument #1 ($int_param) must be of type int, string given
174+
Error: Unknown named parameter $unused_param
175+
object(TestUserWithConstructorArgs)#1 (0) {
176+
}
177+
Destructor for TestUserWithConstructorArgs
178+
Passing too many args to constructor
179+
object(TestUserWithConstructorArgs)#1 (0) {
180+
}
181+
Destructor for TestUserWithConstructorArgs
182+
Testing class with defined constructor and no params
183+
object(TestUserWithConstructorNoParams)#1 (0) {
184+
}
185+
Destructor for TestUserWithConstructorNoParams
186+
Testing class without defined constructor
187+
object(TestUserWithoutConstructor)#1 (0) {
188+
}
189+
Destructor for TestUserWithoutConstructor
190+
object(TestUserWithoutConstructor)#1 (0) {
124191
}
125-
Destructor for TestUser
192+
Destructor for TestUserWithoutConstructor

0 commit comments

Comments
 (0)