Skip to content

Commit 210d6e1

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 9de32b4 commit 210d6e1

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
@@ -5299,3 +5299,62 @@ ZEND_API zend_result zend_get_default_from_internal_arg_info(
52995299
#endif
53005300
return get_default_via_ast(default_value_zval, default_value);
53015301
}
5302+
5303+
/* Namespace extraction helpers for private(namespace) visibility */
5304+
5305+
/* Extract namespace from a fully-qualified name
5306+
* Examples:
5307+
* "Foo\\Bar\\ClassName" -> "Foo\\Bar"
5308+
* "ClassName" -> "" (empty string for global namespace)
5309+
*/
5310+
ZEND_API zend_string* zend_extract_namespace(const zend_string *name)
5311+
{
5312+
const char *class_name = ZSTR_VAL(name);
5313+
const char *last_separator = zend_memrchr(class_name, '\\', ZSTR_LEN(name));
5314+
5315+
if (last_separator == NULL) {
5316+
/* No namespace separator found: global namespace */
5317+
return ZSTR_EMPTY_ALLOC();
5318+
}
5319+
5320+
/* Extract namespace part (everything before the last backslash) */
5321+
size_t namespace_len = last_separator - class_name;
5322+
return zend_string_init(class_name, namespace_len, 0);
5323+
}
5324+
5325+
/* Get namespace from a class entry */
5326+
ZEND_API zend_string* zend_get_class_namespace(const zend_class_entry *ce)
5327+
{
5328+
return zend_extract_namespace(ce->name);
5329+
}
5330+
5331+
/* Get the namespace of the currently executing code */
5332+
ZEND_API zend_string* zend_get_caller_namespace(void)
5333+
{
5334+
zend_execute_data *ex = EG(current_execute_data);
5335+
5336+
if (!ex || !ex->func) {
5337+
/* No execution context - global namespace */
5338+
return ZSTR_EMPTY_ALLOC();
5339+
}
5340+
5341+
/* Case 1: Called from a method: use the class namespace
5342+
* For trait methods, scope is the class that uses the trait,
5343+
* not the trait itself. This is the desired behavior. */
5344+
if (ex->func->common.scope) {
5345+
return zend_get_class_namespace(ex->func->common.scope);
5346+
}
5347+
5348+
/* Case 2: Called from a user function or top-level code */
5349+
if (ex->func->type == ZEND_USER_FUNCTION) {
5350+
zend_op_array *op_array = &ex->func->op_array;
5351+
5352+
/* Use the namespace_name field we added to op_array */
5353+
if (op_array->namespace_name) {
5354+
return op_array->namespace_name;
5355+
}
5356+
}
5357+
5358+
/* Case 3: Internal function or global namespace */
5359+
return ZSTR_EMPTY_ALLOC();
5360+
}

Zend/zend_API.h

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

930+
/* Namespace extraction helpers for private(namespace) visibility */
931+
ZEND_API zend_string* zend_extract_namespace(const zend_string *name);
932+
ZEND_API zend_string* zend_get_class_namespace(const zend_class_entry *ce);
933+
ZEND_API zend_string* zend_get_caller_namespace(void);
934+
930935
END_EXTERN_C()
931936

932937
#if ZEND_DEBUG

0 commit comments

Comments
 (0)