Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0dc1c19
Add a test that fails
withinboredom Mar 26, 2025
8905724
Add scope to class entries
withinboredom Mar 26, 2025
be18661
modify grammar
withinboredom Mar 26, 2025
d382a7f
namespaces: add support
withinboredom Mar 26, 2025
05686a2
allow for defining nested classes
withinboredom Mar 26, 2025
a06723e
add more tests and fix a memory leak
withinboredom Mar 26, 2025
b369064
add more tests
withinboredom Mar 26, 2025
df7f149
add more tests
withinboredom Mar 26, 2025
d3b3666
add reflection support
withinboredom Mar 26, 2025
0c70dae
rewrite scopes during opcache compilation
withinboredom Mar 26, 2025
369af23
add tests for visibility
withinboredom Mar 26, 2025
7d13444
handle protected/private lookups in outer classes
withinboredom Mar 26, 2025
36c7c52
handle private/protected types in properties
withinboredom Mar 26, 2025
3e14baf
do not elide return checks for private/protected inner classes
withinboredom Mar 26, 2025
977643d
do not elide return checks for private/protected inner classes
withinboredom Mar 26, 2025
f26f1e9
add and update tests
withinboredom Mar 26, 2025
f28513d
handle instantiation protections
withinboredom Mar 26, 2025
8e5f4d9
start keeping track of own nested classes
withinboredom Mar 26, 2025
b9ff2ec
simplify resolution logic
withinboredom Mar 26, 2025
fd69299
keep cased namespace names
withinboredom Mar 26, 2025
7e754b7
add alias tests
withinboredom Mar 27, 2025
f7161a9
add more tests and fix a small visibility issue
withinboredom Mar 30, 2025
9117b02
validate scope is set
withinboredom Mar 30, 2025
2640748
allow enums and traits
withinboredom Mar 30, 2025
10b2f63
only allow enums and fix test
withinboredom Mar 30, 2025
21f7dfc
do not allow private/protected classes on interfaces
withinboredom Mar 30, 2025
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
Prev Previous commit
Next Next commit
allow for defining nested classes
Now we can define nested classes inside other classes
  • Loading branch information
withinboredom committed Mar 31, 2025
commit 05686a2361cd786093d0b7559b232aac4c2be5af
176 changes: 161 additions & 15 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "zend_call_stack.h"
#include "zend_frameless_function.h"
#include "zend_property_hooks.h"
#include "zend_namespaces.h"

#define SET_NODE(target, src) do { \
target ## _type = (src)->op_type; \
Expand Down Expand Up @@ -424,6 +425,7 @@ void zend_init_compiler_data_structures(void) /* {{{ */
zend_stack_init(&CG(delayed_oplines_stack), sizeof(zend_op));
zend_stack_init(&CG(short_circuiting_opnums), sizeof(uint32_t));
CG(active_class_entry) = NULL;
CG(nested_class_queue) = NULL;
CG(in_compilation) = 0;
CG(skip_shebang) = 0;

Expand Down Expand Up @@ -1178,6 +1180,60 @@ static zend_string *zend_resolve_const_name(zend_string *name, uint32_t type, bo
name, type, is_fully_qualified, 1, FC(imports_const));
}

static zend_string *get_namespace_from_scope(const zend_class_entry *scope)
{
ZEND_ASSERT(scope != NULL);
while (scope && scope->lexical_scope && scope->type != ZEND_NAMESPACE_CLASS) {
scope = scope->lexical_scope;
}
return zend_string_copy(scope->name);
}

static zend_string *get_scoped_name(zend_string *ns, zend_string *name)
{
name = zend_string_tolower(name);
if (ns && ZSTR_LEN(ns) && ZSTR_LEN(name) > ZSTR_LEN(ns) + 1 &&
memcmp(ZSTR_VAL(name), ZSTR_VAL(ns), ZSTR_LEN(ns)) == 0 &&
ZSTR_VAL(name)[ZSTR_LEN(ns)] == '\\') {
zend_string *ret = zend_string_init(ZSTR_VAL(name) + ZSTR_LEN(ns) + 1, ZSTR_LEN(name) - ZSTR_LEN(ns) - 1, 0);
zend_string_release(name);
return ret;
}
return name;
}

zend_string *zend_resolve_class_in_scope(zend_string *name, const zend_class_entry *scope)
{
zend_string *ns_name = get_namespace_from_scope(scope);
zend_string *original_suffix = get_scoped_name(ns_name, name);
zend_string_release(ns_name);

const zend_class_entry *current_scope = scope;

while (current_scope && current_scope->type != ZEND_NAMESPACE_CLASS) {
zend_string *try_name = zend_string_concat3(
ZSTR_VAL(current_scope->name), ZSTR_LEN(current_scope->name),
"\\", 1,
ZSTR_VAL(original_suffix), ZSTR_LEN(original_suffix));

zend_string *lc_try_name = zend_string_tolower(try_name);

bool has_seen = zend_have_seen_symbol(lc_try_name, ZEND_SYMBOL_CLASS);
zend_string_release(lc_try_name);

if (has_seen) {
zend_string_release(original_suffix);
return try_name;
}
zend_string_release(try_name);

current_scope = current_scope->lexical_scope;
}

zend_string_release(original_suffix);
return NULL;
}

static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /* {{{ */
{
char *compound;
Expand Down Expand Up @@ -1236,6 +1292,13 @@ static zend_string *zend_resolve_class_name(zend_string *name, uint32_t type) /*
}
}

if (CG(active_class_entry)) {
zend_string *nested_name = zend_resolve_class_in_scope(name, CG(active_class_entry));
if (nested_name) {
return nested_name;
}
}

/* If not fully qualified and not an alias, prepend the current namespace */
return zend_prefix_with_ns(name);
}
Expand Down Expand Up @@ -9016,10 +9079,9 @@ static void zend_compile_use_trait(zend_ast *ast) /* {{{ */
}
/* }}} */

static void zend_compile_implements(zend_ast *ast) /* {{{ */
static void zend_compile_implements(zend_ast *ast, zend_class_entry *ce) /* {{{ */
{
zend_ast_list *list = zend_ast_get_list(ast);
zend_class_entry *ce = CG(active_class_entry);
zend_class_name *interface_names;
uint32_t i;

Expand Down Expand Up @@ -9077,6 +9139,42 @@ static void zend_compile_enum_backing_type(zend_class_entry *ce, zend_ast *enum_
zend_type_release(type, 0);
}

static void zend_defer_class_decl(zend_ast *ast)
{
ZEND_ASSERT(CG(active_class_entry));

if (CG(active_op_array)->function_name) {
zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be declared inside functions");
}

if (CG(nested_class_queue) == NULL) {
ALLOC_HASHTABLE(CG(nested_class_queue));
zend_hash_init(CG(nested_class_queue), 8, NULL, ZVAL_PTR_DTOR, 0);
}

zend_hash_next_index_insert_ptr(CG(nested_class_queue), ast);
}

static void zend_scan_nested_class_decl(zend_ast *ast, const zend_string *prefix)
{
const zend_ast_list *list = zend_ast_get_list(ast);
for (int i = 0; i < list->children; i++) {
ast = list->child[i];
if (ast->kind == ZEND_AST_CLASS) {
const zend_ast_decl *decl = (zend_ast_decl *)ast;
zend_string *name = zend_string_concat3(
ZSTR_VAL(prefix), ZSTR_LEN(prefix),
"\\", 1,
ZSTR_VAL(decl->name), ZSTR_LEN(decl->name));
zend_string *lc_name = zend_string_tolower(name);
zend_register_seen_symbol(lc_name, ZEND_SYMBOL_CLASS);
zend_string_release(lc_name);
zend_scan_nested_class_decl(decl->child[2], name);
zend_string_release(name);
}
}
}

static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* {{{ */
{
zend_ast_decl *decl = (zend_ast_decl *) ast;
Expand All @@ -9093,10 +9191,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
if (EXPECTED((decl->flags & ZEND_ACC_ANON_CLASS) == 0)) {
zend_string *unqualified_name = decl->name;

if (CG(active_class_entry)) {
zend_error_noreturn(E_COMPILE_ERROR, "Class declarations may not be nested");
}

const char *type = "a class name";
if (decl->flags & ZEND_ACC_ENUM) {
type = "an enum name";
Expand All @@ -9106,7 +9200,34 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
type = "a trait name";
}
zend_assert_valid_class_name(unqualified_name, type);
name = zend_prefix_with_ns(unqualified_name);

if (CG(active_class_entry)) {
name = zend_string_concat3(
ZSTR_VAL(CG(active_class_entry)->name), ZSTR_LEN(CG(active_class_entry)->name),
"\\", 1,
ZSTR_VAL(unqualified_name), ZSTR_LEN(unqualified_name));

/* configure the class from the modifiers */
decl->flags |= decl->attr & ZEND_ACC_FINAL;
if (decl->attr & ZEND_ACC_ABSTRACT) {
decl->flags |= ZEND_ACC_EXPLICIT_ABSTRACT_CLASS;
}
if (decl->attr & ZEND_ACC_READONLY) {
decl->flags |= ZEND_ACC_READONLY_CLASS | ZEND_ACC_NO_DYNAMIC_PROPERTIES;
}

int propFlags = decl->attr & ZEND_ACC_PPP_MASK;
decl->attr &= ~(ZEND_ACC_PPP_MASK | ZEND_ACC_FINAL | ZEND_ACC_READONLY | ZEND_ACC_ABSTRACT);

ce->required_scope = propFlags & (ZEND_ACC_PRIVATE | ZEND_ACC_PROTECTED) ? CG(active_class_entry) : NULL;
ce->required_scope_absolute = propFlags & ZEND_ACC_PRIVATE ? true : false;
ce->lexical_scope = CG(active_class_entry);
} else {
name = zend_prefix_with_ns(unqualified_name);
ce->required_scope = NULL;
ce->lexical_scope = zend_resolve_namespace(FC(current_namespace));
}

name = zend_new_interned_string(name);
lcname = zend_string_tolower(name);

Expand All @@ -9124,6 +9245,8 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
/* Find an anon class name that is not in use yet. */
name = NULL;
lcname = NULL;
ce->required_scope = NULL;
ce->lexical_scope = CG(active_class_entry) ? CG(active_class_entry) : zend_resolve_namespace(FC(current_namespace));
do {
zend_tmp_string_release(name);
zend_tmp_string_release(lcname);
Expand Down Expand Up @@ -9165,16 +9288,16 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
zend_resolve_const_class_name_reference(extends_ast, "class name");
}

if (implements_ast) {
zend_compile_implements(implements_ast, ce);
}

CG(active_class_entry) = ce;

if (decl->child[3]) {
zend_compile_attributes(&ce->attributes, decl->child[3], 0, ZEND_ATTRIBUTE_TARGET_CLASS, 0);
}

if (implements_ast) {
zend_compile_implements(implements_ast);
}

if (ce->ce_flags & ZEND_ACC_ENUM) {
if (enum_backing_type_ast != NULL) {
zend_compile_enum_backing_type(ce, enum_backing_type_ast);
Expand All @@ -9183,6 +9306,9 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
zend_enum_register_props(ce);
}

if (ce->lexical_scope->type == ZEND_NAMESPACE_CLASS) {
zend_scan_nested_class_decl(stmt_ast, name);
}
zend_compile_stmt(stmt_ast);

/* Reset lineno for final opcodes and errors */
Expand All @@ -9192,8 +9318,6 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
zend_verify_abstract_class(ce);
}

CG(active_class_entry) = original_ce;

if (toplevel) {
ce->ce_flags |= ZEND_ACC_TOP_LEVEL;
}
Expand All @@ -9214,7 +9338,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
&& !zend_compile_ignore_class(parent_ce, ce->info.user.filename)) {
if (zend_try_early_bind(ce, parent_ce, lcname, NULL)) {
zend_string_release(lcname);
return;
goto compile_nested_classes;
}
}
} else if (EXPECTED(zend_hash_add_ptr(CG(class_table), lcname, ce) != NULL)) {
Expand All @@ -9223,7 +9347,7 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
zend_inheritance_check_override(ce);
ce->ce_flags |= ZEND_ACC_LINKED;
zend_observer_class_linked_notify(ce, lcname);
return;
goto compile_nested_classes;
} else {
goto link_unbound;
}
Expand Down Expand Up @@ -9293,6 +9417,24 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel)
opline->result.opline_num = -1;
}
}
compile_nested_classes:

if (CG(nested_class_queue) == NULL) {
CG(active_class_entry) = original_ce;
return;
}

HashTable *queue = CG(nested_class_queue);
CG(nested_class_queue) = NULL;

ZEND_HASH_FOREACH_PTR(queue, ast) {
zend_compile_class_decl(NULL, ast, true);
} ZEND_HASH_FOREACH_END();

CG(active_class_entry) = original_ce;

zend_hash_destroy(queue);
FREE_HASHTABLE(queue);
}
/* }}} */

Expand Down Expand Up @@ -11583,6 +11725,10 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */
zend_compile_use_trait(ast);
break;
case ZEND_AST_CLASS:
if (CG(active_class_entry)) {
zend_defer_class_decl(ast);
break;
}
zend_compile_class_decl(NULL, ast, 0);
break;
case ZEND_AST_GROUP_USE:
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_execute_API.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "zend_observer.h"
#include "zend_call_stack.h"
#include "zend_frameless_function.h"
#include "zend_namespaces.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif
Expand Down Expand Up @@ -148,6 +149,8 @@ void init_executor(void) /* {{{ */

EG(function_table) = CG(function_table);
EG(class_table) = CG(class_table);
EG(namespaces) = NULL;
EG(global_namespace) = NULL;

EG(in_autoload) = NULL;
EG(error_handling) = EH_NORMAL;
Expand Down Expand Up @@ -496,6 +499,8 @@ void shutdown_executor(void) /* {{{ */
FREE_HASHTABLE(*EG(symtable_cache_ptr));
}

zend_destroy_namespaces();

zend_hash_destroy(&EG(included_files));

zend_stack_destroy(&EG(user_error_handlers_error_reporting));
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ struct _zend_compiler_globals {
zend_stack loop_var_stack;

zend_class_entry *active_class_entry;
HashTable *nested_class_queue;

zend_string *compiled_filename;

Expand Down
9 changes: 9 additions & 0 deletions Zend/zend_namespaces.c
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ zend_class_entry *zend_resolve_namespace(zend_string *name) {
if (EG(global_namespace) == NULL) {
EG(global_namespace) = create_namespace(zend_empty_string);
EG(global_namespace)->lexical_scope = NULL;
ALLOC_HASHTABLE(EG(namespaces));
zend_hash_init(EG(namespaces), 8, NULL, ZEND_CLASS_DTOR, 0);
zend_hash_add_ptr(EG(namespaces), zend_empty_string, EG(global_namespace));
}
Expand Down Expand Up @@ -93,3 +94,11 @@ zend_class_entry *zend_lookup_namespace(zend_string *name) {

return ns;
}

void zend_destroy_namespaces(void) {
zend_hash_destroy(EG(namespaces));
FREE_HASHTABLE(EG(namespaces));
EG(namespaces) = NULL;
pefree(EG(global_namespace), 0);
EG(global_namespace) = NULL;
}
1 change: 1 addition & 0 deletions Zend/zend_namespaces.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@

ZEND_API zend_class_entry *zend_resolve_namespace(zend_string *name);
ZEND_API zend_class_entry *zend_lookup_namespace(zend_string *name);
ZEND_API void zend_destroy_namespaces(void);

#endif //ZEND_NAMESPACES_H