Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 109 additions & 35 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -6231,55 +6231,129 @@ void zend_compile_resolve_class_name(znode *result, zend_ast *ast) /* {{{ */
}
/* }}} */

void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
static zend_op *zend_compile_rope_add(znode *result, uint32_t num, znode *elem_node) /* {{{ */
{
zend_ast_list *list = zend_ast_get_list(ast);
uint32_t i;
zend_op *opline = get_next_op(CG(active_op_array));

ZEND_ASSERT(list->children > 0);
if (num == 0) {
result->op_type = IS_TMP_VAR;
result->u.op.var = -1;
opline->opcode = ZEND_ROPE_INIT;
SET_UNUSED(opline->op1);
} else {
opline->opcode = ZEND_ROPE_ADD;
SET_NODE(opline->op1, result);
}
SET_NODE(opline->op2, elem_node);
SET_NODE(opline->result, result);
opline->extended_value = num;
return opline;
}
/* }}} */

result->op_type = IS_TMP_VAR;
result->u.op.var = get_temporary_variable(CG(active_op_array));
static void zend_compile_encaps_list(znode *result, zend_ast *ast) /* {{{ */
{
uint32_t i, j;
uint32_t rope_init_lineno = -1;
zend_op *opline = NULL, *init_opline;
znode elem_node, last_const_node;
zend_ast_list *list = zend_ast_get_list(ast);

for (i = 0; i < list->children; ++i) {
zend_ast *elem_ast = list->child[i];
znode elem_node;
zend_op *opline;
ZEND_ASSERT(list->children > 0);

zend_compile_expr(&elem_node, elem_ast);
j = 0;
last_const_node.op_type = IS_UNUSED;
for (i = 0; i < list->children; i++) {
zend_compile_expr(&elem_node, list->child[i]);

if (elem_ast->kind == ZEND_AST_ZVAL) {
zval *zv = &elem_node.u.constant;
ZEND_ASSERT(Z_TYPE_P(zv) == IS_STRING);
if (elem_node.op_type == IS_CONST) {
convert_to_string(&elem_node.u.constant);

if (Z_STRLEN_P(zv) > 1) {
opline = get_next_op(CG(active_op_array));
opline->opcode = ZEND_ADD_STRING;
} else if (Z_STRLEN_P(zv) == 1) {
char ch = *Z_STRVAL_P(zv);
zend_string_release(Z_STR_P(zv));
ZVAL_LONG(zv, ch);

opline = get_next_op(CG(active_op_array));
opline->opcode = ZEND_ADD_CHAR;
if (Z_STRLEN(elem_node.u.constant) == 0) {
zval_ptr_dtor(&elem_node.u.constant);
} else if (last_const_node.op_type == IS_CONST) {
concat_function(&last_const_node.u.constant, &last_const_node.u.constant, &elem_node.u.constant);
zval_ptr_dtor(&elem_node.u.constant);
} else {
/* String can be empty after a variable at the end of a heredoc */
zend_string_release(Z_STR_P(zv));
continue;
last_const_node.op_type = IS_CONST;
ZVAL_COPY_VALUE(&last_const_node.u.constant, &elem_node.u.constant);
}
continue;
} else {
opline = get_next_op(CG(active_op_array));
opline->opcode = ZEND_ADD_VAR;
ZEND_ASSERT(elem_node.op_type != IS_CONST);
if (j == 0) {
rope_init_lineno = get_next_op_number(CG(active_op_array));
}
if (last_const_node.op_type == IS_CONST) {
zend_compile_rope_add(result, j++, &last_const_node);
last_const_node.op_type = IS_UNUSED;
}
opline = zend_compile_rope_add(result, j++, &elem_node);
}
}

if (i == 0) {
SET_UNUSED(opline->op1);
if (j == 0) {
result->op_type = IS_CONST;
if (last_const_node.op_type == IS_CONST) {
ZVAL_COPY_VALUE(&result->u.constant, &last_const_node.u.constant);
} else {
ZVAL_EMPTY_STRING(&result->u.constant);
/* empty string */
}
return;
} else if (last_const_node.op_type == IS_CONST) {
opline = zend_compile_rope_add(result, j++, &last_const_node);
}
init_opline = CG(active_op_array)->opcodes + rope_init_lineno;
if (j == 1) {
if (opline->op2_type == IS_CONST) {
GET_NODE(result, opline->op2);
MAKE_NOP(opline);
} else {
SET_NODE(opline->op1, result);
opline->opcode = ZEND_CAST;
opline->extended_value = IS_STRING;
opline->op1_type = opline->op2_type;
opline->op1 = opline->op2;
opline->result_type = IS_TMP_VAR;
opline->result.var = get_temporary_variable(CG(active_op_array));
SET_UNUSED(opline->op2);
GET_NODE(result, opline->result);
}
} else if (j == 2) {
opline->opcode = ZEND_FAST_CONCAT;
opline->extended_value = 0;
opline->op1_type = init_opline->op2_type;
opline->op1 = init_opline->op2;
opline->result_type = IS_TMP_VAR;
opline->result.var = get_temporary_variable(CG(active_op_array));
MAKE_NOP(init_opline);
GET_NODE(result, opline->result);
} else {
uint32_t var;

init_opline->extended_value = j;
opline->opcode = ZEND_ROPE_END;
opline->result.var = get_temporary_variable(CG(active_op_array));
var = opline->op1.var = get_temporary_variable(CG(active_op_array));
GET_NODE(result, opline->result);

/* Allocates the necessary number of zval slots to keep the rope */
i = ((j * sizeof(zend_string*)) + (sizeof(zval) - 1)) / sizeof(zval);
while (i > 1) {
get_temporary_variable(CG(active_op_array));
i--;
}
/* Update all the previous opcodes to use the same variable */
while (opline != init_opline) {
opline--;
if (opline->opcode == ZEND_ROPE_ADD &&
opline->result.var == -1) {
opline->op1.var = var;
opline->result.var = var;
} else if (opline->opcode == ZEND_ROPE_INIT &&
opline->result.var == -1) {
opline->result.var = var;
}
}
SET_NODE(opline->op2, &elem_node);
SET_NODE(opline->result, result);
}
}
/* }}} */
Expand Down
1 change: 1 addition & 0 deletions Zend/zend_opcode.c
Original file line number Diff line number Diff line change
Expand Up @@ -861,6 +861,7 @@ ZEND_API binary_op_type get_binary_op(int opcode)
case ZEND_SR:
case ZEND_ASSIGN_SR:
return (binary_op_type) shift_right_function;
case ZEND_FAST_CONCAT:
case ZEND_CONCAT:
case ZEND_ASSIGN_CONCAT:
return (binary_op_type) concat_function;
Expand Down
140 changes: 89 additions & 51 deletions Zend/zend_vm_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -2571,82 +2571,120 @@ ZEND_VM_HANDLER(127, ZEND_FE_FREE, TMPVAR, ANY)
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HANDLER(54, ZEND_ADD_CHAR, TMP|UNUSED, CONST)
ZEND_VM_HANDLER(53, ZEND_FAST_CONCAT, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
USE_OPLINE
zval *str = EX_VAR(opline->result.var);
zend_free_op free_op1, free_op2;
zval *op1, *op2;
zend_string *op1_str, *op2_str, *str;

SAVE_OPLINE();

if (OP1_TYPE == IS_UNUSED) {
/* Initialize for erealloc in add_char_to_string */
ZVAL_EMPTY_STRING(str);
op1 = GET_OP1_ZVAL_PTR(BP_VAR_R);
op2 = GET_OP2_ZVAL_PTR(BP_VAR_R);
if (OP1_TYPE == IS_CONST) {
op1_str = Z_STR_P(op1);
} else {
op1_str = zval_get_string(op1);
}

add_char_to_string(str, str, EX_CONSTANT(opline->op2));

/* FREE_OP is missing intentionally here - we're always working on the same temporary variable */
/*CHECK_EXCEPTION();*/
if (OP2_TYPE == IS_CONST) {
op2_str = Z_STR_P(op2);
} else {
op2_str = zval_get_string(op2);
}
str = zend_string_alloc(op1_str->len + op2_str->len, 0);
memcpy(str->val, op1_str->val, op1_str->len);
memcpy(str->val + op1_str->len, op2_str->val, op2_str->len+1);
ZVAL_NEW_STR(EX_VAR(opline->result.var), str);
if (OP1_TYPE != IS_CONST) {
zend_string_release(op1_str);
}
if (OP2_TYPE != IS_CONST) {
zend_string_release(op2_str);
}
FREE_OP1();
FREE_OP2();
CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HANDLER(55, ZEND_ADD_STRING, TMP|UNUSED, CONST)
ZEND_VM_HANDLER(54, ZEND_ROPE_INIT, UNUSED, CONST|TMPVAR|CV)
{
USE_OPLINE
zval *str = EX_VAR(opline->result.var);

SAVE_OPLINE();
zend_free_op free_op2;
zend_string **rope;
zval *var;

if (OP1_TYPE == IS_UNUSED) {
/* Initialize for erealloc in add_string_to_string */
ZVAL_EMPTY_STRING(str);
/* Compiler allocates the necessary number of zval slots to keep the rope */
rope = (zend_string**)EX_VAR(opline->result.var);
if (OP2_TYPE == IS_CONST) {
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
rope[0] = zend_string_copy(Z_STR_P(var));
} else {
SAVE_OPLINE();
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
rope[0] = zval_get_string(var);
FREE_OP2();
CHECK_EXCEPTION();
}

add_string_to_string(str, str, EX_CONSTANT(opline->op2));

/* FREE_OP is missing intentionally here - we're always working on the same temporary variable */
/*CHECK_EXCEPTION();*/
ZEND_VM_NEXT_OPCODE();
}

ZEND_VM_HANDLER(56, ZEND_ADD_VAR, TMP|UNUSED, TMPVAR|CV)
ZEND_VM_HANDLER(55, ZEND_ROPE_ADD, TMP, CONST|TMPVAR|CV)
{
USE_OPLINE
zend_free_op free_op2;
zval *str = EX_VAR(opline->result.var);
zend_string **rope;
zval *var;
zval var_copy;
int use_copy = 0;

SAVE_OPLINE();
var = GET_OP2_ZVAL_PTR(BP_VAR_R);

if (OP1_TYPE == IS_UNUSED) {
/* Initialize for erealloc in add_string_to_string */
ZVAL_EMPTY_STRING(str);
/* op1 and result are the same */
rope = (zend_string**)EX_VAR(opline->op1.var);
if (OP2_TYPE == IS_CONST) {
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
rope[opline->extended_value] = zend_string_copy(Z_STR_P(var));
} else {
SAVE_OPLINE();
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
rope[opline->extended_value] = zval_get_string(var);
FREE_OP2();
CHECK_EXCEPTION();
}
ZEND_VM_NEXT_OPCODE();
}

if (Z_TYPE_P(var) != IS_STRING) {
use_copy = zend_make_printable_zval(var, &var_copy);

if (use_copy) {
var = &var_copy;
}
ZEND_VM_HANDLER(56, ZEND_ROPE_END, TMP, CONST|TMPVAR|CV)
{
USE_OPLINE
zend_free_op free_op2;
zend_string **rope;
zval *var, *ret;
uint32_t i;
size_t len = 0;
char *target;

rope = (zend_string**)EX_VAR(opline->op1.var);
if (OP2_TYPE == IS_CONST) {
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
rope[opline->extended_value] = zend_string_copy(Z_STR_P(var));
} else {
SAVE_OPLINE();
var = GET_OP2_ZVAL_PTR(BP_VAR_R);
rope[opline->extended_value] = zval_get_string(var);
FREE_OP2();
CHECK_EXCEPTION();
}
add_string_to_string(str, str, var);

if (use_copy) {
zend_string_release(Z_STR_P(var));
for (i = 0; i <= opline->extended_value; i++) {
len += rope[i]->len;
}
/* original comment, possibly problematic:
* FREE_OP is missing intentionally here - we're always working on the same temporary variable
* (Zeev): I don't think it's problematic, we only use variables
* which aren't affected by FREE_OP(Ts, )'s anyway, unless they're
* string offsets or overloaded objects
*/
FREE_OP2();
ret = EX_VAR(opline->result.var);
ZVAL_STR(ret, zend_string_alloc(len, 0));
target = Z_STRVAL_P(ret);
for (i = 0; i <= opline->extended_value; i++) {
memcpy(target, rope[i]->val, rope[i]->len);
target += rope[i]->len;
zend_string_release(rope[i]);
}
*target = '\0';

CHECK_EXCEPTION();
ZEND_VM_NEXT_OPCODE();
}

Expand Down
Loading