From 2c5ed50d5c3efdbd2f24110911bc142866e4eca4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Mon, 17 Jun 2024 17:07:50 +0200 Subject: [PATCH] zend_compile: Add support for `%d` to `sprintf()` optimization (#14561) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * zend_compile: Rename `string_placeholder_count` to `placeholder_count` in `zend_compile_func_sprintf()` This is intended to make the diff of a follow-up commit smaller. * zend_compile: Add support for `%d` to `sprintf()` optimization This extends the existing `sprintf()` optimization by support for the `%d` placeholder, which effectively equivalent to an `(int)` cast followed by a `(string)` cast. For a synthetic test using: children - 1)) { + if (placeholder_count != (args->children - 1)) { return FAILURE; } @@ -4785,27 +4786,22 @@ static zend_result zend_compile_func_sprintf(znode *result, zend_ast_list *args) znode *elements = NULL; - if (string_placeholder_count > 0) { - elements = safe_emalloc(sizeof(*elements), string_placeholder_count, 0); + if (placeholder_count > 0) { + elements = safe_emalloc(sizeof(*elements), placeholder_count, 0); } /* Compile the value expressions first for error handling that is consistent * with a function call: Values that fail to convert to a string may emit errors. */ - for (uint32_t i = 0; i < string_placeholder_count; i++) { + for (uint32_t i = 0; i < placeholder_count; i++) { zend_compile_expr(elements + i, args->child[1 + i]); - if (elements[i].op_type == IS_CONST) { - if (Z_TYPE(elements[i].u.constant) != IS_ARRAY) { - convert_to_string(&elements[i].u.constant); - } - } } uint32_t rope_elements = 0; uint32_t rope_init_lineno = -1; zend_op *opline = NULL; - string_placeholder_count = 0; + placeholder_count = 0; p = Z_STRVAL_P(format_string); end = p + Z_STRLEN_P(format_string); char *offset = p; @@ -4817,7 +4813,7 @@ static zend_result zend_compile_func_sprintf(znode *result, zend_ast_list *args) char *q = p + 1; ZEND_ASSERT(q < end); - ZEND_ASSERT(*q == 's' || *q == '%'); + ZEND_ASSERT(*q == 's' || *q == 'd' || *q == '%'); if (*q == '%') { /* Optimization to not create a dedicated rope element for the literal '%': @@ -4837,21 +4833,32 @@ static zend_result zend_compile_func_sprintf(znode *result, zend_ast_list *args) opline = zend_compile_rope_add(result, rope_elements++, &const_node); } - if (*q == 's') { - /* Perform the cast of constant arrays when actually evaluating corresponding placeholder - * for correct error reporting. - */ - if (elements[string_placeholder_count].op_type == IS_CONST) { - if (Z_TYPE(elements[string_placeholder_count].u.constant) == IS_ARRAY) { - zend_emit_op_tmp(&elements[string_placeholder_count], ZEND_CAST, &elements[string_placeholder_count], NULL)->extended_value = IS_STRING; - } + if (*q != '%') { + switch (*q) { + case 's': + /* Perform the cast of constants when actually evaluating the corresponding placeholder + * for correct error reporting. + */ + if (elements[placeholder_count].op_type == IS_CONST) { + if (Z_TYPE(elements[placeholder_count].u.constant) == IS_ARRAY) { + zend_emit_op_tmp(&elements[placeholder_count], ZEND_CAST, &elements[placeholder_count], NULL)->extended_value = IS_STRING; + } else { + convert_to_string(&elements[placeholder_count].u.constant); + } + } + break; + case 'd': + zend_emit_op_tmp(&elements[placeholder_count], ZEND_CAST, &elements[placeholder_count], NULL)->extended_value = IS_LONG; + break; + EMPTY_SWITCH_DEFAULT_CASE(); } + if (rope_elements == 0) { rope_init_lineno = get_next_op_number(); } - opline = zend_compile_rope_add(result, rope_elements++, &elements[string_placeholder_count]); + opline = zend_compile_rope_add(result, rope_elements++, &elements[placeholder_count]); - string_placeholder_count++; + placeholder_count++; } p = q; diff --git a/ext/standard/tests/strings/sprintf_rope_optimization_001.phpt b/ext/standard/tests/strings/sprintf_rope_optimization_001.phpt index 91fdddfe029..634e575729d 100644 --- a/ext/standard/tests/strings/sprintf_rope_optimization_001.phpt +++ b/ext/standard/tests/strings/sprintf_rope_optimization_001.phpt @@ -100,6 +100,10 @@ try { var_dump(sprintf()); } catch (\Throwable $e) {echo $e, PHP_EOL; } echo PHP_EOL; +try { + var_dump(sprintf('%s-%s-%s', true, false, true)); +} catch (\Throwable $e) {echo $e, PHP_EOL; } echo PHP_EOL; + echo "Done"; ?> --EXPECTF-- @@ -173,4 +177,6 @@ Stack trace: #0 %s(97): sprintf() #1 {main} +string(4) "1--1" + Done diff --git a/ext/standard/tests/strings/sprintf_rope_optimization_003.phpt b/ext/standard/tests/strings/sprintf_rope_optimization_003.phpt new file mode 100644 index 00000000000..9a0d819b277 --- /dev/null +++ b/ext/standard/tests/strings/sprintf_rope_optimization_003.phpt @@ -0,0 +1,134 @@ +--TEST-- +Test sprintf() function : Rope Optimization for '%d'. +--FILE-- + +--EXPECTF-- +string(2) "42" + +string(8) "42/-1337" + +string(10) "42/-1337/3" + + +Warning: Object of class stdClass could not be converted to int in %s on line 33 +string(12) "42/-1337/3/1" + +string(2) "1/" + +string(2) "/1" + +string(3) "/1/" + +string(13) "42/-1337/1/42" + +string(8) "0/53/1/3" + +Called +Called +Called + +Warning: Object of class Foo could not be converted to int in %s on line 57 + +Warning: Object of class Foo could not be converted to int in %s on line 57 + +Warning: Object of class Foo could not be converted to int in %s on line 57 +string(5) "1/1/1" + +string(5) "0/0/0" + +string(42) "9223372036854775807/0/-9223372036854775808" +string(24) "2147483647/0/-2147483648" + +string(5) "1/0/1" + +string(3) "1/0" + +string(1) "0" + +Done diff --git a/ext/standard/tests/strings/sprintf_rope_optimization_004.phpt b/ext/standard/tests/strings/sprintf_rope_optimization_004.phpt new file mode 100644 index 00000000000..a1c937aeb2c --- /dev/null +++ b/ext/standard/tests/strings/sprintf_rope_optimization_004.phpt @@ -0,0 +1,21 @@ +--TEST-- +Test sprintf() function : Rope Optimization for '%d' with GMP objects +--EXTENSIONS-- +gmp +--FILE-- + +--EXPECTF-- +string(63) "42/-1337/4089650035136921599/1000000000000000000000000000000000" + +Done