Skip to content

Commit 0fcc03b

Browse files
committed
Implement namespace extraction utilities for visibility checks
Add helper functions to extract and compare namespaces at runtime: * zend_extract_namespace() - Extracts "Foo\Bar" from "Foo\Bar\ClassName" * zend_get_class_namespace() - Gets namespace from class entry name * zend_get_caller_namespace() - Determines namespace of executing code - From methods: uses class namespace - From functions: uses op_array->namespace_name - From top-level: uses op_array->namespace_name These utilities will be used by visibility checking code to enforce namespace-scoped access rules. Important: For trait methods, scope is the class that uses the trait, not the trait itself. This means private(namespace) in traits is checked against the receiver's namespace, which is the desired behavior.
1 parent a81615b commit 0fcc03b

File tree

2 files changed

+64
-0
lines changed

2 files changed

+64
-0
lines changed

Zend/zend_API.c

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5322,3 +5322,62 @@ ZEND_API zend_result zend_get_default_from_internal_arg_info(
53225322
#endif
53235323
return get_default_via_ast(default_value_zval, default_value);
53245324
}
5325+
5326+
/* Namespace extraction helpers for private(namespace) visibility */
5327+
5328+
/* Extract namespace from a fully-qualified name
5329+
* Examples:
5330+
* "Foo\\Bar\\ClassName" -> "Foo\\Bar"
5331+
* "ClassName" -> "" (empty string for global namespace)
5332+
*/
5333+
ZEND_API zend_string* zend_extract_namespace(const zend_string *name)
5334+
{
5335+
const char *class_name = ZSTR_VAL(name);
5336+
const char *last_separator = zend_memrchr(class_name, '\\', ZSTR_LEN(name));
5337+
5338+
if (last_separator == NULL) {
5339+
/* No namespace separator found: global namespace */
5340+
return ZSTR_EMPTY_ALLOC();
5341+
}
5342+
5343+
/* Extract namespace part (everything before the last backslash) */
5344+
size_t namespace_len = last_separator - class_name;
5345+
return zend_string_init(class_name, namespace_len, 0);
5346+
}
5347+
5348+
/* Get namespace from a class entry */
5349+
ZEND_API zend_string* zend_get_class_namespace(const zend_class_entry *ce)
5350+
{
5351+
return zend_extract_namespace(ce->name);
5352+
}
5353+
5354+
/* Get the namespace of the currently executing code */
5355+
ZEND_API zend_string* zend_get_caller_namespace(void)
5356+
{
5357+
zend_execute_data *ex = EG(current_execute_data);
5358+
5359+
if (!ex || !ex->func) {
5360+
/* No execution context - global namespace */
5361+
return ZSTR_EMPTY_ALLOC();
5362+
}
5363+
5364+
/* Case 1: Called from a method: use the class namespace
5365+
* For trait methods, scope is the class that uses the trait,
5366+
* not the trait itself. This is the desired behavior. */
5367+
if (ex->func->common.scope) {
5368+
return zend_get_class_namespace(ex->func->common.scope);
5369+
}
5370+
5371+
/* Case 2: Called from a user function or top-level code */
5372+
if (ex->func->type == ZEND_USER_FUNCTION) {
5373+
zend_op_array *op_array = &ex->func->op_array;
5374+
5375+
/* Use the namespace_name field we added to op_array */
5376+
if (op_array->namespace_name) {
5377+
return op_array->namespace_name;
5378+
}
5379+
}
5380+
5381+
/* Case 3: Internal function or global namespace */
5382+
return ZSTR_EMPTY_ALLOC();
5383+
}

Zend/zend_API.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -929,6 +929,11 @@ ZEND_API bool zend_is_countable(const zval *countable);
929929
ZEND_API zend_result zend_get_default_from_internal_arg_info(
930930
zval *default_value_zval, const zend_internal_arg_info *arg_info);
931931

932+
/* Namespace extraction helpers for private(namespace) visibility */
933+
ZEND_API zend_string* zend_extract_namespace(const zend_string *name);
934+
ZEND_API zend_string* zend_get_class_namespace(const zend_class_entry *ce);
935+
ZEND_API zend_string* zend_get_caller_namespace(void);
936+
932937
END_EXTERN_C()
933938

934939
#if ZEND_DEBUG

0 commit comments

Comments
 (0)