diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index b2af322d9119e..a0e5702121a96 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -82,6 +82,9 @@ ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval(zval *zv) { ZEND_API zend_ast * ZEND_FASTCALL zend_ast_create_zval_from_str(zend_string *str) { zval zv; ZVAL_STR(&zv, str); + // This doesn't appear to do anything +// Z_SET_IS_LITERAL(zv); + return zend_ast_create_zval_int(&zv, 0, CG(zend_lineno)); } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c671628479e68..edd0156971f48 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -9585,9 +9585,20 @@ static void zend_compile_expr_inner(znode *result, zend_ast *ast) /* {{{ */ return; } + zval *zval_dja; + + printf("ast->kind %d\n", ast->kind); + switch (ast->kind) { case ZEND_AST_ZVAL: - ZVAL_COPY(&result->u.constant, zend_ast_get_zval(ast)); + zval_dja = zend_ast_get_zval(ast); + + if (Z_TYPE_P(zval_dja) == IS_STRING) { +// printf("This is literal p.\n"); + Z_SET_IS_LITERAL_P(zval_dja); + } + + ZVAL_COPY(&result->u.constant, zval_dja); result->op_type = IS_CONST; return; case ZEND_AST_ZNODE: diff --git a/Zend/zend_types.h b/Zend/zend_types.h index da6792ba7bb25..25e6c10250fe1 100644 --- a/Zend/zend_types.h +++ b/Zend/zend_types.h @@ -662,6 +662,11 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define GC_PERSISTENT (1<<7) /* allocated using malloc */ #define GC_PERSISTENT_LOCAL (1<<8) /* persistent, but thread-local */ + +/* Literal string re-uses the GC_PROTECTED bit. This is safe */ +/* to do as GC_PROTECTED is only used for arrays and objects */ +#define GC_IS_LITERAL_FLAG (1<<5) + #define GC_NULL (IS_NULL | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) #define GC_STRING (IS_STRING | (GC_NOT_COLLECTABLE << GC_FLAGS_SHIFT)) #define GC_ARRAY IS_ARRAY @@ -710,6 +715,14 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { #define OBJ_FLAGS(obj) GC_FLAGS(obj) +#define GC_IS_LITERAL(p) \ + (GC_FLAGS(p) & GC_IS_LITERAL_FLAG) + +#define GC_SET_IS_LITERAL(p) do { \ + GC_ADD_FLAGS(p, GC_IS_LITERAL_FLAG); \ + } while (0) + + /* Recursion protection macros must be used only for arrays and objects */ #define GC_IS_RECURSIVE(p) \ (GC_FLAGS(p) & GC_PROTECTED) @@ -730,6 +743,12 @@ static zend_always_inline uint32_t zval_gc_info(uint32_t gc_type_info) { if (!(GC_FLAGS(p) & GC_IMMUTABLE)) GC_UNPROTECT_RECURSION(p); \ } while (0) + +#define Z_IS_LITERAL(zval) GC_IS_LITERAL(Z_COUNTED(zval)) +#define Z_IS_LITERAL_P(zval_p) Z_IS_LITERAL(*(zval_p)) +#define Z_SET_IS_LITERAL(zval) GC_SET_IS_LITERAL(Z_COUNTED(zval)) +#define Z_SET_IS_LITERAL_P(zval_p) Z_SET_IS_LITERAL(*(zval_p)) + #define Z_IS_RECURSIVE(zval) GC_IS_RECURSIVE(Z_COUNTED(zval)) #define Z_PROTECT_RECURSION(zval) GC_PROTECT_RECURSION(Z_COUNTED(zval)) #define Z_UNPROTECT_RECURSION(zval) GC_UNPROTECT_RECURSION(Z_COUNTED(zval)) diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index 54c318877152d..620ff4be3611c 100755 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -49,6 +49,7 @@ typedef struct yy_buffer_state *YY_BUFFER_STATE; #include #include "zend_portability.h" +#include #include #include @@ -2707,3 +2708,128 @@ PHP_FUNCTION(sys_getloadavg) } /* }}} */ #endif + + +/* {{{ */ +PHP_FUNCTION(literal_set) +{ + zval *string; + // TODO - delete this. Just for debugging. + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(string) + ZEND_PARSE_PARAMETERS_END(); + + // TODO - check string is actually a string... + // zend_string *str = Z_STR_P(entry); + + Z_SET_IS_LITERAL_P(string); +} +/* }}} */ + + +static int check_is_literal_or_int(zval *piece, int position) +{ + if (Z_TYPE_P(piece) == IS_LONG) { + return 0; + } + + if (Z_TYPE_P(piece) != IS_STRING) { + zend_throw_exception_ex( + zend_ce_type_error, + 0, + "Only literal strings and ints allowed. Found bad type at position %d", + position + ); + return -1; + } + + if(!Z_IS_LITERAL(*piece)) { + zend_throw_exception_ex( + zend_ce_type_error, + 0, + "Non-literal string found at position %d", + position + ); + return -1; + } + + return 0; +} + +/* {{{ */ +PHP_FUNCTION(literal_combine) +{ + zval *piece; + zval *pieces; + int pieces_count = -1; + + zval pieces_all; + int position = 0; + int ok; + + array_init(&pieces_all); + + ZEND_PARSE_PARAMETERS_START(1, -1) + Z_PARAM_ZVAL(piece) + Z_PARAM_VARIADIC('+', pieces, pieces_count) + ZEND_PARSE_PARAMETERS_END(); + + add_next_index_zval(&pieces_all, piece); + + for (position = 0; position < pieces_count; position++) { + ok = check_is_literal_or_int(&pieces[position], position); + if (ok != 0) { + // Exception is set inside check_is_literal_int_or_bool + RETURN_THROWS(); + } + add_next_index_zval(&pieces_all, &pieces[position]); + } + + zend_string *glue = zend_string_init("", sizeof("") - 1, 0); + php_implode(glue, Z_ARRVAL(pieces_all), return_value); + Z_SET_IS_LITERAL_P(return_value); +} +/* }}} */ + + +/* {{{ */ +PHP_FUNCTION(literal_implode) +{ + zval *pieces; + zval *piece; + zend_string *glue = NULL; + int position = 0; + int ok; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_STR(glue) + Z_PARAM_ARRAY(pieces) + ZEND_PARSE_PARAMETERS_END(); + +// TODO - need macro for inspecting zend_string->refcounted +// if(!Z_IS_LITERAL(*glue)) { +// zend_throw_exception_ex( +// zend_ce_type_error, +// 0, +// "Non-literal string found at position, %d", +// position +// ); +// RETURN_THROWS(); +// } + + ZEND_HASH_FOREACH_VAL(Z_ARRVAL_P(pieces), piece) { + ok = check_is_literal_or_int(piece, position); + if (ok != 0) { + // Exception is set inside check_is_literal_int_or_bool + RETURN_THROWS(); + } + position += 1; + } ZEND_HASH_FOREACH_END(); + + php_implode(glue, Z_ARRVAL_P(pieces), return_value); + Z_SET_IS_LITERAL_P(return_value); +} +/* }}} */ + + diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 99d0d3c63db7a..7677106e0b887 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -373,6 +373,12 @@ function config_get_hash(): array {} function sys_getloadavg(): array|false {} #endif +function literal_set(string $string): void{} + +function literal_implode(string $glue, array $pieces): string {} + +function literal_combine(string $piece, string ...$pieces): string {} + /* browscap.c */ function get_browser(?string $user_agent = null, bool $return_array = false): object|array|false {} @@ -1404,6 +1410,8 @@ function is_float(mixed $value): bool {} /** @alias is_float */ function is_double(mixed $value): bool {} +function is_literal(string $value): bool {} + function is_numeric(mixed $value): bool {} function is_string(mixed $value): bool {} diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 5eafa59b50bd7..fe1986f50e08d 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 7540039937587f05584660bc1a1a8a80aa5ccbd1 */ + * Stub hash: a5270a2c42379d5c34c8355ab769bb0c8685a998 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -585,6 +585,20 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_sys_getloadavg, 0, 0, MAY_BE_ARR ZEND_END_ARG_INFO() #endif +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_literal_set, 0, 1, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_literal_implode, 0, 2, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, glue, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, pieces, IS_ARRAY, 0) +ZEND_END_ARG_INFO() + +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_literal_combine, 0, 1, IS_STRING, 0) + ZEND_ARG_TYPE_INFO(0, piece, IS_STRING, 0) + ZEND_ARG_VARIADIC_TYPE_INFO(0, pieces, IS_STRING, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_get_browser, 0, 0, MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, user_agent, IS_STRING, 1, "null") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, return_array, _IS_BOOL, 0, "false") @@ -2072,6 +2086,10 @@ ZEND_END_ARG_INFO() #define arginfo_is_double arginfo_boolval +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_is_literal, 0, 1, _IS_BOOL, 0) + ZEND_ARG_TYPE_INFO(0, value, IS_STRING, 0) +ZEND_END_ARG_INFO() + #define arginfo_is_numeric arginfo_boolval #define arginfo_is_string arginfo_boolval @@ -2380,6 +2398,9 @@ ZEND_FUNCTION(config_get_hash); #if defined(HAVE_GETLOADAVG) ZEND_FUNCTION(sys_getloadavg); #endif +ZEND_FUNCTION(literal_set); +ZEND_FUNCTION(literal_implode); +ZEND_FUNCTION(literal_combine); ZEND_FUNCTION(get_browser); ZEND_FUNCTION(crc32); ZEND_FUNCTION(crypt); @@ -2783,6 +2804,7 @@ ZEND_FUNCTION(is_resource); ZEND_FUNCTION(is_bool); ZEND_FUNCTION(is_int); ZEND_FUNCTION(is_float); +ZEND_FUNCTION(is_literal); ZEND_FUNCTION(is_numeric); ZEND_FUNCTION(is_string); ZEND_FUNCTION(is_array); @@ -3007,6 +3029,9 @@ static const zend_function_entry ext_functions[] = { #if defined(HAVE_GETLOADAVG) ZEND_FE(sys_getloadavg, arginfo_sys_getloadavg) #endif + ZEND_FE(literal_set, arginfo_literal_set) + ZEND_FE(literal_implode, arginfo_literal_implode) + ZEND_FE(literal_combine, arginfo_literal_combine) ZEND_FE(get_browser, arginfo_get_browser) ZEND_FE(crc32, arginfo_crc32) ZEND_FE(crypt, arginfo_crypt) @@ -3435,6 +3460,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FALIAS(is_long, is_int, arginfo_is_long) ZEND_FE(is_float, arginfo_is_float) ZEND_FALIAS(is_double, is_float, arginfo_is_double) + ZEND_FE(is_literal, arginfo_is_literal) ZEND_FE(is_numeric, arginfo_is_numeric) ZEND_FE(is_string, arginfo_is_string) ZEND_FE(is_array, arginfo_is_array) diff --git a/ext/standard/tests/is_literal/is_literal_basic.phpt b/ext/standard/tests/is_literal/is_literal_basic.phpt new file mode 100644 index 0000000000000..d7f84f10643de --- /dev/null +++ b/ext/standard/tests/is_literal/is_literal_basic.phpt @@ -0,0 +1,94 @@ +--TEST-- +Test is_literal() function +--FILE-- +instance_property; + } +} + +// class constant +if (is_literal(Foo::CLASS_CONST) === true) { + echo "class constant is literal\n"; +} +else { + echo "class constant is NOT literal\n"; +} + + +if (is_literal(Foo::$static_property) === true) { + echo "class static property is literal\n"; +} +else { + echo "class static property is NOT literal\n"; +} + +$foo = new Foo(); +if (is_literal($foo->getInstanceProperty()) === true) { + echo "class instance property is literal\n"; +} +else { + echo "class instance property is NOT literal\n"; +} + +define('CONST_VALUE', 'foobar'); + +if (is_literal(CONST_VALUE) === true) { + echo "constant is literal\n"; +} +else { + echo "constant is NOT literal\n"; +} + + +echo "Done\n"; + +?> +--EXPECTF-- +single char string as parameter is literal +single char string as variable is literal +string as parameter is literal +string as variable is literal +class constant is literal +class static property is literal +class instance property is literal +constant is literal +Done diff --git a/ext/standard/tests/is_literal/literal_combine.phpt b/ext/standard/tests/is_literal/literal_combine.phpt new file mode 100644 index 0000000000000..cb88f3d3dafdf --- /dev/null +++ b/ext/standard/tests/is_literal/literal_combine.phpt @@ -0,0 +1,49 @@ +--TEST-- +Test is_literal() function +--FILE-- +getMessage(), "\n"; +} + + +try { + literal_combine($zok, $fot, new StdClass); + echo "literal_combine failed to throw exception for incorrect type."; +} +catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} + +echo "Done\n"; + +?> +--EXPECTF-- +Result of literal_combine is correctly a literal. +Non-literal string found at position, 1 +Only literal strings and ints allowed. Found bad type at position %d +Done diff --git a/ext/standard/tests/is_literal/literal_implode.phpt b/ext/standard/tests/is_literal/literal_implode.phpt new file mode 100644 index 0000000000000..d909219b49f3d --- /dev/null +++ b/ext/standard/tests/is_literal/literal_implode.phpt @@ -0,0 +1,55 @@ +--TEST-- +Test is_literal() function +--FILE-- +getMessage(), "\n"; +} + + +$pieces = [$question_mark, $non_literal_string, $question_mark]; + +try { + $result = literal_implode($glue, $pieces); + echo "literal_implode failed to throw exception for non-literal piece."; +} +catch(TypeError $e) { + echo $e->getMessage(), "\n"; +} + + +echo "Done\n"; + +?> +--EXPECTF-- +combined string: '?, ?, ?' +imploded string is correctly literal +glue must be literal string or int +Only literal strings and ints allowed. Found bad type at position %d +Done diff --git a/ext/standard/type.c b/ext/standard/type.c index 1036dd7d06615..d03f6e1abab33 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -457,3 +457,28 @@ PHP_FUNCTION(is_countable) RETURN_BOOL(zend_is_countable(var)); } /* }}} */ + +/* {{{ Returns true if value is a literal */ +PHP_FUNCTION(is_literal) +{ + zval *arg; + + ZEND_PARSE_PARAMETERS_START(1, 1) + Z_PARAM_ZVAL(arg) + ZEND_PARSE_PARAMETERS_END(); + + switch (Z_TYPE_P(arg)) { + case IS_STRING: + if (Z_IS_LITERAL_P(arg)) { + RETURN_TRUE; + } else { + RETURN_FALSE; + } + break; + + default: + RETURN_FALSE; + break; + } +} +/* }}} */ diff --git a/literal_test.php b/literal_test.php new file mode 100644 index 0000000000000..980ce467693b6 --- /dev/null +++ b/literal_test.php @@ -0,0 +1,6 @@ +