]> granicus.if.org Git - php/commitdiff
Exception safety for direct CV assign of *CALL instrs
authorNikita Popov <nikic@php.net>
Fri, 17 Jun 2016 22:05:58 +0000 (00:05 +0200)
committerNikita Popov <nikic@php.net>
Fri, 17 Jun 2016 22:08:38 +0000 (00:08 +0200)
Converting T = *CALL; ASSIGN $v, T into $v = *CALL may not be
safe if an exception is thrown after the return value has been
populated -- in this case the value might be destroyed twice.

ext/opcache/Optimizer/dfa_pass.c
ext/opcache/tests/ssa_bug_004.phpt [new file with mode: 0644]
ext/opcache/tests/ssa_bug_005.phpt [new file with mode: 0644]

index f89094edec50f40978e38abde9e63d8bdad5463c..a64619bfe8b6540ee07baa4ca29d2b2262ed886b 100644 (file)
@@ -343,6 +343,24 @@ static inline zend_bool can_elide_return_type_check(
        return 1;
 }
 
+static zend_bool opline_supports_assign_contraction(zend_ssa *ssa, zend_op *opline, int src_var) {
+       if (opline->opcode == ZEND_NEW) {
+               /* see Zend/tests/generators/aborted_yield_during_new.phpt */
+               return 0;
+       }
+
+       if (opline->opcode == ZEND_DO_ICALL || opline->opcode == ZEND_DO_UCALL
+                       || opline->opcode == ZEND_DO_FCALL || opline->opcode == ZEND_DO_FCALL_BY_NAME) {
+               /* Function calls may dtor the return value after it has already been written -- allow
+                * direct assignment only for types where a double-dtor does not matter. */
+               uint32_t type = ssa->var_info[src_var].type;
+               uint32_t simple = MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE;
+               return !((type & MAY_BE_ANY) & ~simple);
+       }
+
+       return 1;
+}
+
 void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx, zend_ssa *ssa)
 {
        if (ctx->debug_level & ZEND_DUMP_BEFORE_DFA_PASS) {
@@ -457,8 +475,8 @@ void zend_dfa_optimize_op_array(zend_op_array *op_array, zend_optimizer_ctx *ctx
                                         && ssa->ops[op_1].op2_use_chain < 0
                                         && !ssa->vars[src_var].phi_use_chain
                                         && !ssa->vars[src_var].sym_use_chain
-                                        /* see Zend/tests/generators/aborted_yield_during_new.phpt */
-                                        && op_array->opcodes[ssa->vars[src_var].definition].opcode != ZEND_NEW
+                                        && opline_supports_assign_contraction(
+                                                ssa, &op_array->opcodes[ssa->vars[src_var].definition], src_var)
                                        ) {
 
                                                int op_2 = ssa->vars[src_var].definition;
diff --git a/ext/opcache/tests/ssa_bug_004.phpt b/ext/opcache/tests/ssa_bug_004.phpt
new file mode 100644 (file)
index 0000000..20e3130
--- /dev/null
@@ -0,0 +1,19 @@
+--TEST--
+Assign elision exception safety: ICALL
+--FILE--
+<?php
+
+set_error_handler(function() { throw new Exception; });
+
+function test() {
+    $x = str_replace(['foo'], [[]], ['foo']);
+}
+try {
+    test();
+} catch (Exception $e) {
+    echo "caught\n";
+}
+
+?>
+--EXPECT--
+caught
diff --git a/ext/opcache/tests/ssa_bug_005.phpt b/ext/opcache/tests/ssa_bug_005.phpt
new file mode 100644 (file)
index 0000000..fb373e3
--- /dev/null
@@ -0,0 +1,24 @@
+--TEST--
+Assign elision exception safety: UCALL
+--FILE--
+<?php
+
+function test() {
+    $dtor = new class { function __destruct() { throw new Exception; } };
+    $a = 1;
+    return [0, $a];
+}
+
+function test2() {
+    $x = test();
+}
+
+try {
+    test2();
+} catch (Exception $e) {
+    echo "caught\n";
+}
+
+?>
+--EXPECT--
+caught