diff --git a/src/compile.h b/src/compile.h index f2578c9..76c7322 100644 --- a/src/compile.h +++ b/src/compile.h @@ -52,3 +52,8 @@ static void str_dtor(zval *zv) { zend_string_release_ex(Z_STR_P(zv), 0); } + +static zend_string *zend_concat_names(char *name1, size_t name1_len, char *name2, size_t name2_len) +{ + return zend_string_concat3(name1, name1_len, "\\", 1, name2, name2_len); +} diff --git a/src/extension.c b/src/extension.c index 844f64e..093dda1 100644 --- a/src/extension.c +++ b/src/extension.c @@ -69,6 +69,11 @@ static PHP_MINIT_FUNCTION(vyrtue) // Register constants REGISTER_STRING_CONSTANT("VyrtueExt\\VERSION", (char *) PHP_VYRTUE_VERSION, flags); +#ifdef VYRTUE_DEBUG + REGISTER_BOOL_CONSTANT("VyrtueExt\\DEBUG", true, flags); +#else + REGISTER_BOOL_CONSTANT("VyrtueExt\\DEBUG", false, flags); +#endif if (NULL == original_ast_process) { original_ast_process = zend_ast_process; diff --git a/src/preprocess.c b/src/preprocess.c index ca6fbd9..bfb5e65 100644 --- a/src/preprocess.c +++ b/src/preprocess.c @@ -28,28 +28,30 @@ #include "Zend/zend_exceptions.h" #include "main/php.h" #include "main/php_streams.h" -#include "ext/standard/php_string.h" #include "php_vyrtue.h" #include "compile.h" struct vyrtue_preprocess_context { - bool should_perform_second_pass; + bool in_namespace; zend_string *current_namespace; HashTable *imports; HashTable *imports_function; HashTable *imports_const; + HashTable *seen_symbols; }; +static zend_ast *vyrtue_ast_walk(zend_ast *ast, struct vyrtue_preprocess_context *ctx); + /** * @see zend_get_import_ht * @license Zend-2.0 */ -static HashTable *vyrtue_get_import_ht(uint32_t type, struct vyrtue_preprocess_context *ctx) /* {{{ */ +static HashTable *vyrtue_get_import_ht(uint32_t type, struct vyrtue_preprocess_context *ctx) { switch (type) { case ZEND_SYMBOL_CLASS: - if (ctx->imports) { + if (!ctx->imports) { ctx->imports = emalloc(sizeof(HashTable)); zend_hash_init(ctx->imports, 8, NULL, str_dtor, 0); } @@ -96,24 +98,84 @@ static void vyrtue_reset_import_tables(struct vyrtue_preprocess_context *ctx) } } +static void vyrtue_end_namespace(struct vyrtue_preprocess_context *ctx) +{ + ctx->in_namespace = false; + vyrtue_reset_import_tables(ctx); + if (ctx->current_namespace) { +#ifdef VYRTUE_DEBUG + if (UNEXPECTED(NULL != getenv("PHP_VYRTUE_DEBUG_DUMP_NAMESPACE"))) { + fprintf( + stderr, "VYRTUE_NAMESPACE: LEFT: %.*s\n", (int) ctx->current_namespace->len, ctx->current_namespace->val + ); + } +#endif + ctx->current_namespace = NULL; + } +} + +static void vyrtue_register_seen_symbol(zend_string *name, uint32_t kind, struct vyrtue_preprocess_context *ctx) +{ + zval *zv = zend_hash_find(ctx->seen_symbols, name); + if (zv) { + Z_LVAL_P(zv) |= kind; + } else { + zval tmp; + ZVAL_LONG(&tmp, kind); + zend_hash_add_new(ctx->seen_symbols, name, &tmp); + } +} + +static bool vyrtue_have_seen_symbol(zend_string *name, uint32_t kind, struct vyrtue_preprocess_context *ctx) +{ + zval *zv = zend_hash_find(ctx->seen_symbols, name); + return zv && (Z_LVAL_P(zv) & kind) != 0; +} + /** * @see zend_compile_namespace */ -static void vyrtue_ast_first_pass_namespace(zend_ast *ast, struct vyrtue_preprocess_context *ctx) +static void vyrtue_ast_process_namespace(zend_ast *ast, struct vyrtue_preprocess_context *ctx) { zend_ast *name_ast = ast->child[0]; + zend_ast *stmt_ast = ast->child[1]; + bool with_bracket = stmt_ast != NULL; + + // there is a bunch of validation here in zend_compile_namespace that we *might* not need if (name_ast) { ctx->current_namespace = zend_ast_get_str(name_ast); } else { ctx->current_namespace = NULL; } + + vyrtue_reset_import_tables(ctx); + + ctx->in_namespace = true; + + if (with_bracket) { + // FC(has_bracketed_namespaces) = 1; + } + +#ifdef VYRTUE_DEBUG + if (UNEXPECTED(NULL != getenv("PHP_VYRTUE_DEBUG_DUMP_NAMESPACE"))) { + fprintf( + stderr, "VYRTUE_NAMESPACE: ENTER: %.*s\n", (int) ctx->current_namespace->len, ctx->current_namespace->val + ); + } +#endif + + if (stmt_ast) { + zend_ast *replace_child = vyrtue_ast_walk(stmt_ast, ctx); + ZEND_ASSERT(replace_child == NULL); + vyrtue_end_namespace(ctx); + } } /** * @see zend_compile_use */ -static void vyrtue_ast_first_pass_use(zend_ast *ast, struct vyrtue_preprocess_context *ctx) +static void vyrtue_ast_process_use(zend_ast *ast, struct vyrtue_preprocess_context *ctx) { zend_ast_list *list = zend_ast_get_list(ast); uint32_t i; @@ -152,33 +214,68 @@ static void vyrtue_ast_first_pass_use(zend_ast *ast, struct vyrtue_preprocess_co zend_str_tolower_copy(ZSTR_VAL(ns_name), ZSTR_VAL(current_ns), ZSTR_LEN(current_ns)); ZSTR_VAL(ns_name)[ZSTR_LEN(current_ns)] = '\\'; memcpy(ZSTR_VAL(ns_name) + ZSTR_LEN(current_ns) + 1, ZSTR_VAL(lookup_name), ZSTR_LEN(lookup_name) + 1); - - // if (zend_have_seen_symbol(ns_name, type)) { - // zend_check_already_in_use(type, old_name, new_name, ns_name); - // } - zend_string_efree(ns_name); - } /* else if (zend_have_seen_symbol(lookup_name, type)) { - zend_check_already_in_use(type, old_name, new_name, lookup_name); - } */ + } zend_string_addref(old_name); old_name = zend_new_interned_string(old_name); - zend_hash_add_ptr(current_import, lookup_name, old_name); - // @todo we could potentially check for registered macros here + if (!zend_hash_add_ptr(current_import, lookup_name, old_name)) { + // symbol was imported twice - let zend_compile handle the error + } + +#ifdef VYRTUE_DEBUG + if (UNEXPECTED(NULL != getenv("PHP_VYRTUE_DEBUG_DUMP_USE"))) { + fprintf( + stderr, + "VYRTUE_USE: %.*s => %.*s\n", + (int) old_name->len, + old_name->val, + (int) new_name->len, + new_name->val + ); + } +#endif zend_string_release_ex(lookup_name, 0); zend_string_release_ex(new_name, 0); } } -static void vyrtue_ast_first_pass(zend_ast *ast, struct vyrtue_preprocess_context *ctx) +/** + * @see zend_compile_group_use + */ +static void vyrtue_ast_process_group_use(zend_ast *ast, struct vyrtue_preprocess_context *ctx) { - if (ast->kind != ZEND_AST_STMT_LIST) { - return; + uint32_t i; + zend_string *ns = zend_ast_get_str(ast->child[0]); + zend_ast_list *list = zend_ast_get_list(ast->child[1]); + + for (i = 0; i < list->children; i++) { + zend_ast *inline_use, *use = list->child[i]; + zval *name_zval = zend_ast_get_zval(use->child[0]); + zend_string *name = Z_STR_P(name_zval); + zend_string *compound_ns = zend_concat_names(ZSTR_VAL(ns), ZSTR_LEN(ns), ZSTR_VAL(name), ZSTR_LEN(name)); + zend_string_release_ex(name, 0); + ZVAL_STR(name_zval, compound_ns); + inline_use = zend_ast_create_list(1, ZEND_AST_USE, use); + inline_use->attr = ast->attr ? ast->attr : use->attr; + vyrtue_ast_process_use(inline_use, ctx); } +} + +static zend_ast *vyrtue_ast_enter_node(zend_ast *ast, struct vyrtue_preprocess_context *ctx) +{ + return NULL; +} + +static zend_ast *vyrtue_ast_leave_node(zend_ast *ast, struct vyrtue_preprocess_context *ctx) +{ + return NULL; +} +static void vyrtue_ast_walk_list(zend_ast *ast, struct vyrtue_preprocess_context *ctx) +{ zend_ast_list *list = zend_ast_get_list(ast); for (int i = 0; i < list->children; i++) { @@ -187,42 +284,88 @@ static void vyrtue_ast_first_pass(zend_ast *ast, struct vyrtue_preprocess_contex continue; } - // fprintf(stderr, "|%d %d %d| \n", (int) child->kind, ZEND_AST_NAMESPACE, ZEND_AST_USE); + zend_ast *replace_child = vyrtue_ast_walk(child, ctx); - switch (child->kind) { - case ZEND_AST_NAMESPACE: - vyrtue_ast_first_pass_namespace(child, ctx); - break; - case ZEND_AST_USE: - vyrtue_ast_first_pass_use(child, ctx); - break; + if (UNEXPECTED(replace_child != NULL)) { + zend_ast_destroy(list->child[i]); + list->child[i] = replace_child; } } } -static void vyrtue_ast_second_pass(zend_ast *ast, struct vyrtue_preprocess_context *ctx) +static void vyrtue_ast_walk_children(zend_ast *ast, struct vyrtue_preprocess_context *ctx) { - // todo + uint32_t i, children = zend_ast_get_num_children(ast); + for (i = 0; i < children; i++) { + if (UNEXPECTED(ast->child[i] == NULL)) { + continue; + } + + zend_ast *replace_child = vyrtue_ast_walk(ast->child[i], ctx); + + if (UNEXPECTED(replace_child != NULL)) { + zend_ast_destroy(ast->child[i]); + ast->child[i] = replace_child; + } + } } -VYRTUE_PUBLIC void vyrtue_ast_process_file(zend_ast *ast) +static zend_ast *vyrtue_ast_walk(zend_ast *ast, struct vyrtue_preprocess_context *ctx) { - struct vyrtue_preprocess_context ctx = { - .should_perform_second_pass = false, - }; + zend_ast *replace_child; - vyrtue_ast_first_pass(ast, &ctx); + if (NULL == ast) { + return NULL; + } + + // these have special handling + switch (ast->kind) { + case ZEND_AST_NAMESPACE: + vyrtue_ast_process_namespace(ast, ctx); + return NULL; + + case ZEND_AST_USE: + vyrtue_ast_process_use(ast, ctx); + return NULL; + + case ZEND_AST_GROUP_USE: + vyrtue_ast_process_group_use(ast, ctx); + return NULL; + } + + replace_child = vyrtue_ast_enter_node(ast, ctx); + if (replace_child) { + // @TODO should we recurse on the replacement (also, below for leave node)? + return replace_child; + } - if (ctx.should_perform_second_pass) { - vyrtue_ast_second_pass(ast, &ctx); + if (zend_ast_is_list(ast)) { + vyrtue_ast_walk_list(ast, ctx); + } else { + vyrtue_ast_walk_children(ast, ctx); } + return vyrtue_ast_leave_node(ast, ctx); +} + +VYRTUE_PUBLIC void vyrtue_ast_process_file(zend_ast *ast) +{ + struct vyrtue_preprocess_context ctx = {0}; + + ctx.seen_symbols = emalloc(sizeof(HashTable)); + zend_hash_init(ctx.seen_symbols, 8, NULL, NULL, 0); + + zend_ast *replace_child = vyrtue_ast_walk(ast, &ctx); + ZEND_ASSERT(replace_child == NULL); + #ifdef VYRTUE_DEBUG - if (NULL != getenv("PHP_VYRTUE_DEBUG_DUMP")) { + if (UNEXPECTED(NULL != getenv("PHP_VYRTUE_DEBUG_DUMP_AST"))) { zend_string *str = zend_ast_export("len, str->val); + fprintf(stderr, "%.*s", (int) str->len, str->val); + zend_string_release(str); } #endif - vyrtue_reset_import_tables(&ctx); + vyrtue_end_namespace(&ctx); + zend_hash_destroy(ctx.seen_symbols); } diff --git a/tests/namespace/detection-01.phpt b/tests/namespace/detection-01.phpt new file mode 100644 index 0000000..3f38764 --- /dev/null +++ b/tests/namespace/detection-01.phpt @@ -0,0 +1,19 @@ +--TEST-- +namespace detection 01 +--EXTENSIONS-- +vyrtue +--ENV-- +PHP_VYRTUE_DEBUG_DUMP_NAMESPACE=1 +PHP_VYRTUE_DEBUG_DUMP_USE=1 +--SKIPIF-- + +--FILE-- + bar +VYRTUE_USE: Foo\bat => booyah +VYRTUE_NAMESPACE: LEFT: FooBar diff --git a/tests/namespace/detection-02.phpt b/tests/namespace/detection-02.phpt new file mode 100644 index 0000000..e632b25 --- /dev/null +++ b/tests/namespace/detection-02.phpt @@ -0,0 +1,38 @@ +--TEST-- +namespace detection 02 +--EXTENSIONS-- +vyrtue +--ENV-- +PHP_VYRTUE_DEBUG_DUMP_NAMESPACE=1 +PHP_VYRTUE_DEBUG_DUMP_USE=1 +--SKIPIF-- + +--FILE-- + Another +VYRTUE_USE: My\Full\NSname => NSname +VYRTUE_USE: ArrayObject => ArrayObject +VYRTUE_USE: My\Full\functionName => functionName +VYRTUE_USE: My\Full\functionName => func +VYRTUE_USE: My\Full\CONSTANT => CONSTANT +VYRTUE_NAMESPACE: LEFT: foo diff --git a/tests/namespace/detection-03.phpt b/tests/namespace/detection-03.phpt new file mode 100644 index 0000000..150fe9b --- /dev/null +++ b/tests/namespace/detection-03.phpt @@ -0,0 +1,36 @@ +--TEST-- +namespace detection 03 +--EXTENSIONS-- +vyrtue +--ENV-- +PHP_VYRTUE_DEBUG_DUMP_NAMESPACE=1 +PHP_VYRTUE_DEBUG_DUMP_USE=1 +--SKIPIF-- + +--FILE-- + Another +VYRTUE_USE: My\Full\NSname => NSname +VYRTUE_USE: My\Full\functionName => functionName +VYRTUE_USE: My\Full\functionName => func +VYRTUE_NAMESPACE: LEFT: FooBar +VYRTUE_NAMESPACE: ENTER: BarBat +VYRTUE_USE: My\Full\Classname => Another +VYRTUE_USE: My\Full\NSname => NSname +VYRTUE_USE: My\Full\functionName => functionName +VYRTUE_USE: My\Full\functionName => func +VYRTUE_NAMESPACE: LEFT: BarBat diff --git a/tests/namespace/detection-04.phpt b/tests/namespace/detection-04.phpt new file mode 100644 index 0000000..2ebc102 --- /dev/null +++ b/tests/namespace/detection-04.phpt @@ -0,0 +1,15 @@ +--TEST-- +namespace detection 04 +--EXTENSIONS-- +vyrtue +--ENV-- +PHP_VYRTUE_DEBUG_DUMP_NAMESPACE=1 +PHP_VYRTUE_DEBUG_DUMP_USE=1 +--SKIPIF-- + +--FILE-- + Another +VYRTUE_USE: My\Full\NSname => NSname diff --git a/tests/namespace/detection-05.phpt b/tests/namespace/detection-05.phpt new file mode 100644 index 0000000..800c46b --- /dev/null +++ b/tests/namespace/detection-05.phpt @@ -0,0 +1,24 @@ +--TEST-- +namespace detection 05 +--EXTENSIONS-- +vyrtue +--ENV-- +PHP_VYRTUE_DEBUG_DUMP_NAMESPACE=1 +PHP_VYRTUE_DEBUG_DUMP_USE=1 +--SKIPIF-- + +--FILE-- + ClassA +VYRTUE_USE: some\namespace\ClassB => ClassB +VYRTUE_USE: some\namespace\ClassC => C +VYRTUE_USE: some\namespace\fn_a => fn_a +VYRTUE_USE: some\namespace\fn_b => fn_b +VYRTUE_USE: some\namespace\fn_c => fn_c +VYRTUE_USE: some\namespace\ConstA => ConstA +VYRTUE_USE: some\namespace\ConstB => ConstB +VYRTUE_USE: some\namespace\ConstC => ConstC