Skip to content

Commit b378815

Browse files
committed
Added tests for static class feature.
Added compile-time check to enforce all methods must be declared static in a static class. Added compile-time mutual-exclusivity check for static classes marked with abstract or readonly modifiers. Added compile-time check to preclude static class inheriting from non-static class and vice-versa. Added compile-time check to preclude static class using a trait with non-static properties or methods. Fixed ZEND_ACC_IMPLICIT_ABSTRACT_CLASS collision with ZEND_ACC_STATIC.
1 parent 5274b65 commit b378815

21 files changed

+251
-5
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Tests that a non-static class cannot inherit from a static class
3+
--FILE--
4+
<?php
5+
6+
static class C {}
7+
8+
class C2 extends C {}
9+
?>
10+
--EXPECTF--
11+
Fatal error: Non-static class C2 cannot extend static class C in %s on line %d
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Tests that a static class cannot inherit from a non-static class
3+
--FILE--
4+
<?php
5+
6+
class C {}
7+
8+
static class C2 extends C {}
9+
?>
10+
--EXPECTF--
11+
Fatal error: Static class C2 cannot extend non-static class C in %s on line %d
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
--TEST--
2+
Tests that a static class can inherit from another static class
3+
--FILE--
4+
<?php
5+
6+
static class C {}
7+
8+
static class C2 extends C {}
9+
?>
10+
--EXPECT--
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
Tests that a static class can inherit static members from a trait
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
static $P = 'OK';
8+
9+
static function F() {
10+
echo self::$P;
11+
}
12+
}
13+
14+
static class C {
15+
use T;
16+
}
17+
18+
C::F();
19+
?>
20+
--EXPECT--
21+
OK
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Tests that a static class cannot inherit an instance method from a trait
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
function F() {}
8+
}
9+
10+
static class C {
11+
use T;
12+
}
13+
?>
14+
--EXPECTF--
15+
Fatal error: Static class C cannot use trait with a non-static method T::F in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
Tests that a static class cannot inherit an instance property from a trait
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
var $V;
8+
}
9+
10+
static class C {
11+
use T;
12+
}
13+
?>
14+
--EXPECTF--
15+
Fatal error: Static class C cannot use trait with a non-static property T::$V in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Tests that a static class cannot be instantiated
3+
--FILE--
4+
<?php
5+
6+
static class C {}
7+
8+
new C;
9+
?>
10+
--EXPECTF--
11+
Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d
12+
Stack trace:%a
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Tests that a static class cannot be instantiated via reflection
3+
--FILE--
4+
<?php
5+
6+
static class C {}
7+
8+
new ReflectionClass('C')->newInstance();
9+
?>
10+
--EXPECTF--
11+
Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d
12+
Stack trace:%a
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Tests that a static class cannot be marked abstract
3+
--FILE--
4+
<?php
5+
6+
abstract static class C {}
7+
?>
8+
--EXPECTF--
9+
Fatal error: Cannot use the static modifier on an abstract class in %s on line %d
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Tests that an anonymous class cannot be marked static
3+
--FILE--
4+
<?php
5+
6+
new static class C {}
7+
?>
8+
--EXPECTF--
9+
Fatal error: Cannot use the static modifier on an anonymous class in %s on line %d
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
Tests that a class can be marked static and functions as a static class
3+
--FILE--
4+
<?php
5+
6+
static class C {
7+
static $P = 'OK';
8+
9+
static function F() {
10+
echo self::$P;
11+
}
12+
}
13+
14+
C::F();
15+
?>
16+
--EXPECT--
17+
OK
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
--TEST--
2+
Tests that a static class cannot contain an instance method
3+
--FILE--
4+
<?php
5+
6+
static class C {
7+
function F() {}
8+
}
9+
?>
10+
--EXPECTF--
11+
Fatal error: Class method C::F() must be declared static in a static class in %s on line %d
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
--TEST--
2+
Tests that a static class can be marked final
3+
--FILE--
4+
<?php
5+
6+
final static class C {}
7+
?>
8+
--EXPECT--
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Tests that an interface cannot be declared static
3+
--FILE--
4+
<?php
5+
6+
static interface I {}
7+
?>
8+
--EXPECTF--
9+
Parse error: syntax error, unexpected token "interface" in %s on line %d
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Tests that a static class cannot be marked readonly
3+
--FILE--
4+
<?php
5+
6+
static readonly class C {}
7+
?>
8+
--EXPECTF--
9+
Fatal error: Cannot use the static modifier on a readonly class in %s on line %d
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Tests that a static class cannot be so marked more than once
3+
--FILE--
4+
<?php
5+
6+
static static class C {}
7+
?>
8+
--EXPECTF--
9+
Fatal error: Multiple static modifiers are not allowed in %s on line %d
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
--TEST--
2+
Tests that a trait cannot be declared static
3+
--FILE--
4+
<?php
5+
6+
static trait T {}
7+
?>
8+
--EXPECTF--
9+
Parse error: syntax error, unexpected token "trait" in %s on line %d
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
--TEST--
2+
Tests that a static class cannot be created via unserialize()
3+
--FILE--
4+
<?php
5+
6+
static class C {}
7+
8+
unserialize('O:1:"C":0:{}');
9+
?>
10+
--EXPECTF--
11+
Fatal error: Uncaught Error: Cannot instantiate static class C in %s:%d
12+
Stack trace:%a

Zend/zend_compile.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -931,7 +931,6 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */
931931
"Multiple abstract modifiers are not allowed", 0);
932932
return 0;
933933
}
934-
935934
if ((flags & ZEND_ACC_FINAL) && (new_flag & ZEND_ACC_FINAL)) {
936935
zend_throw_exception(zend_ce_compile_error, "Multiple final modifiers are not allowed", 0);
937936
return 0;
@@ -945,7 +944,17 @@ uint32_t zend_add_class_modifier(uint32_t flags, uint32_t new_flag) /* {{{ */
945944
"Cannot use the final modifier on an abstract class", 0);
946945
return 0;
947946
}
948-
if ((flags & ZEND_ACC_STATIC) && (new_flag & ZEND_ACC_STATIC)) {
947+
if (new_flags & ZEND_ACC_STATIC) {
948+
if (new_flags & ZEND_ACC_EXPLICIT_ABSTRACT_CLASS) {
949+
zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on an abstract class", 0);
950+
return 0;
951+
}
952+
if (new_flags & ZEND_ACC_READONLY_CLASS) {
953+
zend_throw_exception(zend_ce_compile_error, "Cannot use the static modifier on a readonly class", 0);
954+
return 0;
955+
}
956+
}
957+
if ((flags & ZEND_ACC_STATIC) && (new_flag & ZEND_ACC_STATIC)) {
949958
zend_throw_exception(zend_ce_compile_error,
950959
"Multiple static modifiers are not allowed", 0);
951960
return 0;
@@ -7849,6 +7858,11 @@ static zend_string *zend_begin_method_decl(zend_op_array *op_array, zend_string
78497858
zend_error(E_COMPILE_WARNING, "Private methods cannot be final as they are never overridden by other classes");
78507859
}
78517860

7861+
if (ce->ce_flags & ZEND_ACC_STATIC && !(fn_flags & ZEND_ACC_STATIC)) {
7862+
zend_error(E_COMPILE_ERROR, "Class method %s::%s() must be declared static in a static class",
7863+
ZSTR_VAL(ce->name), ZSTR_VAL(name));
7864+
}
7865+
78527866
if (in_interface) {
78537867
if (!(fn_flags & ZEND_ACC_PUBLIC)) {
78547868
zend_error_noreturn(E_COMPILE_ERROR, "Access type for interface method "

Zend/zend_compile.h

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ typedef struct _zend_oparray_context {
218218
#define ZEND_ACC_CHANGED (1 << 3) /* | X | X | */
219219
/* | | | */
220220
/* Static method or property | | | */
221-
#define ZEND_ACC_STATIC (1 << 4) /* | X | X | */
221+
#define ZEND_ACC_STATIC (1 << 4) /* X | X | X | */
222222
/* | | | */
223223
/* Promoted property / parameter | | | */
224224
#define ZEND_ACC_PROMOTED (1 << 5) /* | | X | X */
@@ -251,7 +251,7 @@ typedef struct _zend_oparray_context {
251251
/* or IS_CONSTANT_VISITED_MARK | | | */
252252
#define ZEND_CLASS_CONST_IS_CASE (1 << 6) /* | | | X */
253253
/* | | | */
254-
/* Class Flags (unused: 30,31) | | | */
254+
/* Class Flags (unused: 31) | | | */
255255
/* =========== | | | */
256256
/* | | | */
257257
/* Special class types | | | */
@@ -265,7 +265,7 @@ typedef struct _zend_oparray_context {
265265
/* | | | */
266266
/* Class is abstract, since it is set by any | | | */
267267
/* abstract method | | | */
268-
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS (1 << 4) /* X | | | */
268+
#define ZEND_ACC_IMPLICIT_ABSTRACT_CLASS (1 << 30) /* X | | | */
269269
/* | | | */
270270
/* Class has magic methods __get/__set/__unset/ | | | */
271271
/* __isset that use guards | | | */

Zend/zend_inheritance.c

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1508,6 +1508,13 @@ ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *par
15081508
);
15091509
}
15101510

1511+
if (UNEXPECTED((ce->ce_flags & ZEND_ACC_STATIC) != (parent_ce->ce_flags & ZEND_ACC_STATIC))) {
1512+
zend_error_noreturn(E_COMPILE_ERROR, "%s class %s cannot extend %s class %s",
1513+
ce->ce_flags & ZEND_ACC_STATIC ? "Static" : "Non-static", ZSTR_VAL(ce->name),
1514+
parent_ce->ce_flags & ZEND_ACC_STATIC ? "static" : "non-static", ZSTR_VAL(parent_ce->name)
1515+
);
1516+
}
1517+
15111518
if (ce->parent_name) {
15121519
zend_string_release_ex(ce->parent_name, 0);
15131520
}
@@ -2001,6 +2008,14 @@ static void zend_add_trait_method(zend_class_entry *ce, zend_string *name, zend_
20012008
}
20022009
}
20032010

2011+
if (ce->ce_flags & ZEND_ACC_STATIC && !(fn->common.fn_flags & ZEND_ACC_STATIC)) {
2012+
zend_error_noreturn(E_COMPILE_ERROR,
2013+
"Static class %s cannot use trait with a non-static method %s::%s",
2014+
ZSTR_VAL(ce->name),
2015+
ZSTR_VAL(fn->common.scope->name), ZSTR_VAL(fn->common.function_name)
2016+
);
2017+
}
2018+
20042019
if (UNEXPECTED(fn->type == ZEND_INTERNAL_FUNCTION)) {
20052020
new_fn = zend_arena_alloc(&CG(arena), sizeof(zend_internal_function));
20062021
memcpy(new_fn, fn, sizeof(zend_internal_function));
@@ -2552,6 +2567,14 @@ static void zend_do_traits_property_binding(zend_class_entry *ce, zend_class_ent
25522567
);
25532568
}
25542569

2570+
if (ce->ce_flags & ZEND_ACC_STATIC && !(property_info->flags & ZEND_ACC_STATIC)) {
2571+
zend_error_noreturn(E_COMPILE_ERROR,
2572+
"Static class %s cannot use trait with a non-static property %s::$%s",
2573+
ZSTR_VAL(ce->name),
2574+
ZSTR_VAL(property_info->ce->name), ZSTR_VAL(prop_name)
2575+
);
2576+
}
2577+
25552578
/* property not found, so lets add it */
25562579
if (flags & ZEND_ACC_STATIC) {
25572580
prop_value = &traits[i]->default_static_members_table[property_info->offset];

0 commit comments

Comments
 (0)