Skip to content

Commit

Permalink
Add namespace and import detection
Browse files Browse the repository at this point in the history
  • Loading branch information
jbboehr committed Feb 7, 2024
1 parent f7a51d1 commit 3b57166
Show file tree
Hide file tree
Showing 8 changed files with 324 additions and 39 deletions.
5 changes: 5 additions & 0 deletions src/compile.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
5 changes: 5 additions & 0 deletions src/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
221 changes: 182 additions & 39 deletions src/preprocess.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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++) {
Expand All @@ -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("<?php\n", ast, "");
fprintf(stderr, "%.*s", str->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);
}
19 changes: 19 additions & 0 deletions tests/namespace/detection-01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
namespace detection 01
--EXTENSIONS--
vyrtue
--ENV--
PHP_VYRTUE_DEBUG_DUMP_NAMESPACE=1
PHP_VYRTUE_DEBUG_DUMP_USE=1
--SKIPIF--
<?php if (!VyrtueExt\DEBUG) die("Skipped: vyrtue not debug build"); ?>
--FILE--
<?php
namespace FooBar;
use function Foo\bar;
use function Foo\bat as booyah;
--EXPECT--
VYRTUE_NAMESPACE: ENTER: FooBar
VYRTUE_USE: Foo\bar => bar
VYRTUE_USE: Foo\bat => booyah
VYRTUE_NAMESPACE: LEFT: FooBar
38 changes: 38 additions & 0 deletions tests/namespace/detection-02.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
--TEST--
namespace detection 02
--EXTENSIONS--
vyrtue
--ENV--
PHP_VYRTUE_DEBUG_DUMP_NAMESPACE=1
PHP_VYRTUE_DEBUG_DUMP_USE=1
--SKIPIF--
<?php if (!VyrtueExt\DEBUG) die("Skipped: vyrtue not debug build"); ?>
--FILE--
<?php
// from https://www.php.net/manual/en/language.namespaces.importing.php
namespace foo;
use My\Full\Classname as Another;

// this is the same as use My\Full\NSname as NSname
use My\Full\NSname;

// importing a global class
use ArrayObject;

// importing a function
use function My\Full\functionName;

// aliasing a function
use function My\Full\functionName as func;

// importing a constant
use const My\Full\CONSTANT;
--EXPECT--
VYRTUE_NAMESPACE: ENTER: foo
VYRTUE_USE: My\Full\Classname => 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
Loading

0 comments on commit 3b57166

Please sign in to comment.