Skip to content

Commit 73c3cb7

Browse files
committed
Fix behaviour with new for internal and userland classes
Turns out the logic is really baffling
1 parent 43e2892 commit 73c3cb7

File tree

3 files changed

+168
-28
lines changed

3 files changed

+168
-28
lines changed

Zend/zend_API.c

+19-2
Original file line numberDiff line numberDiff line change
@@ -1860,15 +1860,31 @@ ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *c
18601860
* - It is not defined
18611861
* - We are not allowed to call the constructor (e.g. private, or internal opaque class)
18621862
* and an exception has been thrown
1863-
* in the former case, we are done and the object is initialized,
1863+
* in the former case, we are (mostly) done and the object is initialized,
18641864
* in the latter we need to destroy the object as initialization failed
18651865
*/
18661866
if (UNEXPECTED(EG(exception))) {
18671867
zval_ptr_dtor(arg);
18681868
ZVAL_UNDEF(arg);
18691869
return FAILURE;
18701870
} else {
1871-
return SUCCESS;
1871+
/* Surprisingly, this is the only case where internal classes will allow to pass extra arguments
1872+
* However, if there are named arguments, an Error must be thrown to be consistent with new ClassName() */
1873+
if (UNEXPECTED(named_params != NULL)) {
1874+
/* Throw standard Error */
1875+
zend_string *arg_name = NULL;
1876+
zend_hash_get_current_key(named_params, &arg_name, /* num_index */ NULL);
1877+
ZEND_ASSERT(arg_name != NULL);
1878+
zend_throw_error(NULL, "Unknown named parameter $%s", ZSTR_VAL(arg_name));
1879+
zend_string_release(arg_name);
1880+
/* Do not call destructor, free object, and set arg to IS_UNDEF */
1881+
zend_object_store_ctor_failed(obj);
1882+
zval_ptr_dtor(arg);
1883+
ZVAL_UNDEF(arg);
1884+
return FAILURE;
1885+
} else {
1886+
return SUCCESS;
1887+
}
18721888
}
18731889
}
18741890
/* A constructor should not return a value, however if an exception is thrown
@@ -1884,6 +1900,7 @@ ZEND_API zend_result object_init_with_constructor(zval *arg, zend_class_entry *c
18841900
named_params
18851901
);
18861902
if (Z_TYPE(retval) == IS_UNDEF) {
1903+
/* Do not call destructor, free object, and set arg to IS_UNDEF */
18871904
zend_object_store_ctor_failed(obj);
18881905
zval_ptr_dtor(arg);
18891906
ZVAL_UNDEF(arg);

ext/zend_test/tests/zend_object_init_with_constructor.phpt

-26
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,6 @@ class TestUserWithConstructorNoParams {
5151
}
5252
}
5353

54-
class TestUserWithoutConstructor {
55-
public function __destruct() {
56-
echo 'Destructor for ', __CLASS__, PHP_EOL;
57-
}
58-
}
59-
6054
echo "Testing impossible initializations\n";
6155
try {
6256
$o = zend_object_init_with_constructor("_ZendTestInterface");
@@ -144,19 +138,6 @@ echo "Testing class with defined constructor and no params\n";
144138
$o = zend_object_init_with_constructor("TestUserWithConstructorNoParams");
145139
var_dump($o);
146140
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-
160141
?>
161142
--EXPECT--
162143
Testing impossible initializations
@@ -183,10 +164,3 @@ Testing class with defined constructor and no params
183164
object(TestUserWithConstructorNoParams)#1 (0) {
184165
}
185166
Destructor for TestUserWithConstructorNoParams
186-
Testing class without defined constructor
187-
object(TestUserWithoutConstructor)#1 (0) {
188-
}
189-
Destructor for TestUserWithoutConstructor
190-
object(TestUserWithoutConstructor)#1 (0) {
191-
}
192-
Destructor for TestUserWithoutConstructor
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
--TEST--
2+
Zend: Test object_init_with_constructor() API for objects without constructors
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
class TestUserWithoutConstructor {
9+
public function __destruct() {
10+
echo 'Destructor for ', __CLASS__, PHP_EOL;
11+
}
12+
}
13+
14+
echo "#### Passing no args ####\n";
15+
echo "Userland class:\n";
16+
echo "Using new:\n";
17+
$o = new TestUserWithoutConstructor();
18+
var_dump($o);
19+
unset($o);
20+
echo "Using zend_object_init_with_constructor():\n";
21+
$o = zend_object_init_with_constructor("TestUserWithoutConstructor");
22+
var_dump($o);
23+
unset($o);
24+
25+
echo "Internal class:\n";
26+
echo "Using new:\n";
27+
$o = new _ZendTestMagicCall();
28+
var_dump($o);
29+
unset($o);
30+
echo "Using zend_object_init_with_constructor():\n";
31+
$o = zend_object_init_with_constructor("_ZendTestMagicCall");
32+
var_dump($o);
33+
unset($o);
34+
35+
echo "\n#### Passing extra positional args ####\n";
36+
echo "Userland class:\n";
37+
echo "Using new:\n";
38+
$o = new TestUserWithoutConstructor('position_arg');
39+
var_dump($o);
40+
unset($o);
41+
echo "Using zend_object_init_with_constructor():\n";
42+
$o = zend_object_init_with_constructor("TestUserWithoutConstructor", 'position_arg');
43+
var_dump($o);
44+
unset($o);
45+
46+
echo "Internal class:\n";
47+
echo "Using new:\n";
48+
try {
49+
$o = new _ZendTestMagicCall('position_arg');
50+
var_dump($o);
51+
unset($o);
52+
} catch (\Throwable $e) {
53+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
54+
}
55+
echo "Using zend_object_init_with_constructor():\n";
56+
try {
57+
$o = zend_object_init_with_constructor("_ZendTestMagicCall", 'position_arg');
58+
var_dump($o);
59+
unset($o);
60+
} catch (\Throwable $e) {
61+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
62+
}
63+
64+
echo "\n#### Passing extra named args ####\n";
65+
echo "Userland class:\n";
66+
echo "Using new:\n";
67+
try {
68+
$o = new TestUserWithoutConstructor(unknown_param: 'named_arg');
69+
var_dump($o);
70+
unset($o);
71+
} catch (\Throwable $e) {
72+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
73+
}
74+
echo "Using zend_object_init_with_constructor():\n";
75+
try {
76+
$o = zend_object_init_with_constructor("TestUserWithoutConstructor", unknown_param: 'named_arg');
77+
var_dump($o);
78+
unset($o);
79+
} catch (\Throwable $e) {
80+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
81+
}
82+
83+
echo "Internal class:\n";
84+
echo "Using new:\n";
85+
try {
86+
$o = new _ZendTestMagicCall(unknown_param: 'named_arg');
87+
var_dump($o);
88+
unset($o);
89+
} catch (\Throwable $e) {
90+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
91+
}
92+
echo "Using zend_object_init_with_constructor():\n";
93+
try {
94+
$o = zend_object_init_with_constructor("_ZendTestMagicCall", unknown_param: 'named_arg');
95+
var_dump($o);
96+
unset($o);
97+
} catch (\Throwable $e) {
98+
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
99+
}
100+
101+
?>
102+
--EXPECT--
103+
#### Passing no args ####
104+
Userland class:
105+
Using new:
106+
object(TestUserWithoutConstructor)#1 (0) {
107+
}
108+
Destructor for TestUserWithoutConstructor
109+
Using zend_object_init_with_constructor():
110+
object(TestUserWithoutConstructor)#1 (0) {
111+
}
112+
Destructor for TestUserWithoutConstructor
113+
Internal class:
114+
Using new:
115+
object(_ZendTestMagicCall)#1 (0) {
116+
}
117+
Using zend_object_init_with_constructor():
118+
object(_ZendTestMagicCall)#1 (0) {
119+
}
120+
121+
#### Passing extra positional args ####
122+
Userland class:
123+
Using new:
124+
object(TestUserWithoutConstructor)#1 (0) {
125+
}
126+
Destructor for TestUserWithoutConstructor
127+
Using zend_object_init_with_constructor():
128+
object(TestUserWithoutConstructor)#1 (0) {
129+
}
130+
Destructor for TestUserWithoutConstructor
131+
Internal class:
132+
Using new:
133+
object(_ZendTestMagicCall)#1 (0) {
134+
}
135+
Using zend_object_init_with_constructor():
136+
object(_ZendTestMagicCall)#1 (0) {
137+
}
138+
139+
#### Passing extra named args ####
140+
Userland class:
141+
Using new:
142+
Error: Unknown named parameter $unknown_param
143+
Using zend_object_init_with_constructor():
144+
Error: Unknown named parameter $unknown_param
145+
Internal class:
146+
Using new:
147+
Error: Unknown named parameter $unknown_param
148+
Using zend_object_init_with_constructor():
149+
Error: Unknown named parameter $unknown_param

0 commit comments

Comments
 (0)