summaryrefslogtreecommitdiff
path: root/Zend/Optimizer/zend_inference.c
diff options
context:
space:
mode:
Diffstat (limited to 'Zend/Optimizer/zend_inference.c')
-rw-r--r--Zend/Optimizer/zend_inference.c4677
1 files changed, 4677 insertions, 0 deletions
diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c
new file mode 100644
index 0000000000..a61a18176c
--- /dev/null
+++ b/Zend/Optimizer/zend_inference.c
@@ -0,0 +1,4677 @@
+/*
+ +----------------------------------------------------------------------+
+ | Zend Engine, e-SSA based Type & Range Inference |
+ +----------------------------------------------------------------------+
+ | Copyright (c) The PHP Group |
+ +----------------------------------------------------------------------+
+ | This source file is subject to version 3.01 of the PHP license, |
+ | that is bundled with this package in the file LICENSE, and is |
+ | available through the world-wide-web at the following url: |
+ | http://www.php.net/license/3_01.txt |
+ | If you did not receive a copy of the PHP license and are unable to |
+ | obtain it through the world-wide-web, please send a note to |
+ | license@php.net so we can mail you a copy immediately. |
+ +----------------------------------------------------------------------+
+ | Authors: Dmitry Stogov <dmitry@php.net> |
+ +----------------------------------------------------------------------+
+*/
+
+#include "php.h"
+#include "zend_compile.h"
+#include "zend_generators.h"
+#include "zend_inference.h"
+#include "zend_func_info.h"
+#include "zend_call_graph.h"
+#include "zend_worklist.h"
+
+/* The used range inference algorithm is described in:
+ * V. Campos, R. Rodrigues, I. de Assis Costa and F. Pereira.
+ * "Speed and Precision in Range Analysis", SBLP'12.
+ *
+ * There are a couple degrees of freedom, we use:
+ * * Propagation on SCCs.
+ * * e-SSA for live range splitting.
+ * * Only intra-procedural inference.
+ * * Widening with warmup passes, but without jump sets.
+ */
+
+/* Whether to handle symbolic range constraints */
+#define SYM_RANGE
+
+/* Whether to handle negative range constraints */
+/* Negative range inference is buggy, so disabled for now */
+#undef NEG_RANGE
+
+/* Number of warmup passes to use prior to widening */
+#define RANGE_WARMUP_PASSES 16
+
+/* Logging for range inference in general */
+#if 0
+#define LOG_SSA_RANGE(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define LOG_SSA_RANGE(...)
+#endif
+
+/* Logging for negative range constraints */
+#if 0
+#define LOG_NEG_RANGE(...) fprintf(stderr, __VA_ARGS__)
+#else
+#define LOG_NEG_RANGE(...)
+#endif
+
+/* Pop elements in unspecified order from worklist until it is empty */
+#define WHILE_WORKLIST(worklist, len, i) do { \
+ bool _done = 0; \
+ while (!_done) { \
+ _done = 1; \
+ ZEND_BITSET_FOREACH(worklist, len, i) { \
+ zend_bitset_excl(worklist, i); \
+ _done = 0;
+
+#define WHILE_WORKLIST_END() \
+ } ZEND_BITSET_FOREACH_END(); \
+ } \
+} while (0)
+
+#define CHECK_SCC_VAR(var2) \
+ do { \
+ if (!ssa->vars[var2].no_val) { \
+ if (dfs[var2] < 0) { \
+ zend_ssa_check_scc_var(op_array, ssa, var2, index, dfs, root, stack); \
+ } \
+ if (ssa->vars[var2].scc < 0 && dfs[root[var]] >= dfs[root[var2]]) { \
+ root[var] = root[var2]; \
+ } \
+ } \
+ } while (0)
+
+#define CHECK_SCC_ENTRY(var2) \
+ do { \
+ if (ssa->vars[var2].scc != ssa->vars[var].scc) { \
+ ssa->vars[var2].scc_entry = 1; \
+ } \
+ } while (0)
+
+#define ADD_SCC_VAR(_var) \
+ do { \
+ if (ssa->vars[_var].scc == scc) { \
+ zend_bitset_incl(worklist, _var); \
+ } \
+ } while (0)
+
+#define ADD_SCC_VAR_1(_var) \
+ do { \
+ if (ssa->vars[_var].scc == scc && \
+ !zend_bitset_in(visited, _var)) { \
+ zend_bitset_incl(worklist, _var); \
+ } \
+ } while (0)
+
+#define FOR_EACH_DEFINED_VAR(line, MACRO) \
+ do { \
+ if (ssa->ops[line].op1_def >= 0) { \
+ MACRO(ssa->ops[line].op1_def); \
+ } \
+ if (ssa->ops[line].op2_def >= 0) { \
+ MACRO(ssa->ops[line].op2_def); \
+ } \
+ if (ssa->ops[line].result_def >= 0) { \
+ MACRO(ssa->ops[line].result_def); \
+ } \
+ if (op_array->opcodes[line].opcode == ZEND_OP_DATA) { \
+ if (ssa->ops[line-1].op1_def >= 0) { \
+ MACRO(ssa->ops[line-1].op1_def); \
+ } \
+ if (ssa->ops[line-1].op2_def >= 0) { \
+ MACRO(ssa->ops[line-1].op2_def); \
+ } \
+ if (ssa->ops[line-1].result_def >= 0) { \
+ MACRO(ssa->ops[line-1].result_def); \
+ } \
+ } else if ((uint32_t)line+1 < op_array->last && \
+ op_array->opcodes[line+1].opcode == ZEND_OP_DATA) { \
+ if (ssa->ops[line+1].op1_def >= 0) { \
+ MACRO(ssa->ops[line+1].op1_def); \
+ } \
+ if (ssa->ops[line+1].op2_def >= 0) { \
+ MACRO(ssa->ops[line+1].op2_def); \
+ } \
+ if (ssa->ops[line+1].result_def >= 0) { \
+ MACRO(ssa->ops[line+1].result_def); \
+ } \
+ } \
+ } while (0)
+
+
+#define FOR_EACH_VAR_USAGE(_var, MACRO) \
+ do { \
+ zend_ssa_phi *p = ssa->vars[_var].phi_use_chain; \
+ int use = ssa->vars[_var].use_chain; \
+ while (use >= 0) { \
+ FOR_EACH_DEFINED_VAR(use, MACRO); \
+ use = zend_ssa_next_use(ssa->ops, _var, use); \
+ } \
+ p = ssa->vars[_var].phi_use_chain; \
+ while (p) { \
+ MACRO(p->ssa_var); \
+ p = zend_ssa_next_use_phi(ssa, _var, p); \
+ } \
+ } while (0)
+
+static inline bool add_will_overflow(zend_long a, zend_long b) {
+ return (b > 0 && a > ZEND_LONG_MAX - b)
+ || (b < 0 && a < ZEND_LONG_MIN - b);
+}
+#if 0
+static inline bool sub_will_overflow(zend_long a, zend_long b) {
+ return (b > 0 && a < ZEND_LONG_MIN + b)
+ || (b < 0 && a > ZEND_LONG_MAX + b);
+}
+#endif
+
+static void zend_ssa_check_scc_var(const zend_op_array *op_array, zend_ssa *ssa, int var, int *index, int *dfs, int *root, zend_worklist_stack *stack) /* {{{ */
+{
+#ifdef SYM_RANGE
+ zend_ssa_phi *p;
+#endif
+
+ dfs[var] = *index;
+ (*index)++;
+ root[var] = var;
+
+ FOR_EACH_VAR_USAGE(var, CHECK_SCC_VAR);
+
+#ifdef SYM_RANGE
+ /* Process symbolic control-flow constraints */
+ p = ssa->vars[var].sym_use_chain;
+ while (p) {
+ CHECK_SCC_VAR(p->ssa_var);
+ p = p->sym_use_chain;
+ }
+#endif
+
+ if (root[var] == var) {
+ ssa->vars[var].scc = ssa->sccs;
+ while (stack->len > 0) {
+ int var2 = zend_worklist_stack_peek(stack);
+ if (dfs[var2] <= dfs[var]) {
+ break;
+ }
+ zend_worklist_stack_pop(stack);
+ ssa->vars[var2].scc = ssa->sccs;
+ }
+ ssa->sccs++;
+ } else {
+ zend_worklist_stack_push(stack, var);
+ }
+}
+/* }}} */
+
+ZEND_API int zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ int index = 0, *dfs, *root;
+ zend_worklist_stack stack;
+ int j;
+ ALLOCA_FLAG(dfs_use_heap)
+ ALLOCA_FLAG(root_use_heap)
+ ALLOCA_FLAG(stack_use_heap)
+
+ dfs = do_alloca(sizeof(int) * ssa->vars_count, dfs_use_heap);
+ memset(dfs, -1, sizeof(int) * ssa->vars_count);
+ root = do_alloca(sizeof(int) * ssa->vars_count, root_use_heap);
+ ZEND_WORKLIST_STACK_ALLOCA(&stack, ssa->vars_count, stack_use_heap);
+
+ /* Find SCCs using Tarjan's algorithm. */
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (!ssa->vars[j].no_val && dfs[j] < 0) {
+ zend_ssa_check_scc_var(op_array, ssa, j, &index, dfs, root, &stack);
+ }
+ }
+
+ /* Revert SCC order. This results in a topological order. */
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (ssa->vars[j].scc >= 0) {
+ ssa->vars[j].scc = ssa->sccs - (ssa->vars[j].scc + 1);
+ }
+ }
+
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (ssa->vars[j].scc >= 0) {
+ int var = j;
+ if (root[j] == j) {
+ ssa->vars[j].scc_entry = 1;
+ }
+ FOR_EACH_VAR_USAGE(var, CHECK_SCC_ENTRY);
+ }
+ }
+
+ ZEND_WORKLIST_STACK_FREE_ALLOCA(&stack, stack_use_heap);
+ free_alloca(root, root_use_heap);
+ free_alloca(dfs, dfs_use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+ZEND_API int zend_ssa_find_false_dependencies(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ zend_ssa_var *ssa_vars = ssa->vars;
+ zend_ssa_op *ssa_ops = ssa->ops;
+ int ssa_vars_count = ssa->vars_count;
+ zend_bitset worklist;
+ int i, j, use;
+ zend_ssa_phi *p;
+ ALLOCA_FLAG(use_heap);
+
+ if (!op_array->function_name || !ssa->vars || !ssa->ops) {
+ return SUCCESS;
+ }
+
+ worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap);
+ memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count));
+
+ for (i = 0; i < ssa_vars_count; i++) {
+ ssa_vars[i].no_val = 1; /* mark as unused */
+ use = ssa->vars[i].use_chain;
+ while (use >= 0) {
+ if (!zend_ssa_is_no_val_use(&op_array->opcodes[use], &ssa->ops[use], i)) {
+ ssa_vars[i].no_val = 0; /* used directly */
+ zend_bitset_incl(worklist, i);
+ break;
+ }
+ use = zend_ssa_next_use(ssa_ops, i, use);
+ }
+ }
+
+ WHILE_WORKLIST(worklist, zend_bitset_len(ssa_vars_count), i) {
+ if (ssa_vars[i].definition_phi) {
+ /* mark all possible sources as used */
+ p = ssa_vars[i].definition_phi;
+ if (p->pi >= 0) {
+ if (ssa_vars[p->sources[0]].no_val) {
+ ssa_vars[p->sources[0]].no_val = 0; /* used indirectly */
+ zend_bitset_incl(worklist, p->sources[0]);
+ }
+ } else {
+ for (j = 0; j < ssa->cfg.blocks[p->block].predecessors_count; j++) {
+ ZEND_ASSERT(p->sources[j] >= 0);
+ if (ssa->vars[p->sources[j]].no_val) {
+ ssa_vars[p->sources[j]].no_val = 0; /* used indirectly */
+ zend_bitset_incl(worklist, p->sources[j]);
+ }
+ }
+ }
+ }
+ } WHILE_WORKLIST_END();
+
+ free_alloca(worklist, use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+/* From "Hacker's Delight" */
+zend_ulong minOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (~a & c & m) {
+ temp = (a | m) & -m;
+ if (temp <= b) {
+ a = temp;
+ break;
+ }
+ } else if (a & ~c & m) {
+ temp = (c | m) & -m;
+ if (temp <= d) {
+ c = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return a | c;
+}
+
+zend_ulong maxOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (b & d & m) {
+ temp = (b - m) | (m - 1);
+ if (temp >= a) {
+ b = temp;
+ break;
+ }
+ temp = (d - m) | (m - 1);
+ if (temp >= c) {
+ d = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return b | d;
+}
+
+zend_ulong minAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (~a & ~c & m) {
+ temp = (a | m) & -m;
+ if (temp <= b) {
+ a = temp;
+ break;
+ }
+ temp = (c | m) & -m;
+ if (temp <= d) {
+ c = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return a & c;
+}
+
+zend_ulong maxAND(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ zend_ulong m, temp;
+
+ m = Z_UL(1) << (sizeof(zend_ulong) * 8 - 1);
+ while (m != 0) {
+ if (b & ~d & m) {
+ temp = (b | ~m) | (m - 1);
+ if (temp >= a) {
+ b = temp;
+ break;
+ }
+ } else if (~b & d & m) {
+ temp = (d | ~m) | (m - 1);
+ if (temp >= c) {
+ d = temp;
+ break;
+ }
+ }
+ m = m >> 1;
+ }
+ return b & d;
+}
+
+zend_ulong minXOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ return minAND(a, b, ~d, ~c) | minAND(~b, ~a, c, d);
+}
+
+zend_ulong maxXOR(zend_ulong a, zend_ulong b, zend_ulong c, zend_ulong d)
+{
+ return maxOR(0, maxAND(a, b, ~d, ~c), 0, maxAND(~b, ~a, c, d));
+}
+
+/* Based on "Hacker's Delight" */
+
+/*
+0: + + + + 0 0 0 0 => 0 0 + min/max
+2: + + - + 0 0 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d)
+3: + + - - 0 0 1 1 => 1 1 - min/max
+8: - + + + 1 0 0 0 => 1 0 ? min(a,-1,b,d)/max(0,b,c,d)
+a: - + - + 1 0 1 0 => 1 0 ? MIN(a,c)/max(0,b,0,d)
+b: - + - - 1 0 1 1 => 1 1 - c/-1
+c: - - + + 1 1 0 0 => 1 1 - min/max
+e: - - - + 1 1 1 0 => 1 1 - a/-1
+f - - - - 1 1 1 1 => 1 1 - min/max
+*/
+static void zend_ssa_range_or(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp)
+{
+ int x = ((a < 0) ? 8 : 0) |
+ ((b < 0) ? 4 : 0) |
+ ((c < 0) ? 2 : 0) |
+ ((d < 0) ? 2 : 0);
+ switch (x) {
+ case 0x0:
+ case 0x3:
+ case 0xc:
+ case 0xf:
+ tmp->min = minOR(a, b, c, d);
+ tmp->max = maxOR(a, b, c, d);
+ break;
+ case 0x2:
+ tmp->min = minOR(a, b, c, -1);
+ tmp->max = maxOR(a, b, 0, d);
+ break;
+ case 0x8:
+ tmp->min = minOR(a, -1, c, d);
+ tmp->max = maxOR(0, b, c, d);
+ break;
+ case 0xa:
+ tmp->min = MIN(a, c);
+ tmp->max = maxOR(0, b, 0, d);
+ break;
+ case 0xb:
+ tmp->min = c;
+ tmp->max = -1;
+ break;
+ case 0xe:
+ tmp->min = a;
+ tmp->max = -1;
+ break;
+ }
+}
+
+/*
+0: + + + + 0 0 0 0 => 0 0 + min/max
+2: + + - + 0 0 1 0 => 0 0 + 0/b
+3: + + - - 0 0 1 1 => 0 0 + min/max
+8: - + + + 1 0 0 0 => 0 0 + 0/d
+a: - + - + 1 0 1 0 => 1 0 ? min(a,-1,c,-1)/NAX(b,d)
+b: - + - - 1 0 1 1 => 1 0 ? min(a,-1,c,d)/max(0,b,c,d)
+c: - - + + 1 1 0 0 => 1 1 - min/max
+e: - - - + 1 1 1 0 => 1 0 ? min(a,b,c,-1)/max(a,b,0,d)
+f - - - - 1 1 1 1 => 1 1 - min/max
+*/
+static void zend_ssa_range_and(zend_long a, zend_long b, zend_long c, zend_long d, zend_ssa_range *tmp)
+{
+ int x = ((a < 0) ? 8 : 0) |
+ ((b < 0) ? 4 : 0) |
+ ((c < 0) ? 2 : 0) |
+ ((d < 0) ? 2 : 0);
+ switch (x) {
+ case 0x0:
+ case 0x3:
+ case 0xc:
+ case 0xf:
+ tmp->min = minAND(a, b, c, d);
+ tmp->max = maxAND(a, b, c, d);
+ break;
+ case 0x2:
+ tmp->min = 0;
+ tmp->max = b;
+ break;
+ case 0x8:
+ tmp->min = 0;
+ tmp->max = d;
+ break;
+ case 0xa:
+ tmp->min = minAND(a, -1, c, -1);
+ tmp->max = MAX(b, d);
+ break;
+ case 0xb:
+ tmp->min = minAND(a, -1, c, d);
+ tmp->max = maxAND(0, b, c, d);
+ break;
+ case 0xe:
+ tmp->min = minAND(a, b, c, -1);
+ tmp->max = maxAND(a, b, 0, d);
+ break;
+ }
+}
+
+static inline bool zend_abs_range(
+ zend_long min, zend_long max, zend_long *abs_min, zend_long *abs_max) {
+ if (min == ZEND_LONG_MIN) {
+ /* Cannot take absolute value of LONG_MIN */
+ return 0;
+ }
+
+ if (min >= 0) {
+ *abs_min = min;
+ *abs_max = max;
+ } else if (max <= 0) {
+ *abs_min = -max;
+ *abs_max = -min;
+ } else {
+ /* Range crossing zero */
+ *abs_min = 0;
+ *abs_max = MAX(max, -min);
+ }
+
+ return 1;
+}
+
+static inline zend_long safe_shift_left(zend_long n, zend_long s) {
+ return (zend_long) ((zend_ulong) n << (zend_ulong) s);
+}
+
+static inline bool shift_left_overflows(zend_long n, zend_long s) {
+ /* This considers shifts that shift in the sign bit to be overflowing as well */
+ if (n >= 0) {
+ return s >= SIZEOF_ZEND_LONG * 8 - 1 || safe_shift_left(n, s) < n;
+ } else {
+ return s >= SIZEOF_ZEND_LONG * 8 || safe_shift_left(n, s) > n;
+ }
+}
+
+/* If b does not divide a exactly, return the two adjacent values between which the real result
+ * lies. */
+static void float_div(zend_long a, zend_long b, zend_long *r1, zend_long *r2) {
+ *r1 = *r2 = a / b;
+ if (a % b != 0) {
+ if (*r2 < 0) {
+ (*r2)--;
+ } else {
+ (*r2)++;
+ }
+ }
+}
+
+static int zend_inference_calc_binary_op_range(
+ const zend_op_array *op_array, zend_ssa *ssa,
+ zend_op *opline, zend_ssa_op *ssa_op, zend_uchar opcode, zend_ssa_range *tmp) {
+ zend_long op1_min, op2_min, op1_max, op2_max, t1, t2, t3, t4;
+
+ switch (opcode) {
+ case ZEND_ADD:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ zend_add_will_overflow(op1_min, op2_min)) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ } else {
+ tmp->min = op1_min + op2_min;
+ }
+ if (OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW() ||
+ zend_add_will_overflow(op1_max, op2_max)) {
+ tmp->overflow = 1;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->max = op1_max + op2_max;
+ }
+ return 1;
+ }
+ break;
+ case ZEND_SUB:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_OVERFLOW() ||
+ zend_sub_will_overflow(op1_min, op2_max)) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ } else {
+ tmp->min = op1_min - op2_max;
+ }
+ if (OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ zend_sub_will_overflow(op1_max, op2_min)) {
+ tmp->overflow = 1;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->max = op1_max - op2_min;
+ }
+ return 1;
+ }
+ break;
+ case ZEND_MUL:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ double dummy;
+ zend_long t1_overflow, t2_overflow, t3_overflow, t4_overflow;
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ /* Suppress uninit variable warnings, these will only be used if the overflow
+ * flags are all false. */
+ t1 = t2 = t3 = t4 = 0;
+ ZEND_SIGNED_MULTIPLY_LONG(op1_min, op2_min, t1, dummy, t1_overflow);
+ ZEND_SIGNED_MULTIPLY_LONG(op1_min, op2_max, t2, dummy, t2_overflow);
+ ZEND_SIGNED_MULTIPLY_LONG(op1_max, op2_min, t3, dummy, t3_overflow);
+ ZEND_SIGNED_MULTIPLY_LONG(op1_max, op2_max, t4, dummy, t4_overflow);
+ (void) dummy;
+
+ // FIXME: more careful overflow checks?
+ if (OP1_RANGE_UNDERFLOW() || OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() || OP2_RANGE_OVERFLOW() ||
+ t1_overflow || t2_overflow || t3_overflow || t4_overflow
+ ) {
+ tmp->underflow = 1;
+ tmp->overflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
+ tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
+ }
+ return 1;
+ }
+ break;
+ case ZEND_DIV:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (op2_min <= 0 && op2_max >= 0) {
+ /* If op2 crosses zero, then floating point values close to zero might be
+ * possible, which will result in arbitrarily large results. As such, we can't
+ * do anything useful in that case. */
+ break;
+ }
+ if (op1_min == ZEND_LONG_MIN && op2_max == -1) {
+ /* Avoid ill-defined division, which may trigger SIGFPE. */
+ break;
+ }
+
+ zend_long t1_, t2_, t3_, t4_;
+ float_div(op1_min, op2_min, &t1, &t1_);
+ float_div(op1_min, op2_max, &t2, &t2_);
+ float_div(op1_max, op2_min, &t3, &t3_);
+ float_div(op1_max, op2_max, &t4, &t4_);
+
+ /* The only case in which division can "overflow" either a division by an absolute
+ * value smaller than one, or LONG_MIN / -1 in particular. Both cases have already
+ * been excluded above. */
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->underflow = 1;
+ tmp->overflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->min = MIN(MIN(MIN(t1, t2), MIN(t3, t4)), MIN(MIN(t1_, t2_), MIN(t3_, t4_)));
+ tmp->max = MAX(MAX(MAX(t1, t2), MAX(t3, t4)), MAX(MAX(t1_, t2_), MAX(t3_, t4_)));
+ }
+ return 1;
+ }
+ break;
+ case ZEND_MOD:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ zend_long op2_abs_min, op2_abs_max;
+
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ if (!zend_abs_range(op2_min, op2_max, &op2_abs_min, &op2_abs_max)) {
+ break;
+ }
+
+ if (op2_abs_max == 0) {
+ /* Always modulus by zero, nothing we can do */
+ break;
+ }
+ if (op2_abs_min == 0) {
+ /* Ignore the modulus by zero case, which will throw */
+ op2_abs_min++;
+ }
+
+ if (op1_min >= 0) {
+ tmp->min = op1_max < op2_abs_min ? op1_min : 0;
+ tmp->max = MIN(op1_max, op2_abs_max - 1);
+ } else if (op1_max <= 0) {
+ tmp->min = MAX(op1_min, -op2_abs_max + 1);
+ tmp->max = op1_min > -op2_abs_min ? op1_max : 0;
+ } else {
+ tmp->min = MAX(op1_min, -op2_abs_max + 1);
+ tmp->max = MIN(op1_max, op2_abs_max - 1);
+ }
+ }
+ return 1;
+ }
+ break;
+ case ZEND_SL:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ /* Shifts by negative numbers will throw, ignore them */
+ if (op2_min < 0) {
+ op2_min = 0;
+ }
+ if (op2_max < 0) {
+ op2_max = 0;
+ }
+
+ if (shift_left_overflows(op1_min, op2_max)
+ || shift_left_overflows(op1_max, op2_max)) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ t1 = safe_shift_left(op1_min, op2_min);
+ t2 = safe_shift_left(op1_min, op2_max);
+ t3 = safe_shift_left(op1_max, op2_min);
+ t4 = safe_shift_left(op1_max, op2_max);
+ tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
+ tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
+ }
+ }
+ return 1;
+ }
+ break;
+ case ZEND_SR:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ /* Shifts by negative numbers will throw, ignore them */
+ if (op2_min < 0) {
+ op2_min = 0;
+ }
+ if (op2_max < 0) {
+ op2_max = 0;
+ }
+
+ /* Shifts by more than the integer size will be 0 or -1 */
+ if (op2_min >= SIZEOF_ZEND_LONG * 8) {
+ op2_min = SIZEOF_ZEND_LONG * 8 - 1;
+ }
+ if (op2_max >= SIZEOF_ZEND_LONG * 8) {
+ op2_max = SIZEOF_ZEND_LONG * 8 - 1;
+ }
+
+ t1 = op1_min >> op2_min;
+ t2 = op1_min >> op2_max;
+ t3 = op1_max >> op2_min;
+ t4 = op1_max >> op2_max;
+ tmp->min = MIN(MIN(t1, t2), MIN(t3, t4));
+ tmp->max = MAX(MAX(t1, t2), MAX(t3, t4));
+ }
+ return 1;
+ }
+ break;
+ case ZEND_BW_OR:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ zend_ssa_range_or(op1_min, op1_max, op2_min, op2_max, tmp);
+ }
+ return 1;
+ }
+ break;
+ case ZEND_BW_AND:
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP2_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW() ||
+ OP2_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ zend_ssa_range_and(op1_min, op1_max, op2_min, op2_max, tmp);
+ }
+ return 1;
+ }
+ break;
+ case ZEND_BW_XOR:
+ // TODO
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ return 0;
+}
+
+int zend_inference_calc_range(const zend_op_array *op_array, zend_ssa *ssa, int var, int widening, int narrowing, zend_ssa_range *tmp)
+{
+ uint32_t line;
+ zend_op *opline;
+ zend_ssa_op *ssa_op;
+
+ if (ssa->vars[var].definition_phi) {
+ zend_ssa_phi *p = ssa->vars[var].definition_phi;
+ int i;
+
+ tmp->underflow = 0;
+ tmp->min = ZEND_LONG_MAX;
+ tmp->max = ZEND_LONG_MIN;
+ tmp->overflow = 0;
+ if (p->pi >= 0 && p->has_range_constraint) {
+ zend_ssa_range_constraint *constraint = &p->constraint.range;
+ if (constraint->negative) {
+ if (ssa->var_info[p->sources[0]].has_range) {
+ *tmp = ssa->var_info[p->sources[0]].range;
+ } else if (narrowing) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 1;
+ }
+
+#ifdef NEG_RANGE
+ if (constraint->min_ssa_var < 0 &&
+ constraint->max_ssa_var < 0 &&
+ ssa->var_info[p->ssa_var].has_range) {
+ LOG_NEG_RANGE("%s() #%d [%ld..%ld] -> [%ld..%ld]?\n",
+ ZSTR_VAL(op_array->function_name),
+ p->ssa_var,
+ ssa->var_info[p->ssa_var].range.min,
+ ssa->var_info[p->ssa_var].range.max,
+ tmp->min,
+ tmp->max);
+ if (constraint->negative == NEG_USE_LT &&
+ tmp->max >= constraint->range.min) {
+ tmp->overflow = 0;
+ tmp->max = constraint->range.min - 1;
+ LOG_NEG_RANGE(" => [%ld..%ld]\n", tmp->min, tmp->max);
+ } else if (constraint->negative == NEG_USE_GT &&
+ tmp->min <= constraint->range.max) {
+ tmp->underflow = 0;
+ tmp->min = constraint->range.max + 1;
+ LOG_NEG_RANGE(" => [%ld..%ld]\n", tmp->min, tmp->max);
+ }
+ }
+#endif
+ } else if (ssa->var_info[p->sources[0]].has_range) {
+ /* intersection */
+ *tmp = ssa->var_info[p->sources[0]].range;
+ if (constraint->min_ssa_var < 0) {
+ tmp->underflow = constraint->range.underflow && tmp->underflow;
+ tmp->min = MAX(constraint->range.min, tmp->min);
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
+ tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow && tmp->underflow;
+ if (!add_will_overflow(ssa->var_info[constraint->min_ssa_var].range.min, constraint->range.min)) {
+ tmp->min = MAX(ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min, tmp->min);
+ }
+#endif
+ }
+ if (constraint->max_ssa_var < 0) {
+ tmp->max = MIN(constraint->range.max, tmp->max);
+ tmp->overflow = constraint->range.overflow && tmp->overflow;
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
+ if (!add_will_overflow(ssa->var_info[constraint->max_ssa_var].range.max, constraint->range.max)) {
+ tmp->max = MIN(ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max, tmp->max);
+ }
+ tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow && tmp->overflow;
+#endif
+ }
+ } else if (narrowing) {
+ if (constraint->min_ssa_var < 0) {
+ tmp->underflow = constraint->range.underflow;
+ tmp->min = constraint->range.min;
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->min_ssa_var].has_range) {
+ if (add_will_overflow(ssa->var_info[constraint->min_ssa_var].range.min, constraint->range.min)) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ } else {
+ tmp->underflow = ssa->var_info[constraint->min_ssa_var].range.underflow;
+ tmp->min = ssa->var_info[constraint->min_ssa_var].range.min + constraint->range.min;
+ }
+#endif
+ } else {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ }
+ if (constraint->max_ssa_var < 0) {
+ tmp->max = constraint->range.max;
+ tmp->overflow = constraint->range.overflow;
+#ifdef SYM_RANGE
+ } else if (narrowing && ssa->var_info[constraint->max_ssa_var].has_range) {
+ if (add_will_overflow(ssa->var_info[constraint->max_ssa_var].range.max, constraint->range.max)) {
+ tmp->overflow = 1;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ tmp->max = ssa->var_info[constraint->max_ssa_var].range.max + constraint->range.max;
+ tmp->overflow = ssa->var_info[constraint->max_ssa_var].range.overflow;
+ }
+#endif
+ } else {
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 1;
+ }
+ }
+ } else {
+ for (i = 0; i < ssa->cfg.blocks[p->block].predecessors_count; i++) {
+ ZEND_ASSERT(p->sources[i] >= 0);
+ if (ssa->var_info[p->sources[i]].has_range) {
+ /* union */
+ tmp->underflow |= ssa->var_info[p->sources[i]].range.underflow;
+ tmp->min = MIN(tmp->min, ssa->var_info[p->sources[i]].range.min);
+ tmp->max = MAX(tmp->max, ssa->var_info[p->sources[i]].range.max);
+ tmp->overflow |= ssa->var_info[p->sources[i]].range.overflow;
+ } else if (narrowing) {
+ tmp->underflow = 1;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 1;
+ }
+ }
+ }
+ return (tmp->min <= tmp->max);
+ } else if (ssa->vars[var].definition < 0) {
+ if (var < op_array->last_var &&
+ op_array->function_name) {
+
+ tmp->min = 0;
+ tmp->max = 0;
+ tmp->underflow = 0;
+ tmp->overflow = 0;
+ return 1;
+ }
+ return 0;
+ }
+ line = ssa->vars[var].definition;
+ opline = op_array->opcodes + line;
+ ssa_op = &ssa->ops[line];
+
+ return zend_inference_propagate_range(op_array, ssa, opline, ssa_op, var, tmp);
+}
+
+ZEND_API int zend_inference_propagate_range(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, zend_ssa_op* ssa_op, int var, zend_ssa_range *tmp)
+{
+ zend_long op1_min, op2_min, op1_max, op2_max;
+
+ tmp->underflow = 0;
+ tmp->overflow = 0;
+ switch (opline->opcode) {
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_MOD:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ if (ssa_op->result_def == var) {
+ return zend_inference_calc_binary_op_range(
+ op_array, ssa, opline, ssa_op, opline->opcode, tmp);
+ }
+ break;
+
+ case ZEND_BW_NOT:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ if (OP1_RANGE_UNDERFLOW() ||
+ OP1_RANGE_OVERFLOW()) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else {
+ op1_min = OP1_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ tmp->min = ~op1_max;
+ tmp->max = ~op1_min;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_CAST:
+ if (ssa_op->op1_def == var) {
+ if (ssa_op->op1_def >= 0) {
+ if (OP1_HAS_RANGE()) {
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ } else if (ssa_op->result_def == var) {
+ if (opline->extended_value == IS_LONG) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ return 1;
+ } else {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ }
+ }
+ }
+ break;
+ case ZEND_BOOL:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ tmp->min = (op1_min > 0 || op1_max < 0);
+ tmp->max = (op1_min != 0 || op1_max != 0);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_BOOL_NOT:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ tmp->min = (op1_min == 0 && op1_max == 0);
+ tmp->max = (op1_min <= 0 && op1_max >= 0);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_BOOL_XOR:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+ op1_min = (op1_min > 0 || op1_max < 0);
+ op1_max = (op1_min != 0 || op1_max != 0);
+ op2_min = (op2_min > 0 || op2_max < 0);
+ op2_max = (op2_min != 0 || op2_max != 0);
+ tmp->min = 0;
+ tmp->max = 1;
+ if (op1_min == op1_max && op2_min == op2_max) {
+ if (op1_min == op2_min) {
+ tmp->max = 0;
+ } else {
+ tmp->min = 1;
+ }
+ }
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = (op1_min == op1_max &&
+ op2_min == op2_max &&
+ op1_min == op2_max);
+ tmp->max = (op1_min <= op2_max && op1_max >= op2_min);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_NOT_EQUAL:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = (op1_min > op2_max || op1_max < op2_min);
+ tmp->max = (op1_min != op1_max ||
+ op2_min != op2_max ||
+ op1_min != op2_max);
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_SMALLER:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = op1_max < op2_min;
+ tmp->max = op1_min < op2_max;
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE() && OP2_HAS_RANGE()) {
+ op1_min = OP1_MIN_RANGE();
+ op2_min = OP2_MIN_RANGE();
+ op1_max = OP1_MAX_RANGE();
+ op2_max = OP2_MAX_RANGE();
+
+ tmp->min = op1_max <= op2_min;
+ tmp->max = op1_min <= op2_max;
+ return 1;
+ } else {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ }
+ break;
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_COPY_TMP:
+ if (ssa_op->op1_def == var) {
+ if (ssa_op->op1_def >= 0) {
+ if (OP1_HAS_RANGE()) {
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ }
+ if (ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ case ZEND_ASSERT_CHECK:
+ if (ssa_op->result_def == var) {
+ tmp->min = 0;
+ tmp->max = 1;
+ return 1;
+ }
+ break;
+ case ZEND_SEND_VAR:
+ if (ssa_op->op1_def == var) {
+ if (ssa_op->op1_def >= 0) {
+ if (OP1_HAS_RANGE()) {
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ }
+ break;
+ case ZEND_PRE_INC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (tmp->max < ZEND_LONG_MAX) {
+ tmp->max++;
+ } else {
+ tmp->overflow = 1;
+ }
+ if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) {
+ tmp->min++;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_PRE_DEC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (tmp->min > ZEND_LONG_MIN) {
+ tmp->min--;
+ } else {
+ tmp->underflow = 1;
+ }
+ if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) {
+ tmp->max--;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_POST_INC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (ssa_op->result_def == var) {
+ return 1;
+ }
+ if (tmp->max < ZEND_LONG_MAX) {
+ tmp->max++;
+ } else {
+ tmp->overflow = 1;
+ }
+ if (tmp->min < ZEND_LONG_MAX && !tmp->underflow) {
+ tmp->min++;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_POST_DEC:
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ if (ssa_op->result_def == var) {
+ return 1;
+ }
+ if (tmp->min > ZEND_LONG_MIN) {
+ tmp->min--;
+ } else {
+ tmp->underflow = 1;
+ }
+ if (tmp->max > ZEND_LONG_MIN && !tmp->overflow) {
+ tmp->max--;
+ }
+ return 1;
+ }
+ }
+ break;
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ if (ssa_op->op1_def == var) {
+ /* If op1 is scalar, UNSET_DIM and UNSET_OBJ have no effect, so we can keep
+ * the previous ranges. */
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ case ZEND_ASSIGN:
+ if (ssa_op->op1_def == var || ssa_op->op2_def == var || ssa_op->result_def == var) {
+ if (OP2_HAS_RANGE()) {
+ tmp->min = OP2_MIN_RANGE();
+ tmp->max = OP2_MAX_RANGE();
+ tmp->underflow = OP2_RANGE_UNDERFLOW();
+ tmp->overflow = OP2_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_STATIC_PROP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ if ((ssa_op+1)->op1_def == var) {
+ opline++;
+ ssa_op++;
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ }
+ return 1;
+ }
+ break;
+ case ZEND_ASSIGN_OP:
+ if (opline->extended_value != ZEND_CONCAT
+ && opline->extended_value != ZEND_POW) {
+ if (ssa_op->op1_def == var || ssa_op->result_def == var) {
+ return zend_inference_calc_binary_op_range(
+ op_array, ssa, opline, ssa_op,
+ opline->extended_value, tmp);
+ }
+ }
+ break;
+ case ZEND_OP_DATA:
+ if (ssa_op->op1_def == var) {
+ if ((opline-1)->opcode == ZEND_ASSIGN_DIM ||
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ ||
+ (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP ||
+ (opline-1)->opcode == ZEND_ASSIGN_DIM_OP ||
+ (opline-1)->opcode == ZEND_ASSIGN_OBJ_OP ||
+ (opline-1)->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
+ if (OP1_HAS_RANGE()) {
+ tmp->min = OP1_MIN_RANGE();
+ tmp->max = OP1_MAX_RANGE();
+ tmp->underflow = OP1_RANGE_UNDERFLOW();
+ tmp->overflow = OP1_RANGE_OVERFLOW();
+ return 1;
+ }
+ }
+ break;
+ }
+ break;
+ case ZEND_RECV:
+ case ZEND_RECV_INIT:
+ if (ssa_op->result_def == var) {
+ if (op_array->arg_info &&
+ opline->op1.num <= op_array->num_args) {
+ zend_type type = op_array->arg_info[opline->op1.num-1].type;
+ uint32_t mask = ZEND_TYPE_PURE_MASK_WITHOUT_NULL(type);
+ if (mask == MAY_BE_LONG) {
+ tmp->underflow = 0;
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ tmp->overflow = 0;
+ return 1;
+ } else if (mask == MAY_BE_BOOL) {
+ tmp->underflow = 0;
+ tmp->min = 0;
+ tmp->max = 1;
+ tmp->overflow = 0;
+ return 1;
+ }
+ }
+ }
+ break;
+ case ZEND_STRLEN:
+ if (ssa_op->result_def == var) {
+#if SIZEOF_ZEND_LONG == 4
+ /* The length of a string is a non-negative integer. However, on 32-bit
+ * platforms overflows into negative lengths may occur, so it's better
+ * to not assume any particular range. */
+ tmp->min = ZEND_LONG_MIN;
+#else
+ tmp->min = 0;
+#endif
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ }
+ break;
+ case ZEND_FUNC_NUM_ARGS:
+ tmp->min = 0;
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ case ZEND_COUNT:
+ /* count() on Countable objects may return negative numbers */
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ return 1;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ if (ssa_op->result_def == var) {
+ zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+ zend_call_info *call_info;
+ if (!func_info || !func_info->call_map) {
+ break;
+ }
+
+ call_info = func_info->call_map[opline - op_array->opcodes];
+ if (!call_info) {
+ break;
+ }
+ if (call_info->callee_func->type == ZEND_USER_FUNCTION) {
+ func_info = ZEND_FUNC_INFO(&call_info->callee_func->op_array);
+ if (func_info && func_info->return_info.has_range) {
+ *tmp = func_info->return_info.range;
+ return 1;
+ }
+ }
+//TODO: we can't use type inference for internal functions at this point ???
+#if 0
+ uint32_t type;
+
+ type = zend_get_func_info(call_info, ssa);
+ if (!(type & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG)))) {
+ tmp->underflow = 0;
+ tmp->min = 0;
+ tmp->max = 0;
+ tmp->overflow = 0;
+ if (type & MAY_BE_LONG) {
+ tmp->min = ZEND_LONG_MIN;
+ tmp->max = ZEND_LONG_MAX;
+ } else if (type & MAY_BE_TRUE) {
+ if (!(type & (MAY_BE_NULL|MAY_BE_FALSE))) {
+ tmp->min = 1;
+ }
+ tmp->max = 1;
+ }
+ return 1;
+ }
+#endif
+ }
+ break;
+ // FIXME: support for more opcodes
+ default:
+ break;
+ }
+ return 0;
+}
+
+void zend_inference_init_range(const zend_op_array *op_array, zend_ssa *ssa, int var, bool underflow, zend_long min, zend_long max, bool overflow)
+{
+ if (underflow) {
+ min = ZEND_LONG_MIN;
+ }
+ if (overflow) {
+ max = ZEND_LONG_MAX;
+ }
+ ssa->var_info[var].has_range = 1;
+ ssa->var_info[var].range.underflow = underflow;
+ ssa->var_info[var].range.min = min;
+ ssa->var_info[var].range.max = max;
+ ssa->var_info[var].range.overflow = overflow;
+ LOG_SSA_RANGE(" change range (init SCC %2d) %2d [%s%ld..%ld%s]\n", ssa->vars[var].scc, var, (underflow?"-- ":""), min, max, (overflow?" ++":""));
+}
+
+int zend_inference_widening_meet(zend_ssa_var_info *var_info, zend_ssa_range *r)
+{
+ if (!var_info->has_range) {
+ var_info->has_range = 1;
+ } else {
+ if (r->underflow ||
+ var_info->range.underflow ||
+ r->min < var_info->range.min) {
+ r->underflow = 1;
+ r->min = ZEND_LONG_MIN;
+ }
+ if (r->overflow ||
+ var_info->range.overflow ||
+ r->max > var_info->range.max) {
+ r->overflow = 1;
+ r->max = ZEND_LONG_MAX;
+ }
+ if (var_info->range.min == r->min &&
+ var_info->range.max == r->max &&
+ var_info->range.underflow == r->underflow &&
+ var_info->range.overflow == r->overflow) {
+ return 0;
+ }
+ }
+ var_info->range = *r;
+ return 1;
+}
+
+static int zend_ssa_range_widening(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc)
+{
+ zend_ssa_range tmp;
+
+ if (zend_inference_calc_range(op_array, ssa, var, 1, 0, &tmp)) {
+ if (zend_inference_widening_meet(&ssa->var_info[var], &tmp)) {
+ LOG_SSA_RANGE(" change range (widening SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int zend_inference_narrowing_meet(zend_ssa_var_info *var_info, zend_ssa_range *r)
+{
+ if (!var_info->has_range) {
+ var_info->has_range = 1;
+ } else {
+ if (!r->underflow &&
+ !var_info->range.underflow &&
+ var_info->range.min < r->min) {
+ r->min = var_info->range.min;
+ }
+ if (!r->overflow &&
+ !var_info->range.overflow &&
+ var_info->range.max > r->max) {
+ r->max = var_info->range.max;
+ }
+ if (r->underflow) {
+ r->min = ZEND_LONG_MIN;
+ }
+ if (r->overflow) {
+ r->max = ZEND_LONG_MAX;
+ }
+ if (var_info->range.min == r->min &&
+ var_info->range.max == r->max &&
+ var_info->range.underflow == r->underflow &&
+ var_info->range.overflow == r->overflow) {
+ return 0;
+ }
+ }
+ var_info->range = *r;
+ return 1;
+}
+
+static int zend_ssa_range_narrowing(const zend_op_array *op_array, zend_ssa *ssa, int var, int scc)
+{
+ zend_ssa_range tmp;
+
+ if (zend_inference_calc_range(op_array, ssa, var, 0, 1, &tmp)) {
+ if (zend_inference_narrowing_meet(&ssa->var_info[var], &tmp)) {
+ LOG_SSA_RANGE(" change range (narrowing SCC %2d) %2d [%s%ld..%ld%s]\n", scc, var, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#ifdef NEG_RANGE
+# define CHECK_INNER_CYCLE(var2) \
+ do { \
+ if (ssa->vars[var2].scc == ssa->vars[var].scc && \
+ !ssa->vars[var2].scc_entry && \
+ !zend_bitset_in(visited, var2) && \
+ zend_check_inner_cycles(op_array, ssa, worklist, visited, var2)) { \
+ return 1; \
+ } \
+ } while (0)
+
+static int zend_check_inner_cycles(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, zend_bitset visited, int var)
+{
+ if (zend_bitset_in(worklist, var)) {
+ return 1;
+ }
+ zend_bitset_incl(worklist, var);
+ FOR_EACH_VAR_USAGE(var, CHECK_INNER_CYCLE);
+ zend_bitset_incl(visited, var);
+ return 0;
+}
+#endif
+
+static void zend_infer_ranges_warmup(const zend_op_array *op_array, zend_ssa *ssa, int *scc_var, int *next_scc_var, int scc)
+{
+ int worklist_len = zend_bitset_len(ssa->vars_count);
+ int j, n;
+ zend_ssa_range tmp;
+ ALLOCA_FLAG(use_heap)
+ zend_bitset worklist = do_alloca(sizeof(zend_ulong) * worklist_len * 2, use_heap);
+ zend_bitset visited = worklist + worklist_len;
+#ifdef NEG_RANGE
+ int has_inner_cycles = 0;
+
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+ memset(visited, 0, sizeof(zend_ulong) * worklist_len);
+ j = scc_var[scc];
+ while (j >= 0) {
+ if (!zend_bitset_in(visited, j) &&
+ zend_check_inner_cycles(op_array, ssa, worklist, visited, j)) {
+ has_inner_cycles = 1;
+ break;
+ }
+ j = next_scc_var[j];
+ }
+#endif
+
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+
+ for (n = 0; n < RANGE_WARMUP_PASSES; n++) {
+ j= scc_var[scc];
+ while (j >= 0) {
+ if (ssa->vars[j].scc_entry) {
+ zend_bitset_incl(worklist, j);
+ }
+ j = next_scc_var[j];
+ }
+
+ memset(visited, 0, sizeof(zend_ulong) * worklist_len);
+
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_inference_calc_range(op_array, ssa, j, 0, 0, &tmp)) {
+#ifdef NEG_RANGE
+ if (!has_inner_cycles &&
+ ssa->var_info[j].has_range &&
+ ssa->vars[j].definition_phi &&
+ ssa->vars[j].definition_phi->pi >= 0 &&
+ ssa->vars[j].definition_phi->has_range_constraint &&
+ ssa->vars[j].definition_phi->constraint.range.negative &&
+ ssa->vars[j].definition_phi->constraint.range.min_ssa_var < 0 &&
+ ssa->vars[j].definition_phi->constraint.range.max_ssa_var < 0) {
+ zend_ssa_range_constraint *constraint =
+ &ssa->vars[j].definition_phi->constraint.range;
+ if (tmp.min == ssa->var_info[j].range.min &&
+ tmp.max == ssa->var_info[j].range.max) {
+ if (constraint->negative == NEG_INIT) {
+ LOG_NEG_RANGE("#%d INVARIANT\n", j);
+ constraint->negative = NEG_INVARIANT;
+ }
+ } else if (tmp.min == ssa->var_info[j].range.min &&
+ tmp.max == ssa->var_info[j].range.max + 1 &&
+ tmp.max < constraint->range.min) {
+ if (constraint->negative == NEG_INIT ||
+ constraint->negative == NEG_INVARIANT) {
+ LOG_NEG_RANGE("#%d LT\n", j);
+ constraint->negative = NEG_USE_LT;
+//???NEG
+ } else if (constraint->negative == NEG_USE_GT) {
+ LOG_NEG_RANGE("#%d UNKNOWN\n", j);
+ constraint->negative = NEG_UNKNOWN;
+ }
+ } else if (tmp.max == ssa->var_info[j].range.max &&
+ tmp.min == ssa->var_info[j].range.min - 1 &&
+ tmp.min > constraint->range.max) {
+ if (constraint->negative == NEG_INIT ||
+ constraint->negative == NEG_INVARIANT) {
+ LOG_NEG_RANGE("#%d GT\n", j);
+ constraint->negative = NEG_USE_GT;
+//???NEG
+ } else if (constraint->negative == NEG_USE_LT) {
+ LOG_NEG_RANGE("#%d UNKNOWN\n", j);
+ constraint->negative = NEG_UNKNOWN;
+ }
+ } else {
+ LOG_NEG_RANGE("#%d UNKNOWN\n", j);
+ constraint->negative = NEG_UNKNOWN;
+ }
+ }
+#endif
+ if (zend_inference_narrowing_meet(&ssa->var_info[j], &tmp)) {
+ LOG_SSA_RANGE(" change range (warmup %2d SCC %2d) %2d [%s%ld..%ld%s]\n", n, scc, j, (tmp.underflow?"-- ":""), tmp.min, tmp.max, (tmp.overflow?" ++":""));
+ zend_bitset_incl(visited, j);
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR_1);
+ }
+ }
+ } WHILE_WORKLIST_END();
+ }
+ free_alloca(worklist, use_heap);
+}
+
+static int zend_infer_ranges(const zend_op_array *op_array, zend_ssa *ssa) /* {{{ */
+{
+ int worklist_len = zend_bitset_len(ssa->vars_count);
+ zend_bitset worklist;
+ int *next_scc_var;
+ int *scc_var;
+ zend_ssa_phi *p;
+ zend_ssa_range tmp;
+ int scc, j;
+ ALLOCA_FLAG(use_heap);
+
+ worklist = do_alloca(
+ ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len) +
+ ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count) +
+ sizeof(int) * ssa->sccs, use_heap);
+ next_scc_var = (int*)((char*)worklist + ZEND_MM_ALIGNED_SIZE(sizeof(zend_ulong) * worklist_len));
+ scc_var = (int*)((char*)next_scc_var + ZEND_MM_ALIGNED_SIZE(sizeof(int) * ssa->vars_count));
+
+ LOG_SSA_RANGE("Range Inference\n");
+
+ /* Create linked lists of SSA variables for each SCC */
+ memset(scc_var, -1, sizeof(int) * ssa->sccs);
+ for (j = 0; j < ssa->vars_count; j++) {
+ if (ssa->vars[j].scc >= 0) {
+ next_scc_var[j] = scc_var[ssa->vars[j].scc];
+ scc_var[ssa->vars[j].scc] = j;
+ }
+ }
+
+ for (scc = 0; scc < ssa->sccs; scc++) {
+ j = scc_var[scc];
+ if (next_scc_var[j] < 0) {
+ /* SCC with a single element */
+ if (zend_inference_calc_range(op_array, ssa, j, 0, 1, &tmp)) {
+ zend_inference_init_range(op_array, ssa, j, tmp.underflow, tmp.min, tmp.max, tmp.overflow);
+ } else {
+ zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
+ }
+ } else {
+ /* Find SCC entry points */
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+ do {
+ if (ssa->vars[j].scc_entry) {
+ zend_bitset_incl(worklist, j);
+ }
+ j = next_scc_var[j];
+ } while (j >= 0);
+
+#if RANGE_WARMUP_PASSES > 0
+ zend_infer_ranges_warmup(op_array, ssa, scc_var, next_scc_var, scc);
+ j = scc_var[scc];
+ do {
+ zend_bitset_incl(worklist, j);
+ j = next_scc_var[j];
+ } while (j >= 0);
+#endif
+
+ /* widening */
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_ssa_range_widening(op_array, ssa, j, scc)) {
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+ }
+ } WHILE_WORKLIST_END();
+
+ /* initialize missing ranges */
+ for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
+ if (!ssa->var_info[j].has_range) {
+ zend_inference_init_range(op_array, ssa, j, 1, ZEND_LONG_MIN, ZEND_LONG_MAX, 1);
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+ }
+ }
+
+ /* widening (second round) */
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_ssa_range_widening(op_array, ssa, j, scc)) {
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+ }
+ } WHILE_WORKLIST_END();
+
+ /* Add all SCC entry variables into worklist for narrowing */
+ for (j = scc_var[scc]; j >= 0; j = next_scc_var[j]) {
+ if (ssa->vars[j].definition_phi
+ && ssa->vars[j].definition_phi->pi < 0) {
+ /* narrowing Phi functions first */
+ zend_ssa_range_narrowing(op_array, ssa, j, scc);
+ }
+ zend_bitset_incl(worklist, j);
+ }
+
+ /* narrowing */
+ WHILE_WORKLIST(worklist, worklist_len, j) {
+ if (zend_ssa_range_narrowing(op_array, ssa, j, scc)) {
+ FOR_EACH_VAR_USAGE(j, ADD_SCC_VAR);
+#ifdef SYM_RANGE
+ /* Process symbolic control-flow constraints */
+ p = ssa->vars[j].sym_use_chain;
+ while (p) {
+ ADD_SCC_VAR(p->ssa_var);
+ p = p->sym_use_chain;
+ }
+#endif
+ }
+ } WHILE_WORKLIST_END();
+ }
+ }
+
+ free_alloca(worklist, use_heap);
+
+ return SUCCESS;
+}
+/* }}} */
+
+static uint32_t get_ssa_alias_types(zend_ssa_alias_kind alias) {
+ if (alias == HTTP_RESPONSE_HEADER_ALIAS) {
+ return MAY_BE_ARRAY | MAY_BE_ARRAY_KEY_LONG | MAY_BE_ARRAY_OF_STRING | MAY_BE_RC1 | MAY_BE_RCN;
+ } else {
+ return MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+}
+
+#define UPDATE_SSA_TYPE(_type, _var) \
+ do { \
+ uint32_t __type = (_type) & ~MAY_BE_GUARD; \
+ int __var = (_var); \
+ if (__type & MAY_BE_REF) { \
+ __type |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF; \
+ } \
+ if (__var >= 0) { \
+ zend_ssa_var *__ssa_var = &ssa_vars[__var]; \
+ if (__ssa_var->var < op_array->last_var) { \
+ if (__type & (MAY_BE_REF|MAY_BE_RCN)) { \
+ __type |= MAY_BE_RC1 | MAY_BE_RCN; \
+ } \
+ if ((__type & MAY_BE_RC1) && (__type & MAY_BE_STRING)) {\
+ /* TODO: support for array keys and ($str . "")*/ \
+ __type |= MAY_BE_RCN; \
+ } \
+ if (__ssa_var->alias) { \
+ __type |= get_ssa_alias_types(__ssa_var->alias); \
+ } \
+ } \
+ if (ssa_var_info[__var].type != __type) { \
+ if (ssa_var_info[__var].type & ~__type) { \
+ emit_type_narrowing_warning(op_array, ssa, __var); \
+ return FAILURE; \
+ } \
+ ssa_var_info[__var].type = __type; \
+ if (update_worklist) { \
+ add_usages(op_array, ssa, worklist, __var); \
+ } \
+ } \
+ /*zend_bitset_excl(worklist, var);*/ \
+ } \
+ } while (0)
+
+#define UPDATE_SSA_OBJ_TYPE(_ce, _is_instanceof, var) \
+ do { \
+ if (var >= 0) { \
+ if (ssa_var_info[var].ce != (_ce) || \
+ ssa_var_info[var].is_instanceof != (_is_instanceof)) { \
+ ssa_var_info[var].ce = (_ce); \
+ ssa_var_info[var].is_instanceof = (_is_instanceof); \
+ if (update_worklist) { \
+ add_usages(op_array, ssa, worklist, var); \
+ } \
+ } \
+ /*zend_bitset_excl(worklist, var);*/ \
+ } \
+ } while (0)
+
+#define COPY_SSA_OBJ_TYPE(from_var, to_var) do { \
+ if ((from_var) >= 0 && (ssa_var_info[(from_var)].type & MAY_BE_OBJECT) \
+ && ssa_var_info[(from_var)].ce) { \
+ UPDATE_SSA_OBJ_TYPE(ssa_var_info[(from_var)].ce, \
+ ssa_var_info[(from_var)].is_instanceof, (to_var)); \
+ } else { \
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, (to_var)); \
+ } \
+} while (0)
+
+static void add_usages(const zend_op_array *op_array, zend_ssa *ssa, zend_bitset worklist, int var)
+{
+ if (ssa->vars[var].phi_use_chain) {
+ zend_ssa_phi *p = ssa->vars[var].phi_use_chain;
+ do {
+ zend_bitset_incl(worklist, p->ssa_var);
+ p = zend_ssa_next_use_phi(ssa, var, p);
+ } while (p);
+ }
+ if (ssa->vars[var].use_chain >= 0) {
+ int use = ssa->vars[var].use_chain;
+ zend_ssa_op *op;
+
+ do {
+ op = ssa->ops + use;
+ if (op->result_def >= 0) {
+ zend_bitset_incl(worklist, op->result_def);
+ }
+ if (op->op1_def >= 0) {
+ zend_bitset_incl(worklist, op->op1_def);
+ }
+ if (op->op2_def >= 0) {
+ zend_bitset_incl(worklist, op->op2_def);
+ }
+ if (op_array->opcodes[use].opcode == ZEND_OP_DATA) {
+ op--;
+ if (op->result_def >= 0) {
+ zend_bitset_incl(worklist, op->result_def);
+ }
+ if (op->op1_def >= 0) {
+ zend_bitset_incl(worklist, op->op1_def);
+ }
+ if (op->op2_def >= 0) {
+ zend_bitset_incl(worklist, op->op2_def);
+ }
+ }
+ use = zend_ssa_next_use(ssa->ops, var, use);
+ } while (use >= 0);
+ }
+}
+
+static void emit_type_narrowing_warning(const zend_op_array *op_array, zend_ssa *ssa, int var)
+{
+ int def_op_num = ssa->vars[var].definition;
+ const zend_op *def_opline = def_op_num >= 0 ? &op_array->opcodes[def_op_num] : NULL;
+ const char *def_op_name = def_opline ? zend_get_opcode_name(def_opline->opcode) : "PHI";
+ zend_error(E_WARNING, "Narrowing occurred during type inference of %s. Please file a bug report on bugs.php.net", def_op_name);
+}
+
+ZEND_API uint32_t zend_array_element_type(uint32_t t1, zend_uchar op_type, int write, int insert)
+{
+ uint32_t tmp = 0;
+
+ if (t1 & MAY_BE_OBJECT) {
+ if (!write) {
+ /* can't be REF because of ZVAL_COPY_DEREF() usage */
+ tmp |= MAY_BE_ANY | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= MAY_BE_ANY | MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (write) {
+ tmp |= MAY_BE_INDIRECT;
+ }
+ }
+ if (t1 & MAY_BE_ARRAY) {
+ if (insert) {
+ tmp |= MAY_BE_NULL;
+ } else {
+ tmp |= MAY_BE_NULL | ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
+ if (tmp & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ if (!write) {
+ /* can't be REF because of ZVAL_COPY_DEREF() usage */
+ tmp |= MAY_BE_RCN;
+ if ((op_type & (IS_VAR|IS_TMP_VAR)) && (t1 & MAY_BE_RC1)) {
+ tmp |= MAY_BE_RC1;
+ }
+ } else if (t1 & MAY_BE_ARRAY_OF_REF) {
+ tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
+ } else {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ }
+ }
+ if (write) {
+ tmp |= MAY_BE_INDIRECT;
+ }
+ }
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_RC1;
+ if (write) {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
+ tmp |= MAY_BE_NULL;
+ if (write) {
+ tmp |= MAY_BE_INDIRECT;
+ }
+ }
+ if (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE)) {
+ if (!write) {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ return tmp;
+}
+
+static uint32_t assign_dim_result_type(
+ uint32_t arr_type, uint32_t dim_type, uint32_t value_type, zend_uchar dim_op_type) {
+ uint32_t tmp = arr_type & ~(MAY_BE_RC1|MAY_BE_RCN);
+
+ if (arr_type & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
+ tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE);
+ tmp |= MAY_BE_ARRAY|MAY_BE_RC1;
+ }
+ if (tmp & (MAY_BE_ARRAY|MAY_BE_STRING)) {
+ tmp |= MAY_BE_RC1;
+ }
+ if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ if (tmp & MAY_BE_ARRAY) {
+ /* Only add key type if we have a value type. We want to maintain the invariant that a
+ * key type exists iff a value type exists even in dead code that may use empty types. */
+ if (value_type & (MAY_BE_ANY|MAY_BE_UNDEF)) {
+ if (value_type & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_ARRAY_OF_NULL;
+ }
+ if (dim_op_type == IS_UNUSED) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ } else {
+ if (dim_type & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ if (dim_type & MAY_BE_STRING) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ if (dim_op_type != IS_CONST) {
+ // FIXME: numeric string
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ }
+ if (dim_type & (MAY_BE_UNDEF|MAY_BE_NULL)) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ }
+ }
+ }
+ /* Only add value type if we have a key type. It might be that the key type is illegal
+ * for arrays. */
+ if (tmp & MAY_BE_ARRAY_KEY_ANY) {
+ tmp |= (value_type & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ }
+ }
+ return tmp;
+}
+
+/* For binary ops that have compound assignment operators */
+static uint32_t binary_op_result_type(
+ zend_ssa *ssa, zend_uchar opcode, uint32_t t1, uint32_t t2, int result_var,
+ zend_long optimization_level) {
+ uint32_t tmp = 0;
+ uint32_t t1_type = (t1 & MAY_BE_ANY) | (t1 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);
+ uint32_t t2_type = (t2 & MAY_BE_ANY) | (t2 & MAY_BE_UNDEF ? MAY_BE_NULL : 0);
+
+ if (!(ZEND_OPTIMIZER_IGNORE_OVERLOADING & optimization_level)) {
+ /* Handle potentially overloaded operators.
+ * This could be made more precise by checking the class type, if known. */
+ if ((t1_type & MAY_BE_OBJECT) || (t2_type & MAY_BE_OBJECT)) {
+ /* This is somewhat GMP specific. */
+ tmp |= MAY_BE_OBJECT | MAY_BE_FALSE | MAY_BE_RC1;
+ }
+ }
+
+ switch (opcode) {
+ case ZEND_ADD:
+ if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
+ if (result_var < 0 ||
+ !ssa->var_info[result_var].has_range ||
+ ssa->var_info[result_var].range.underflow ||
+ ssa->var_info[result_var].range.overflow) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ } else if (t1_type == MAY_BE_ARRAY && t2_type == MAY_BE_ARRAY) {
+ tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ } else {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ if ((t1_type & MAY_BE_ARRAY) && (t2_type & MAY_BE_ARRAY)) {
+ tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ tmp |= t2 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ }
+ }
+ break;
+ case ZEND_SUB:
+ case ZEND_MUL:
+ if (t1_type == MAY_BE_LONG && t2_type == MAY_BE_LONG) {
+ if (result_var < 0 ||
+ !ssa->var_info[result_var].has_range ||
+ ssa->var_info[result_var].range.underflow ||
+ ssa->var_info[result_var].range.overflow) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ break;
+ case ZEND_DIV:
+ case ZEND_POW:
+ if (t1_type == MAY_BE_DOUBLE || t2_type == MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ /* Division by zero results in Inf/-Inf/Nan (double), so it doesn't need any special
+ * handling */
+ break;
+ case ZEND_MOD:
+ tmp |= MAY_BE_LONG;
+ /* Division by zero results in an exception, so it doesn't need any special handling */
+ break;
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ if ((t1_type & MAY_BE_STRING) && (t2_type & MAY_BE_STRING)) {
+ tmp |= MAY_BE_STRING | MAY_BE_RC1;
+ }
+ if ((t1_type & ~MAY_BE_STRING) || (t2_type & ~MAY_BE_STRING)) {
+ tmp |= MAY_BE_LONG;
+ }
+ break;
+ case ZEND_SL:
+ case ZEND_SR:
+ tmp |= MAY_BE_LONG;
+ break;
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ /* TODO: +MAY_BE_OBJECT ??? */
+ tmp = MAY_BE_STRING | MAY_BE_RC1 | MAY_BE_RCN;
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ return tmp;
+}
+
+static inline zend_class_entry *get_class_entry(const zend_script *script, zend_string *lcname) {
+ zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL;
+ if (ce) {
+ return ce;
+ }
+
+ ce = zend_hash_find_ptr(CG(class_table), lcname);
+ if (ce && ce->type == ZEND_INTERNAL_CLASS) {
+ return ce;
+ }
+
+ return NULL;
+}
+
+static uint32_t zend_convert_type_declaration_mask(uint32_t type_mask) {
+ uint32_t result_mask = type_mask & MAY_BE_ANY;
+ if (type_mask & MAY_BE_VOID) {
+ result_mask |= MAY_BE_NULL;
+ }
+ if (type_mask & MAY_BE_CALLABLE) {
+ result_mask |= MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ if (type_mask & MAY_BE_ITERABLE) {
+ result_mask |= MAY_BE_OBJECT|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ if (type_mask & MAY_BE_STATIC) {
+ result_mask |= MAY_BE_OBJECT;
+ }
+ if (type_mask & MAY_BE_ARRAY) {
+ result_mask |= MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ return result_mask;
+}
+
+ZEND_API uint32_t zend_fetch_arg_info_type(const zend_script *script, zend_arg_info *arg_info, zend_class_entry **pce)
+{
+ uint32_t tmp;
+
+ *pce = NULL;
+ if (!ZEND_TYPE_IS_SET(arg_info->type)) {
+ return MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_RC1|MAY_BE_RCN;
+ }
+
+ tmp = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(arg_info->type));
+ if (ZEND_TYPE_HAS_CLASS(arg_info->type)) {
+ tmp |= MAY_BE_OBJECT;
+ /* As we only have space to store one CE, we use a plain object type for class unions. */
+ if (ZEND_TYPE_HAS_NAME(arg_info->type)) {
+ zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(arg_info->type));
+ *pce = get_class_entry(script, lcname);
+ zend_string_release_ex(lcname, 0);
+ }
+ }
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ return tmp;
+}
+
+static zend_property_info *lookup_prop_info(zend_class_entry *ce, zend_string *name, zend_class_entry *scope) {
+ zend_property_info *prop_info;
+
+ /* If the class is linked, reuse the precise runtime logic. */
+ if ((ce->ce_flags & ZEND_ACC_LINKED)
+ && (!scope || (scope->ce_flags & ZEND_ACC_LINKED))) {
+ zend_class_entry *prev_scope = EG(fake_scope);
+ EG(fake_scope) = scope;
+ prop_info = zend_get_property_info(ce, name, 1);
+ EG(fake_scope) = prev_scope;
+ if (prop_info && prop_info != ZEND_WRONG_PROPERTY_INFO) {
+ return prop_info;
+ }
+ return NULL;
+ }
+
+ /* Otherwise, handle only some safe cases */
+ prop_info = zend_hash_find_ptr(&ce->properties_info, name);
+ if (prop_info &&
+ ((prop_info->ce == scope) ||
+ (!scope && (prop_info->flags & ZEND_ACC_PUBLIC)))
+ ) {
+ return prop_info;
+ }
+ return NULL;
+}
+
+static zend_property_info *zend_fetch_prop_info(const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline, zend_ssa_op *ssa_op)
+{
+ zend_property_info *prop_info = NULL;
+ if (opline->op2_type == IS_CONST) {
+ zend_class_entry *ce = NULL;
+
+ if (opline->op1_type == IS_UNUSED) {
+ ce = op_array->scope;
+ } else if (ssa_op->op1_use >= 0) {
+ ce = ssa->var_info[ssa_op->op1_use].ce;
+ }
+ if (ce) {
+ prop_info = lookup_prop_info(ce,
+ Z_STR_P(CRT_CONSTANT(opline->op2)),
+ op_array->scope);
+ if (prop_info && (prop_info->flags & ZEND_ACC_STATIC)) {
+ prop_info = NULL;
+ }
+ }
+ }
+ return prop_info;
+}
+
+static zend_property_info *zend_fetch_static_prop_info(const zend_script *script, const zend_op_array *op_array, zend_ssa *ssa, zend_op *opline)
+{
+ zend_property_info *prop_info = NULL;
+ if (opline->op1_type == IS_CONST) {
+ zend_class_entry *ce = NULL;
+ if (opline->op2_type == IS_UNUSED) {
+ int fetch_type = opline->op2.num & ZEND_FETCH_CLASS_MASK;
+ switch (fetch_type) {
+ case ZEND_FETCH_CLASS_SELF:
+ case ZEND_FETCH_CLASS_STATIC:
+ /* We enforce that static property types cannot change during inheritance, so
+ * handling static the same way as self here is legal. */
+ ce = op_array->scope;
+ break;
+ case ZEND_FETCH_CLASS_PARENT:
+ if (op_array->scope && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
+ ce = op_array->scope->parent;
+ }
+ break;
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op2);
+ ce = get_class_entry(script, Z_STR_P(zv + 1));
+ }
+
+ if (ce) {
+ zval *zv = CRT_CONSTANT(opline->op1);
+ prop_info = lookup_prop_info(ce, Z_STR_P(zv), op_array->scope);
+ if (prop_info && !(prop_info->flags & ZEND_ACC_STATIC)) {
+ prop_info = NULL;
+ }
+ }
+ }
+ return prop_info;
+}
+
+static uint32_t zend_fetch_prop_type(const zend_script *script, zend_property_info *prop_info, zend_class_entry **pce)
+{
+ if (pce) {
+ *pce = NULL;
+ }
+ if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) {
+ uint32_t type = zend_convert_type_declaration_mask(ZEND_TYPE_PURE_MASK(prop_info->type));
+
+ if (type & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ type |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ if (ZEND_TYPE_HAS_CLASS(prop_info->type)) {
+ type |= MAY_BE_OBJECT;
+ if (pce) {
+ if (ZEND_TYPE_HAS_CE(prop_info->type)) {
+ *pce = ZEND_TYPE_CE(prop_info->type);
+ } else if (ZEND_TYPE_HAS_NAME(prop_info->type)) {
+ zend_string *lcname = zend_string_tolower(ZEND_TYPE_NAME(prop_info->type));
+ *pce = get_class_entry(script, lcname);
+ zend_string_release(lcname);
+ }
+ }
+ }
+ return type;
+ }
+ return MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
+}
+
+static zend_always_inline int _zend_update_type_info(
+ const zend_op_array *op_array,
+ zend_ssa *ssa,
+ const zend_script *script,
+ zend_bitset worklist,
+ zend_op *opline,
+ zend_ssa_op *ssa_op,
+ const zend_op **ssa_opcodes,
+ zend_long optimization_level,
+ bool update_worklist)
+{
+ uint32_t t1, t2;
+ uint32_t tmp, orig;
+ zend_ssa_var *ssa_vars = ssa->vars;
+ zend_ssa_var_info *ssa_var_info = ssa->var_info;
+ zend_class_entry *ce;
+ int j;
+
+ if (opline->opcode == ZEND_OP_DATA) {
+ opline--;
+ ssa_op--;
+ }
+
+ t1 = OP1_INFO();
+ t2 = OP2_INFO();
+
+ /* If one of the operands cannot have any type, this means the operand derives from
+ * unreachable code. Propagate the empty result early, so that that the following
+ * code may assume that operands have at least one type. */
+ if (!(t1 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))
+ || !(t2 & (MAY_BE_ANY|MAY_BE_UNDEF|MAY_BE_CLASS))) {
+ tmp = 0;
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->op2_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ }
+ return 1;
+ }
+
+ switch (opline->opcode) {
+ case ZEND_ADD:
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_DIV:
+ case ZEND_POW:
+ case ZEND_MOD:
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ case ZEND_SL:
+ case ZEND_SR:
+ case ZEND_CONCAT:
+ tmp = binary_op_result_type(ssa, opline->opcode, t1, t2, ssa_op->result_def, optimization_level);
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_BW_NOT:
+ tmp = 0;
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_RC1;
+ }
+ if (t1 & (MAY_BE_ANY-MAY_BE_STRING)) {
+ tmp |= MAY_BE_LONG;
+ }
+ if (!(ZEND_OPTIMIZER_IGNORE_OVERLOADING & optimization_level)) {
+ if (t1 & MAY_BE_OBJECT) {
+ /* Potentially overloaded operator. */
+ tmp |= MAY_BE_OBJECT | MAY_BE_RC1;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_BEGIN_SILENCE:
+ UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
+ break;
+ case ZEND_BOOL_NOT:
+ case ZEND_BOOL_XOR:
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_INSTANCEOF:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ case ZEND_BOOL:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ISSET_ISEMPTY_STATIC_PROP:
+ case ZEND_ASSERT_CHECK:
+ case ZEND_IN_ARRAY:
+ case ZEND_ARRAY_KEY_EXISTS:
+ UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
+ break;
+ case ZEND_CAST:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) &&
+ (opline->extended_value == IS_ARRAY ||
+ opline->extended_value == IS_OBJECT)) {
+ tmp |= MAY_BE_RCN;
+ } else if ((t1 & MAY_BE_STRING) &&
+ opline->extended_value == IS_STRING) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ tmp = 1 << opline->extended_value;
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ if ((tmp & MAY_BE_ANY) == (t1 & MAY_BE_ANY)) {
+ tmp |= (t1 & MAY_BE_RC1) | MAY_BE_RCN;
+ } else if ((opline->extended_value == IS_ARRAY ||
+ opline->extended_value == IS_OBJECT) &&
+ (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT))) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else if (opline->extended_value == IS_STRING &&
+ (t1 & (MAY_BE_STRING|MAY_BE_OBJECT))) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else {
+ tmp |= MAY_BE_RC1;
+ }
+ }
+ if (opline->extended_value == IS_ARRAY) {
+ if (t1 & MAY_BE_ARRAY) {
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF);
+ }
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= ((t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT) | ((t1 & MAY_BE_ANY) ? MAY_BE_ARRAY_PACKED : 0);
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP_SET:
+ case ZEND_COALESCE:
+ case ZEND_COPY_TMP:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (t1 & (MAY_BE_RC1|MAY_BE_REF)) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ tmp = t1 & ~(MAY_BE_UNDEF|MAY_BE_REF);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= (t1 & (MAY_BE_RC1|MAY_BE_RCN));
+ if (opline->op1_type == IS_CV) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ if (opline->opcode != ZEND_QM_ASSIGN) {
+ /* COALESCE and JMP_SET result can't be null */
+ tmp &= ~MAY_BE_NULL;
+ if (opline->opcode == ZEND_JMP_SET) {
+ /* JMP_SET result can't be false either */
+ tmp &= ~MAY_BE_FALSE;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
+ break;
+ case ZEND_JMP_NULL:
+ if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EXPR) {
+ tmp = MAY_BE_NULL;
+ } else if (opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_ISSET) {
+ tmp = MAY_BE_FALSE;
+ } else {
+ ZEND_ASSERT(opline->extended_value == ZEND_SHORT_CIRCUITING_CHAIN_EMPTY);
+ tmp = MAY_BE_TRUE;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_ASSIGN_OP:
+ case ZEND_ASSIGN_DIM_OP:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_STATIC_PROP_OP:
+ {
+ zend_property_info *prop_info = NULL;
+ orig = 0;
+ tmp = 0;
+ if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
+ prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
+ orig = t1;
+ t1 = zend_fetch_prop_type(script, prop_info, &ce);
+ t2 = OP1_DATA_INFO();
+ } else if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
+ if (t1 & MAY_BE_ARRAY_OF_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ orig = t1;
+ t1 = zend_array_element_type(t1, opline->op1_type, 1, 0);
+ t2 = OP1_DATA_INFO();
+ } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
+ prop_info = zend_fetch_static_prop_info(script, op_array, ssa, opline);
+ t1 = zend_fetch_prop_type(script, prop_info, &ce);
+ t2 = OP1_DATA_INFO();
+ } else {
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ }
+
+ tmp |= binary_op_result_type(
+ ssa, opline->extended_value, t1, t2,
+ opline->opcode == ZEND_ASSIGN_OP ? ssa_op->op1_def : -1, optimization_level);
+ if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY)) {
+ tmp |= MAY_BE_RC1;
+ }
+ if (tmp & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+
+ if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
+ if (opline->op1_type == IS_CV) {
+ orig = assign_dim_result_type(orig, OP2_INFO(), tmp, opline->op2_type);
+ UPDATE_SSA_TYPE(orig, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
+ if (opline->op1_type == IS_CV) {
+ orig = (orig & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
+ UPDATE_SSA_TYPE(orig, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP) {
+ /* Nothing to do */
+ } else {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ ce = NULL;
+ if (opline->opcode == ZEND_ASSIGN_DIM_OP) {
+ if (opline->op2_type == IS_UNUSED) {
+ /* When appending to an array and the LONG_MAX key is already used
+ * null will be returned. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) {
+ /* Arrays and objects cannot be used as keys. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) {
+ /* null and false are implicitly converted to array, anything else
+ * results in a null return value. */
+ tmp |= MAY_BE_NULL;
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_OBJ_OP) {
+ /* The return value must also satisfy the property type */
+ if (prop_info) {
+ tmp &= zend_fetch_prop_type(script, prop_info, NULL);
+ }
+ } else if (opline->opcode == ZEND_ASSIGN_STATIC_PROP_OP) {
+ /* The return value must also satisfy the property type */
+ if (prop_info) {
+ tmp &= zend_fetch_prop_type(script, prop_info, NULL);
+ }
+ }
+ tmp &= ~MAY_BE_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ }
+ break;
+ }
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ tmp = 0;
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RC1;
+ if (ssa_op->result_def >= 0) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
+ if (!ssa_var_info[ssa_op->op1_use].has_range ||
+ (opline->opcode == ZEND_PRE_DEC &&
+ (ssa_var_info[ssa_op->op1_use].range.underflow ||
+ ssa_var_info[ssa_op->op1_use].range.min == ZEND_LONG_MIN)) ||
+ (opline->opcode == ZEND_PRE_INC &&
+ (ssa_var_info[ssa_op->op1_use].range.overflow ||
+ ssa_var_info[ssa_op->op1_use].range.max == ZEND_LONG_MAX))) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else {
+ if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ if (opline->opcode == ZEND_PRE_INC) {
+ tmp |= MAY_BE_LONG;
+ } else {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ if (t1 & MAY_BE_LONG) {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY);
+ }
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ if (ssa_op->result_def >= 0) {
+ tmp = 0;
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RC1|MAY_BE_RCN;
+ }
+ tmp |= t1 & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RCN);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ tmp = 0;
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RC1;
+ }
+ if ((t1 & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_LONG) {
+ if (!ssa_var_info[ssa_op->op1_use].has_range ||
+ (opline->opcode == ZEND_POST_DEC &&
+ (ssa_var_info[ssa_op->op1_use].range.underflow ||
+ ssa_var_info[ssa_op->op1_use].range.min == ZEND_LONG_MIN)) ||
+ (opline->opcode == ZEND_POST_INC &&
+ (ssa_var_info[ssa_op->op1_use].range.overflow ||
+ ssa_var_info[ssa_op->op1_use].range.max == ZEND_LONG_MAX))) {
+ /* may overflow */
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ } else {
+ tmp |= MAY_BE_LONG;
+ }
+ } else {
+ if (t1 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ if (opline->opcode == ZEND_POST_INC) {
+ tmp |= MAY_BE_LONG;
+ } else {
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ if (t1 & MAY_BE_LONG) {
+ tmp |= MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_DOUBLE) {
+ tmp |= MAY_BE_DOUBLE;
+ }
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_LONG | MAY_BE_DOUBLE;
+ }
+ tmp |= t1 & (MAY_BE_FALSE | MAY_BE_TRUE | MAY_BE_RESOURCE | MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_ARRAY_KEY_ANY);
+ }
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_DIM:
+ if (opline->op1_type == IS_CV) {
+ tmp = assign_dim_result_type(t1, t2, OP1_DATA_INFO(), opline->op2_type);
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = 0;
+ if (t1 & MAY_BE_STRING) {
+ tmp |= MAY_BE_STRING;
+ }
+ if (t1 & ((MAY_BE_ANY|MAY_BE_UNDEF) - MAY_BE_STRING)) {
+ tmp |= (OP1_DATA_INFO() & (MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
+
+ if (opline->op2_type == IS_UNUSED) {
+ /* When appending to an array and the LONG_MAX key is already used
+ * null will be returned. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t2 & (MAY_BE_ARRAY | MAY_BE_OBJECT)) {
+ /* Arrays and objects cannot be used as keys. */
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & (MAY_BE_ANY - (MAY_BE_NULL | MAY_BE_FALSE | MAY_BE_STRING | MAY_BE_ARRAY))) {
+ /* undef, null and false are implicitly converted to array, anything else
+ * results in a null return value. */
+ tmp |= MAY_BE_NULL;
+ }
+ }
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_REF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if ((ssa_op+1)->op1_def >= 0) {
+ opline++;
+ ssa_op++;
+ tmp = OP1_INFO();
+ if (tmp & (MAY_BE_ANY | MAY_BE_REF)) {
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_OBJ:
+ if (opline->op1_type == IS_CV) {
+ tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ // TODO: If there is no __set we might do better
+ tmp = zend_fetch_prop_type(script,
+ zend_fetch_prop_info(op_array, ssa, opline, ssa_op), &ce);
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ }
+ if ((ssa_op+1)->op1_def >= 0) {
+ opline++;
+ ssa_op++;
+ tmp = OP1_INFO();
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP:
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF | MAY_BE_RC1 | MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if ((ssa_op+1)->op1_def >= 0) {
+ opline++;
+ ssa_op++;
+ tmp = OP1_INFO();
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ if (opline->op1_type == IS_CV) {
+ tmp = (t1 & (MAY_BE_REF|MAY_BE_OBJECT))|MAY_BE_RC1|MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ // TODO: ???
+ tmp = MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ASSIGN:
+ if (ssa_op->op2_def >= 0) {
+ tmp = t2;
+ if (tmp & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ }
+ tmp = t2 & ~(MAY_BE_UNDEF|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ if (t1 & MAY_BE_REF) {
+ tmp |= MAY_BE_REF;
+ }
+ if (t2 & MAY_BE_REF) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
+ tmp |= t2 & (MAY_BE_RC1|MAY_BE_RCN);
+ } else if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RCN;
+ }
+ if (RETURN_VALUE_USED(opline) && (tmp & MAY_BE_RC1)) {
+ tmp |= MAY_BE_RCN;
+ }
+ if (ssa_op->op1_def >= 0) {
+ if (ssa_var_info[ssa_op->op1_def].use_as_double) {
+ tmp &= ~MAY_BE_LONG;
+ tmp |= MAY_BE_DOUBLE;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp & ~MAY_BE_REF, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ASSIGN_REF:
+// TODO: ???
+ if (opline->op2_type == IS_CV) {
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ }
+ if (opline->op2_type == IS_VAR && opline->extended_value == ZEND_RETURNS_FUNCTION) {
+ tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
+ } else {
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ASSIGN_OBJ_REF:
+ if (opline->op1_type == IS_CV) {
+ tmp = t1;
+ if (tmp & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+
+ t2 = OP1_DATA_INFO();
+ if ((opline+1)->op1_type == IS_VAR && (opline->extended_value & ZEND_RETURNS_FUNCTION)) {
+ tmp = (MAY_BE_REF | MAY_BE_RCN | MAY_BE_RC1 | t2) & ~MAY_BE_UNDEF;
+ } else {
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ if (ssa_op->result_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ if ((opline+1)->op1_type == IS_CV) {
+ opline++;
+ ssa_op++;
+ tmp = (MAY_BE_REF | t2) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_ASSIGN_STATIC_PROP_REF:
+ if ((opline+1)->op1_type == IS_CV) {
+ opline++;
+ ssa_op++;
+ UPDATE_SSA_TYPE(MAY_BE_REF, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_BIND_GLOBAL:
+ tmp = MAY_BE_REF | MAY_BE_ANY
+ | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ break;
+ case ZEND_BIND_STATIC:
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | ((opline->extended_value & ZEND_BIND_REF) ? MAY_BE_REF : (MAY_BE_RC1 | MAY_BE_RCN));
+ if (opline->extended_value & ZEND_BIND_IMPLICIT) {
+ tmp |= MAY_BE_UNDEF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ break;
+ case ZEND_SEND_VAR:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (t1 & (MAY_BE_RC1|MAY_BE_REF)) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_BIND_LEXICAL:
+ if (ssa_op->op2_def >= 0) {
+ if (opline->extended_value & ZEND_BIND_REF) {
+ tmp = t2 | MAY_BE_REF;
+ } else {
+ tmp = t2 & ~(MAY_BE_RC1|MAY_BE_RCN);
+ if (t2 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->op2_def);
+ }
+ break;
+ case ZEND_YIELD:
+ if (ssa_op->op1_def >= 0) {
+ if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
+ tmp = t1 | MAY_BE_REF;
+ } else {
+ tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN);
+ if (t1 & (MAY_BE_RC1|MAY_BE_RCN)) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF
+ | MAY_BE_RC1 | MAY_BE_RCN;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ if (ssa_op->op1_def >= 0) {
+ tmp = (t1 & MAY_BE_UNDEF)|MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_SEND_REF:
+ if (ssa_op->op1_def >= 0) {
+ tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_SEND_UNPACK:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (t1 & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ if (t1 & MAY_BE_ARRAY_OF_ANY) {
+ /* SEND_UNPACK may acquire references into the array */
+ tmp |= MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ }
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_FAST_CONCAT:
+ case ZEND_ROPE_INIT:
+ case ZEND_ROPE_ADD:
+ case ZEND_ROPE_END:
+ UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
+ break;
+ case ZEND_RECV:
+ case ZEND_RECV_INIT:
+ case ZEND_RECV_VARIADIC:
+ {
+ /* Typehinting */
+ zend_arg_info *arg_info = &op_array->arg_info[opline->op1.num-1];
+
+ ce = NULL;
+ tmp = zend_fetch_arg_info_type(script, arg_info, &ce);
+ if (ZEND_ARG_SEND_MODE(arg_info)) {
+ tmp |= MAY_BE_REF;
+ }
+
+ if (opline->opcode == ZEND_RECV_VARIADIC) {
+ uint32_t elem_type = tmp & MAY_BE_REF
+ ? MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF
+ : (tmp & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ tmp = MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|elem_type;
+ ce = NULL;
+ }
+
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ break;
+ }
+ case ZEND_DECLARE_ANON_CLASS:
+ UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_op->result_def);
+ if (script && (ce = zend_hash_find_ptr(&script->class_table, Z_STR_P(CRT_CONSTANT(opline->op1)))) != NULL) {
+ UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_CLASS:
+ UPDATE_SSA_TYPE(MAY_BE_CLASS, ssa_op->result_def);
+ if (opline->op2_type == IS_UNUSED) {
+ switch (opline->op1.num & ZEND_FETCH_CLASS_MASK) {
+ case ZEND_FETCH_CLASS_SELF:
+ if (op_array->scope) {
+ UPDATE_SSA_OBJ_TYPE(op_array->scope, 0, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_CLASS_PARENT:
+ if (op_array->scope && op_array->scope->parent && (op_array->scope->ce_flags & ZEND_ACC_LINKED)) {
+ UPDATE_SSA_OBJ_TYPE(op_array->scope->parent, 0, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_CLASS_STATIC:
+ default:
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ break;
+ }
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op2);
+ if (Z_TYPE_P(zv) == IS_STRING) {
+ ce = get_class_entry(script, Z_STR_P(zv+1));
+ UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ } else {
+ COPY_SSA_OBJ_TYPE(ssa_op->op2_use, ssa_op->result_def);
+ }
+ break;
+ case ZEND_NEW:
+ tmp = MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT;
+ if (opline->op1_type == IS_CONST &&
+ (ce = get_class_entry(script, Z_STR_P(CRT_CONSTANT(opline->op1)+1))) != NULL) {
+ UPDATE_SSA_OBJ_TYPE(ce, 0, ssa_op->result_def);
+ } else if ((t1 & MAY_BE_CLASS) && ssa_op->op1_use >= 0 && ssa_var_info[ssa_op->op1_use].ce) {
+ UPDATE_SSA_OBJ_TYPE(ssa_var_info[ssa_op->op1_use].ce, ssa_var_info[ssa_op->op1_use].is_instanceof, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_CLONE:
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_OBJECT, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
+ break;
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ if (ssa_op->op1_def >= 0) {
+ if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
+ tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ } else if ((t1 & (MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN)) == MAY_BE_REF) {
+ tmp = (MAY_BE_REF | t1) & ~(MAY_BE_UNDEF|MAY_BE_RC1|MAY_BE_RCN);
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_NULL;
+ }
+ } else if (t1 & MAY_BE_REF) {
+ tmp = (MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | t1);
+ } else {
+ tmp = t1;
+ if (t1 & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_RC1|MAY_BE_ARRAY;
+ if (ssa_op->result_use >= 0) {
+ tmp |= ssa_var_info[ssa_op->result_use].type;
+ }
+ if (opline->op1_type != IS_UNUSED) {
+ tmp |= (t1 & MAY_BE_ANY) << MAY_BE_ARRAY_SHIFT;
+ if (t1 & MAY_BE_UNDEF) {
+ tmp |= MAY_BE_ARRAY_OF_NULL;
+ }
+ if (opline->extended_value & ZEND_ARRAY_ELEMENT_REF) {
+ tmp |= MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ }
+ if (opline->op2_type == IS_UNUSED) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ } else {
+ if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_DOUBLE|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ if (t2 & (MAY_BE_STRING)) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ if (opline->op2_type != IS_CONST) {
+ // FIXME: numeric string
+ tmp |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ }
+ if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ tmp |= MAY_BE_ARRAY_KEY_STRING;
+ }
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_ADD_ARRAY_UNPACK:
+ tmp = ssa_var_info[ssa_op->result_use].type;
+ ZEND_ASSERT(tmp & MAY_BE_ARRAY);
+ tmp |= t1 & (MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF);
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_UNSET_CV:
+ tmp = MAY_BE_UNDEF;
+ if (!op_array->function_name) {
+ /* In global scope, we know nothing */
+ tmp |= MAY_BE_REF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ break;
+ case ZEND_UNSET_DIM:
+ case ZEND_UNSET_OBJ:
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(t1, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ if (ssa_op->op1_def >= 0) {
+ tmp = t1;
+ if (opline->opcode == ZEND_FE_RESET_RW) {
+ tmp |= MAY_BE_REF;
+ } else if (t1 & MAY_BE_RC1) {
+ tmp |= MAY_BE_RCN;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ if (opline->opcode == ZEND_FE_RESET_RW) {
+//???
+ tmp = MAY_BE_REF | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT));
+ } else {
+ tmp = MAY_BE_RC1 | MAY_BE_RCN | (t1 & (MAY_BE_ARRAY | MAY_BE_OBJECT | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF));
+ }
+ /* The result is set to UNDEF for invalid foreach inputs. */
+ if ((t1 & (MAY_BE_ANY | MAY_BE_UNDEF)) & ~(MAY_BE_ARRAY | MAY_BE_OBJECT)) {
+ tmp |= MAY_BE_UNDEF;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->result_def);
+ break;
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ tmp = t2 & MAY_BE_REF;
+ if (t1 & MAY_BE_OBJECT) {
+ if (opline->opcode == ZEND_FE_FETCH_RW) {
+ tmp |= MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= MAY_BE_REF | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ }
+ if (t1 & MAY_BE_ARRAY) {
+ if (opline->opcode == ZEND_FE_FETCH_RW) {
+ tmp |= MAY_BE_REF | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ } else {
+ tmp |= ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
+ if (tmp & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (t1 & MAY_BE_ARRAY_OF_REF) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ } else if (tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->op2_def);
+ if (ssa_op->result_def >= 0) {
+ tmp = (ssa_op->result_use >= 0) ? RES_USE_INFO() : 0;
+ if (t1 & MAY_BE_OBJECT) {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ }
+ if (t1 & MAY_BE_ARRAY) {
+ if (t1 & MAY_BE_ARRAY_KEY_LONG) {
+ tmp |= MAY_BE_LONG;
+ }
+ if (t1 & MAY_BE_ARRAY_KEY_STRING) {
+ tmp |= MAY_BE_STRING | MAY_BE_RCN;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ case ZEND_FETCH_DIM_R:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_LIST_R:
+ case ZEND_FETCH_LIST_W:
+ if (ssa_op->op1_def >= 0) {
+ uint32_t key_type = 0;
+ tmp = t1 & ~(MAY_BE_RC1|MAY_BE_RCN);
+ if (opline->opcode == ZEND_FETCH_DIM_W ||
+ opline->opcode == ZEND_FETCH_DIM_RW ||
+ opline->opcode == ZEND_FETCH_DIM_FUNC_ARG ||
+ opline->opcode == ZEND_FETCH_LIST_W) {
+ if (t1 & (MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE)) {
+ if (opline->opcode != ZEND_FETCH_DIM_FUNC_ARG) {
+ tmp &= ~(MAY_BE_UNDEF|MAY_BE_NULL|MAY_BE_FALSE);
+ }
+ tmp |= MAY_BE_ARRAY | MAY_BE_RC1;
+ }
+ if (t1 & (MAY_BE_STRING|MAY_BE_ARRAY)) {
+ tmp |= MAY_BE_RC1;
+ if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG) {
+ tmp |= t1 & MAY_BE_RCN;
+ }
+ }
+ if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= t1 & (MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if (opline->op2_type == IS_UNUSED) {
+ key_type |= MAY_BE_ARRAY_KEY_LONG;
+ } else {
+ if (t2 & (MAY_BE_LONG|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_RESOURCE|MAY_BE_DOUBLE)) {
+ key_type |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ if (t2 & MAY_BE_STRING) {
+ key_type |= MAY_BE_ARRAY_KEY_STRING;
+ if (opline->op2_type != IS_CONST) {
+ // FIXME: numeric string
+ key_type |= MAY_BE_ARRAY_KEY_LONG;
+ }
+ }
+ if (t2 & (MAY_BE_UNDEF | MAY_BE_NULL)) {
+ key_type |= MAY_BE_ARRAY_KEY_STRING;
+ }
+ }
+ } else if (opline->opcode == ZEND_FETCH_DIM_UNSET) {
+ if (t1 & MAY_BE_ARRAY) {
+ tmp |= MAY_BE_RC1;
+ }
+ if (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE)) {
+ tmp |= t1 & (MAY_BE_RC1|MAY_BE_RCN);
+ }
+ }
+ if (opline->opcode == ZEND_FETCH_DIM_RW
+ || opline->opcode == ZEND_FETCH_DIM_W
+ || opline->opcode == ZEND_FETCH_DIM_FUNC_ARG
+ || opline->opcode == ZEND_FETCH_LIST_W) {
+ j = ssa_vars[ssa_op->result_def].use_chain;
+ while (j >= 0) {
+ zend_uchar opcode;
+
+ if (!ssa_opcodes) {
+ ZEND_ASSERT(j == (opline - op_array->opcodes) + 1 && "Use must be in next opline");
+ opcode = op_array->opcodes[j].opcode;
+ } else {
+ ZEND_ASSERT(ssa_opcodes[j] == opline + 1 && "Use must be in next opline");
+ opcode = ssa_opcodes[j]->opcode;
+ }
+ switch (opcode) {
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_LIST_W:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_DIM_OP:
+ tmp |= key_type | MAY_BE_ARRAY | MAY_BE_ARRAY_OF_ARRAY;
+ break;
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ case ZEND_SEND_REF:
+ case ZEND_ASSIGN_REF:
+ case ZEND_YIELD:
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ case ZEND_RETURN_BY_REF:
+ case ZEND_VERIFY_RETURN_TYPE:
+ case ZEND_MAKE_REF:
+ case ZEND_FE_RESET_RW:
+ tmp |= key_type | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ break;
+ case ZEND_PRE_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_POST_INC:
+ case ZEND_POST_DEC:
+ if (tmp & MAY_BE_ARRAY_OF_LONG) {
+ /* may overflow */
+ tmp |= key_type | MAY_BE_ARRAY_OF_DOUBLE;
+ } else if (!(tmp & (MAY_BE_ARRAY_OF_LONG|MAY_BE_ARRAY_OF_DOUBLE))) {
+ tmp |= key_type | MAY_BE_ARRAY_OF_LONG | MAY_BE_ARRAY_OF_DOUBLE;
+ }
+ break;
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_ASSIGN_OBJ:
+ case ZEND_ASSIGN_OBJ_OP:
+ case ZEND_ASSIGN_OBJ_REF:
+ case ZEND_PRE_INC_OBJ:
+ case ZEND_PRE_DEC_OBJ:
+ case ZEND_POST_INC_OBJ:
+ case ZEND_POST_DEC_OBJ:
+ /* These will result in an error exception, unless the element
+ * is already an object. */
+ break;
+ case ZEND_SEND_VAR:
+ /* This can occur if a DIM_FETCH_FUNC_ARG with UNUSED op2 is left
+ * behind, because it can't be converted to DIM_FETCH_R. */
+ break;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ j = zend_ssa_next_use(ssa->ops, ssa_op->result_def, j);
+ ZEND_ASSERT(j < 0 && "There should only be one use");
+ }
+ }
+ if ((tmp & MAY_BE_ARRAY) && (tmp & MAY_BE_ARRAY_KEY_ANY)) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ } else {
+ /* invalid key type */
+ tmp = (tmp & (MAY_BE_RC1|MAY_BE_RCN)) | (t1 & ~(MAY_BE_RC1|MAY_BE_RCN));
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ COPY_SSA_OBJ_TYPE(ssa_op->op1_use, ssa_op->op1_def);
+ }
+ /* FETCH_LIST on a string behaves like FETCH_R on null */
+ tmp = zend_array_element_type(
+ opline->opcode != ZEND_FETCH_LIST_R ? t1 : ((t1 & ~MAY_BE_STRING) | MAY_BE_NULL),
+ opline->op1_type,
+ opline->result_type == IS_VAR,
+ opline->op2_type == IS_UNUSED);
+ if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_FETCH_THIS:
+ UPDATE_SSA_OBJ_TYPE(op_array->scope, 1, ssa_op->result_def);
+ UPDATE_SSA_TYPE(MAY_BE_RCN|MAY_BE_OBJECT, ssa_op->result_def);
+ break;
+ case ZEND_FETCH_OBJ_R:
+ case ZEND_FETCH_OBJ_IS:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ if (ssa_op->result_def >= 0) {
+ zend_property_info *prop_info = zend_fetch_prop_info(op_array, ssa, opline, ssa_op);
+
+ tmp = zend_fetch_prop_type(script, prop_info, &ce);
+ if (opline->result_type != IS_TMP_VAR) {
+ tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
+ } else if (!(opline->op1_type & (IS_VAR|IS_TMP_VAR)) || !(t1 & MAY_BE_RC1)) {
+ zend_class_entry *ce = NULL;
+
+ if (opline->op1_type == IS_UNUSED) {
+ ce = op_array->scope;
+ } else if (ssa_op->op1_use >= 0 && !ssa->var_info[ssa_op->op1_use].is_instanceof) {
+ ce = ssa->var_info[ssa_op->op1_use].ce;
+ }
+ if (prop_info) {
+ /* FETCH_OBJ_R/IS for plain property increments reference counter,
+ so it can't be 1 */
+ if (ce && !ce->create_object) {
+ tmp &= ~MAY_BE_RC1;
+ }
+ } else {
+ if (ce && !ce->create_object && !ce->__get) {
+ tmp &= ~MAY_BE_RC1;
+ }
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ }
+ break;
+ case ZEND_FETCH_STATIC_PROP_R:
+ case ZEND_FETCH_STATIC_PROP_IS:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ tmp = zend_fetch_prop_type(script,
+ zend_fetch_static_prop_info(script, op_array, ssa, opline), &ce);
+ if (opline->result_type != IS_TMP_VAR) {
+ tmp |= MAY_BE_REF | MAY_BE_INDIRECT;
+ } else {
+ tmp &= ~MAY_BE_RC1;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ }
+ break;
+ case ZEND_DO_FCALL:
+ case ZEND_DO_ICALL:
+ case ZEND_DO_UCALL:
+ case ZEND_DO_FCALL_BY_NAME:
+ if (ssa_op->result_def >= 0) {
+ zend_func_info *func_info = ZEND_FUNC_INFO(op_array);
+ zend_call_info *call_info;
+
+ if (!func_info || !func_info->call_map) {
+ goto unknown_opcode;
+ }
+ call_info = func_info->call_map[opline - op_array->opcodes];
+ if (!call_info) {
+ goto unknown_opcode;
+ }
+
+ zend_class_entry *ce;
+ bool ce_is_instanceof;
+ tmp = zend_get_func_info(call_info, ssa, &ce, &ce_is_instanceof);
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, ce_is_instanceof, ssa_op->result_def);
+ }
+ }
+ break;
+ case ZEND_FETCH_CONSTANT:
+ case ZEND_FETCH_CLASS_CONSTANT:
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN|MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
+ break;
+ case ZEND_STRLEN:
+ tmp = MAY_BE_LONG;
+ if (t1 & (MAY_BE_ANY - (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING))) {
+ tmp |= MAY_BE_NULL;
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ break;
+ case ZEND_COUNT:
+ case ZEND_FUNC_NUM_ARGS:
+ UPDATE_SSA_TYPE(MAY_BE_LONG, ssa_op->result_def);
+ break;
+ case ZEND_FUNC_GET_ARGS:
+ UPDATE_SSA_TYPE(MAY_BE_RC1|MAY_BE_RCN| MAY_BE_ARRAY | MAY_BE_ARRAY_PACKED | MAY_BE_ARRAY_OF_ANY, ssa_op->result_def);
+ break;
+ case ZEND_GET_CLASS:
+ case ZEND_GET_CALLED_CLASS:
+ UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_STRING|MAY_BE_RCN, ssa_op->result_def);
+ break;
+ case ZEND_GET_TYPE:
+ UPDATE_SSA_TYPE(MAY_BE_STRING|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
+ break;
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ UPDATE_SSA_TYPE(MAY_BE_FALSE|MAY_BE_TRUE, ssa_op->result_def);
+ break;
+ case ZEND_VERIFY_RETURN_TYPE:
+ if (t1 & MAY_BE_REF) {
+ tmp = t1;
+ ce = NULL;
+ } else {
+ zend_arg_info *ret_info = op_array->arg_info - 1;
+ tmp = zend_fetch_arg_info_type(script, ret_info, &ce);
+ }
+ if (opline->op1_type & (IS_TMP_VAR|IS_VAR|IS_CV)) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->op1_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->op1_def);
+ }
+ } else {
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ce) {
+ UPDATE_SSA_OBJ_TYPE(ce, 1, ssa_op->result_def);
+ } else {
+ UPDATE_SSA_OBJ_TYPE(NULL, 0, ssa_op->result_def);
+ }
+ }
+ break;
+ case ZEND_MAKE_REF:
+ tmp = MAY_BE_REF|MAY_BE_RC1|MAY_BE_RCN|MAY_BE_ANY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ if (ssa_op->op1_def >= 0) {
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ break;
+ case ZEND_CATCH:
+ /* Forbidden opcodes */
+ ZEND_UNREACHABLE();
+ break;
+ default:
+unknown_opcode:
+ if (ssa_op->op1_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ UPDATE_SSA_TYPE(tmp, ssa_op->op1_def);
+ }
+ if (ssa_op->result_def >= 0) {
+ tmp = MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ if (opline->result_type == IS_TMP_VAR) {
+ if (opline->opcode == ZEND_FETCH_R || opline->opcode == ZEND_FETCH_IS) {
+ tmp |= MAY_BE_RCN;
+ } else {
+ tmp |= MAY_BE_RC1 | MAY_BE_RCN;
+ }
+ } else {
+ tmp |= MAY_BE_REF | MAY_BE_RC1 | MAY_BE_RCN;
+ switch (opline->opcode) {
+ case ZEND_FETCH_W:
+ case ZEND_FETCH_RW:
+ case ZEND_FETCH_FUNC_ARG:
+ case ZEND_FETCH_UNSET:
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_DIM_RW:
+ case ZEND_FETCH_DIM_FUNC_ARG:
+ case ZEND_FETCH_DIM_UNSET:
+ case ZEND_FETCH_OBJ_W:
+ case ZEND_FETCH_OBJ_RW:
+ case ZEND_FETCH_OBJ_FUNC_ARG:
+ case ZEND_FETCH_OBJ_UNSET:
+ case ZEND_FETCH_STATIC_PROP_W:
+ case ZEND_FETCH_STATIC_PROP_RW:
+ case ZEND_FETCH_STATIC_PROP_FUNC_ARG:
+ case ZEND_FETCH_STATIC_PROP_UNSET:
+ tmp |= MAY_BE_INDIRECT;
+ break;
+ }
+ }
+ UPDATE_SSA_TYPE(tmp, ssa_op->result_def);
+ }
+ break;
+ }
+
+ return SUCCESS;
+}
+
+ZEND_API int zend_update_type_info(
+ const zend_op_array *op_array,
+ zend_ssa *ssa,
+ const zend_script *script,
+ zend_op *opline,
+ zend_ssa_op *ssa_op,
+ const zend_op **ssa_opcodes,
+ zend_long optimization_level)
+{
+ return _zend_update_type_info(op_array, ssa, script, NULL, opline, ssa_op, ssa_opcodes, optimization_level, 0);
+}
+
+static uint32_t get_class_entry_rank(zend_class_entry *ce) {
+ uint32_t rank = 0;
+ if (ce->ce_flags & ZEND_ACC_LINKED) {
+ while (ce->parent) {
+ rank++;
+ ce = ce->parent;
+ }
+ }
+ return rank;
+}
+
+/* Compute least common ancestor on class inheritance tree only */
+static zend_class_entry *join_class_entries(
+ zend_class_entry *ce1, zend_class_entry *ce2, int *is_instanceof) {
+ uint32_t rank1, rank2;
+ if (ce1 == ce2) {
+ return ce1;
+ }
+ if (!ce1 || !ce2) {
+ return NULL;
+ }
+
+ rank1 = get_class_entry_rank(ce1);
+ rank2 = get_class_entry_rank(ce2);
+
+ while (rank1 != rank2) {
+ if (rank1 > rank2) {
+ ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
+ rank1--;
+ } else {
+ ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
+ rank2--;
+ }
+ }
+
+ while (ce1 != ce2) {
+ ce1 = !(ce1->ce_flags & ZEND_ACC_LINKED) ? NULL : ce1->parent;
+ ce2 = !(ce2->ce_flags & ZEND_ACC_LINKED) ? NULL : ce2->parent;
+ }
+
+ if (ce1) {
+ *is_instanceof = 1;
+ }
+ return ce1;
+}
+
+int zend_infer_types_ex(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_bitset worklist, zend_long optimization_level)
+{
+ zend_basic_block *blocks = ssa->cfg.blocks;
+ zend_ssa_var *ssa_vars = ssa->vars;
+ zend_ssa_var_info *ssa_var_info = ssa->var_info;
+ int ssa_vars_count = ssa->vars_count;
+ int i, j;
+ uint32_t tmp, worklist_len = zend_bitset_len(ssa_vars_count);
+ bool update_worklist = 1;
+
+ while (!zend_bitset_empty(worklist, worklist_len)) {
+ j = zend_bitset_first(worklist, worklist_len);
+ zend_bitset_excl(worklist, j);
+ if (ssa_vars[j].definition_phi) {
+ zend_ssa_phi *p = ssa_vars[j].definition_phi;
+ if (p->pi >= 0) {
+ zend_class_entry *ce = ssa_var_info[p->sources[0]].ce;
+ int is_instanceof = ssa_var_info[p->sources[0]].is_instanceof;
+ tmp = get_ssa_var_info(ssa, p->sources[0]);
+
+ if (!p->has_range_constraint) {
+ zend_ssa_type_constraint *constraint = &p->constraint.type;
+ tmp &= constraint->type_mask;
+ if (!(tmp & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
+ tmp &= ~(MAY_BE_RC1|MAY_BE_RCN);
+ }
+ if ((tmp & MAY_BE_OBJECT) && constraint->ce && ce != constraint->ce) {
+ if (!ce) {
+ ce = constraint->ce;
+ is_instanceof = 1;
+ } else if (is_instanceof && instanceof_function(constraint->ce, ce)) {
+ ce = constraint->ce;
+ } else {
+ /* Ignore the constraint (either ce instanceof constraint->ce or
+ * they are unrelated, as far as we can statically determine) */
+ }
+ }
+ }
+
+ UPDATE_SSA_TYPE(tmp, j);
+ UPDATE_SSA_OBJ_TYPE(ce, is_instanceof, j);
+ } else {
+ int first = 1;
+ int is_instanceof = 0;
+ zend_class_entry *ce = NULL;
+
+ tmp = 0;
+ for (i = 0; i < blocks[p->block].predecessors_count; i++) {
+ tmp |= get_ssa_var_info(ssa, p->sources[i]);
+ }
+ UPDATE_SSA_TYPE(tmp, j);
+ for (i = 0; i < blocks[p->block].predecessors_count; i++) {
+ zend_ssa_var_info *info;
+
+ ZEND_ASSERT(p->sources[i] >= 0);
+ info = &ssa_var_info[p->sources[i]];
+ if (info->type & MAY_BE_OBJECT) {
+ if (first) {
+ ce = info->ce;
+ is_instanceof = info->is_instanceof;
+ first = 0;
+ } else {
+ is_instanceof |= info->is_instanceof;
+ ce = join_class_entries(ce, info->ce, &is_instanceof);
+ }
+ }
+ }
+ UPDATE_SSA_OBJ_TYPE(ce, ce ? is_instanceof : 0, j);
+ }
+ } else if (ssa_vars[j].definition >= 0) {
+ i = ssa_vars[j].definition;
+ if (_zend_update_type_info(op_array, ssa, script, worklist, op_array->opcodes + i, ssa->ops + i, NULL, optimization_level, 1) == FAILURE) {
+ return FAILURE;
+ }
+ }
+ }
+ return SUCCESS;
+}
+
+static bool is_narrowable_instr(zend_op *opline) {
+ return opline->opcode == ZEND_ADD || opline->opcode == ZEND_SUB
+ || opline->opcode == ZEND_MUL || opline->opcode == ZEND_DIV;
+}
+
+static bool is_effective_op1_double_cast(zend_op *opline, zval *op2) {
+ return (opline->opcode == ZEND_ADD && Z_LVAL_P(op2) == 0)
+ || (opline->opcode == ZEND_SUB && Z_LVAL_P(op2) == 0)
+ || (opline->opcode == ZEND_MUL && Z_LVAL_P(op2) == 1)
+ || (opline->opcode == ZEND_DIV && Z_LVAL_P(op2) == 1);
+}
+static bool is_effective_op2_double_cast(zend_op *opline, zval *op1) {
+ /* In PHP it holds that (double)(0-$int) is bitwise identical to 0.0-(double)$int,
+ * so allowing SUB here is fine. */
+ return (opline->opcode == ZEND_ADD && Z_LVAL_P(op1) == 0)
+ || (opline->opcode == ZEND_SUB && Z_LVAL_P(op1) == 0)
+ || (opline->opcode == ZEND_MUL && Z_LVAL_P(op1) == 1);
+}
+
+/* This function recursively checks whether it's possible to convert an integer variable
+ * initialization to a double initialization. The basic idea is that if the value is used
+ * only in add/sub/mul/div ("narrowable" instructions) with a double result value, then it
+ * will be cast to double at that point anyway, so we may as well do it earlier already.
+ *
+ * The tricky case are chains of operations, where it's not necessarily a given that converting
+ * an integer to double before the chain of operations is the same as converting it after the
+ * chain. What this function does is detect two cases where it is safe:
+ * * If the operations only involve constants, then we can simply verify that performing the
+ * calculation on integers and doubles yields the same value.
+ * * Even if one operand is not known, we may be able to determine that the operations with the
+ * integer replaced by a double only acts as an effective double cast on the unknown operand.
+ * E.g. 0+$i and 0.0+$i only differ by that cast. If then the consuming instruction of this
+ * result will perform a double cast anyway, the conversion is safe.
+ *
+ * The checks happens recursively, while keeping track of which variables are already visisted to
+ * avoid infinite loops. An iterative, worklist driven approach would be possible, but the state
+ * management more cumbersome to implement, so we don't bother for now.
+ */
+static bool can_convert_to_double(
+ const zend_op_array *op_array, zend_ssa *ssa, int var_num,
+ zval *value, zend_bitset visited) {
+ zend_ssa_var *var = &ssa->vars[var_num];
+ zend_ssa_phi *phi;
+ int use;
+ uint32_t type;
+
+ if (zend_bitset_in(visited, var_num)) {
+ return 1;
+ }
+ zend_bitset_incl(visited, var_num);
+
+ for (use = var->use_chain; use >= 0; use = zend_ssa_next_use(ssa->ops, var_num, use)) {
+ zend_op *opline = &op_array->opcodes[use];
+ zend_ssa_op *ssa_op = &ssa->ops[use];
+
+ if (zend_ssa_is_no_val_use(opline, ssa_op, var_num)) {
+ continue;
+ }
+
+ if (!is_narrowable_instr(opline)) {
+ return 0;
+ }
+
+ /* Instruction always returns double, the conversion is certainly fine */
+ type = ssa->var_info[ssa_op->result_def].type;
+ if ((type & MAY_BE_ANY) == MAY_BE_DOUBLE) {
+ continue;
+ }
+
+ /* UNDEF signals that the previous result is an effective double cast, this is only allowed
+ * if this instruction would have done the cast anyway (previous check). */
+ if (Z_ISUNDEF_P(value)) {
+ return 0;
+ }
+
+ /* Check that narrowing can actually be useful */
+ if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
+ return 0;
+ }
+
+ {
+ /* For calculation on original values */
+ zval orig_op1, orig_op2, orig_result;
+ /* For calculation with var_num cast to double */
+ zval dval_op1, dval_op2, dval_result;
+
+ ZVAL_UNDEF(&orig_op1);
+ ZVAL_UNDEF(&dval_op1);
+ if (ssa_op->op1_use == var_num) {
+ ZVAL_COPY_VALUE(&orig_op1, value);
+ ZVAL_DOUBLE(&dval_op1, (double) Z_LVAL_P(value));
+ } else if (opline->op1_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op1);
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
+ ZVAL_COPY_VALUE(&orig_op1, zv);
+ ZVAL_COPY_VALUE(&dval_op1, zv);
+ }
+ }
+
+ ZVAL_UNDEF(&orig_op2);
+ ZVAL_UNDEF(&dval_op2);
+ if (ssa_op->op2_use == var_num) {
+ ZVAL_COPY_VALUE(&orig_op2, value);
+ ZVAL_DOUBLE(&dval_op2, (double) Z_LVAL_P(value));
+ } else if (opline->op2_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op2);
+ if (Z_TYPE_P(zv) == IS_LONG || Z_TYPE_P(zv) == IS_DOUBLE) {
+ ZVAL_COPY_VALUE(&orig_op2, zv);
+ ZVAL_COPY_VALUE(&dval_op2, zv);
+ }
+ }
+
+ ZEND_ASSERT(!Z_ISUNDEF(orig_op1) || !Z_ISUNDEF(orig_op2));
+ if (Z_ISUNDEF(orig_op1)) {
+ if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op2) == 0) {
+ ZVAL_LONG(&orig_result, 0);
+ } else if (is_effective_op1_double_cast(opline, &orig_op2)) {
+ ZVAL_UNDEF(&orig_result);
+ } else {
+ return 0;
+ }
+ } else if (Z_ISUNDEF(orig_op2)) {
+ if (opline->opcode == ZEND_MUL && Z_LVAL(orig_op1) == 0) {
+ ZVAL_LONG(&orig_result, 0);
+ } else if (is_effective_op2_double_cast(opline, &orig_op1)) {
+ ZVAL_UNDEF(&orig_result);
+ } else {
+ return 0;
+ }
+ } else {
+ zend_uchar opcode = opline->opcode;
+
+ if (opcode == ZEND_ASSIGN_OP) {
+ opcode = opline->extended_value;
+ }
+
+ /* Avoid division by zero */
+ if (opcode == ZEND_DIV && zval_get_double(&orig_op2) == 0.0) {
+ return 0;
+ }
+
+ get_binary_op(opcode)(&orig_result, &orig_op1, &orig_op2);
+ get_binary_op(opcode)(&dval_result, &dval_op1, &dval_op2);
+ ZEND_ASSERT(Z_TYPE(dval_result) == IS_DOUBLE);
+ if (zval_get_double(&orig_result) != Z_DVAL(dval_result)) {
+ return 0;
+ }
+ }
+
+ if (!can_convert_to_double(op_array, ssa, ssa_op->result_def, &orig_result, visited)) {
+ return 0;
+ }
+ }
+ }
+
+ for (phi = var->phi_use_chain; phi; phi = zend_ssa_next_use_phi(ssa, var_num, phi)) {
+ /* Check that narrowing can actually be useful */
+ type = ssa->var_info[phi->ssa_var].type;
+ if ((type & MAY_BE_ANY) & ~(MAY_BE_LONG|MAY_BE_DOUBLE)) {
+ return 0;
+ }
+
+ if (!can_convert_to_double(op_array, ssa, phi->ssa_var, value, visited)) {
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static int zend_type_narrowing(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
+{
+ uint32_t bitset_len = zend_bitset_len(ssa->vars_count);
+ zend_bitset visited, worklist;
+ int i, v;
+ zend_op *opline;
+ bool narrowed = 0;
+ ALLOCA_FLAG(use_heap)
+
+ visited = ZEND_BITSET_ALLOCA(2 * bitset_len, use_heap);
+ worklist = visited + bitset_len;
+
+ zend_bitset_clear(worklist, bitset_len);
+
+ for (v = op_array->last_var; v < ssa->vars_count; v++) {
+ if ((ssa->var_info[v].type & (MAY_BE_REF | MAY_BE_ANY | MAY_BE_UNDEF)) != MAY_BE_LONG) continue;
+ if (ssa->vars[v].definition < 0) continue;
+ if (ssa->vars[v].no_val) continue;
+ opline = op_array->opcodes + ssa->vars[v].definition;
+ /* Go through assignments of literal integers and check if they can be converted to
+ * doubles instead, in the hope that we'll narrow long|double to double. */
+ if (opline->opcode == ZEND_ASSIGN && opline->result_type == IS_UNUSED &&
+ opline->op1_type == IS_CV && opline->op2_type == IS_CONST) {
+ zval *value = CRT_CONSTANT(opline->op2);
+
+ zend_bitset_clear(visited, bitset_len);
+ if (can_convert_to_double(op_array, ssa, v, value, visited)) {
+ narrowed = 1;
+ ssa->var_info[v].use_as_double = 1;
+ /* The "visited" vars are exactly those which may change their type due to
+ * narrowing. Reset their types and add them to the type inference worklist */
+ ZEND_BITSET_FOREACH(visited, bitset_len, i) {
+ ssa->var_info[i].type &= ~MAY_BE_ANY;
+ } ZEND_BITSET_FOREACH_END();
+ zend_bitset_union(worklist, visited, bitset_len);
+ }
+ }
+ }
+
+ if (!narrowed) {
+ free_alloca(visited, use_heap);
+ return SUCCESS;
+ }
+
+ if (zend_infer_types_ex(op_array, script, ssa, worklist, optimization_level) != SUCCESS) {
+ free_alloca(visited, use_heap);
+ return FAILURE;
+ }
+
+ free_alloca(visited, use_heap);
+ return SUCCESS;
+}
+
+static int is_recursive_tail_call(const zend_op_array *op_array,
+ zend_op *opline)
+{
+ zend_func_info *info = ZEND_FUNC_INFO(op_array);
+
+ if (info->ssa.ops && info->ssa.vars && info->call_map &&
+ info->ssa.ops[opline - op_array->opcodes].op1_use >= 0 &&
+ info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition >= 0) {
+
+ zend_op *op = op_array->opcodes + info->ssa.vars[info->ssa.ops[opline - op_array->opcodes].op1_use].definition;
+
+ if (op->opcode == ZEND_DO_UCALL) {
+ zend_call_info *call_info = info->call_map[op - op_array->opcodes];
+ if (call_info && op_array == &call_info->callee_func->op_array) {
+ return 1;
+ }
+ }
+ }
+ return 0;
+}
+
+ZEND_API void zend_init_func_return_info(
+ const zend_op_array *op_array, const zend_script *script, zend_ssa_var_info *ret)
+{
+ if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) {
+ zend_arg_info *ret_info = op_array->arg_info - 1;
+ zend_ssa_range tmp_range = {0, 0, 0, 0};
+
+ ret->type = zend_fetch_arg_info_type(script, ret_info, &ret->ce);
+ if (op_array->fn_flags & ZEND_ACC_RETURN_REFERENCE) {
+ ret->type |= MAY_BE_REF;
+ }
+ ret->is_instanceof = (ret->ce) ? 1 : 0;
+ ret->range = tmp_range;
+ ret->has_range = 0;
+ }
+}
+
+void zend_func_return_info(const zend_op_array *op_array,
+ const zend_script *script,
+ int recursive,
+ int widening,
+ zend_ssa_var_info *ret)
+{
+ zend_func_info *info = ZEND_FUNC_INFO(op_array);
+ zend_ssa *ssa = &info->ssa;
+ int blocks_count = info->ssa.cfg.blocks_count;
+ zend_basic_block *blocks = info->ssa.cfg.blocks;
+ int j;
+ uint32_t t1;
+ uint32_t tmp = 0;
+ zend_class_entry *tmp_ce = NULL;
+ int tmp_is_instanceof = -1;
+ zend_class_entry *arg_ce;
+ int arg_is_instanceof;
+ zend_ssa_range tmp_range = {0, 0, 0, 0};
+ int tmp_has_range = -1;
+
+ if (op_array->fn_flags & ZEND_ACC_GENERATOR) {
+ ret->type = MAY_BE_OBJECT | MAY_BE_RC1 | MAY_BE_RCN;
+ ret->ce = zend_ce_generator;
+ ret->is_instanceof = 0;
+ ret->range = tmp_range;
+ ret->has_range = 0;
+ return;
+ }
+
+ for (j = 0; j < blocks_count; j++) {
+ if ((blocks[j].flags & ZEND_BB_REACHABLE) && blocks[j].len != 0) {
+ zend_op *opline = op_array->opcodes + blocks[j].start + blocks[j].len - 1;
+
+ if (opline->opcode == ZEND_RETURN || opline->opcode == ZEND_RETURN_BY_REF) {
+ zend_ssa_op *ssa_op = ssa->ops ? &ssa->ops[opline - op_array->opcodes] : NULL;
+ if (!recursive && ssa_op && info->ssa.var_info &&
+ ssa_op->op1_use >= 0 &&
+ info->ssa.var_info[ssa_op->op1_use].recursive) {
+ continue;
+ }
+ if (is_recursive_tail_call(op_array, opline)) {
+ continue;
+ }
+ t1 = OP1_INFO();
+ if (t1 & MAY_BE_UNDEF) {
+ t1 |= MAY_BE_NULL;
+ }
+ if (opline->opcode == ZEND_RETURN) {
+ if (t1 & MAY_BE_RC1) {
+ t1 |= MAY_BE_RCN;
+ }
+ t1 &= ~(MAY_BE_UNDEF | MAY_BE_REF);
+ } else {
+ t1 |= MAY_BE_REF;
+ t1 &= ~(MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN);
+ }
+ tmp |= t1;
+
+ if (ssa_op && info->ssa.var_info &&
+ ssa_op->op1_use >= 0 &&
+ info->ssa.var_info[ssa_op->op1_use].ce) {
+ arg_ce = info->ssa.var_info[ssa_op->op1_use].ce;
+ arg_is_instanceof = info->ssa.var_info[ssa_op->op1_use].is_instanceof;
+ } else {
+ arg_ce = NULL;
+ arg_is_instanceof = 0;
+ }
+
+ if (tmp_is_instanceof < 0) {
+ tmp_ce = arg_ce;
+ tmp_is_instanceof = arg_is_instanceof;
+ } else if (arg_ce && arg_ce == tmp_ce) {
+ if (tmp_is_instanceof != arg_is_instanceof) {
+ tmp_is_instanceof = 1;
+ }
+ } else {
+ tmp_ce = NULL;
+ tmp_is_instanceof = 0;
+ }
+
+ if (opline->op1_type == IS_CONST) {
+ zval *zv = CRT_CONSTANT(opline->op1);
+
+ if (Z_TYPE_P(zv) == IS_NULL) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = 0;
+ tmp_range.max = 0;
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, 0);
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, 0);
+ }
+ }
+ } else if (Z_TYPE_P(zv) == IS_FALSE) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = 0;
+ tmp_range.max = 0;
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, 0);
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, 0);
+ }
+ }
+ } else if (Z_TYPE_P(zv) == IS_TRUE) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = 1;
+ tmp_range.max = 1;
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, 1);
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, 1);
+ }
+ }
+ } else if (Z_TYPE_P(zv) == IS_LONG) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 0;
+ tmp_range.min = Z_LVAL_P(zv);
+ tmp_range.max = Z_LVAL_P(zv);
+ tmp_range.overflow = 0;
+ } else if (tmp_has_range) {
+ if (!tmp_range.underflow) {
+ tmp_range.min = MIN(tmp_range.min, Z_LVAL_P(zv));
+ }
+ if (!tmp_range.overflow) {
+ tmp_range.max = MAX(tmp_range.max, Z_LVAL_P(zv));
+ }
+ }
+ } else {
+ tmp_has_range = 0;
+ }
+ } else if (ssa_op && info->ssa.var_info && ssa_op->op1_use >= 0) {
+ if (info->ssa.var_info[ssa_op->op1_use].has_range) {
+ if (tmp_has_range < 0) {
+ tmp_has_range = 1;
+ tmp_range = info->ssa.var_info[ssa_op->op1_use].range;
+ } else if (tmp_has_range) {
+ /* union */
+ if (info->ssa.var_info[ssa_op->op1_use].range.underflow) {
+ tmp_range.underflow = 1;
+ tmp_range.min = ZEND_LONG_MIN;
+ } else {
+ tmp_range.min = MIN(tmp_range.min, info->ssa.var_info[ssa_op->op1_use].range.min);
+ }
+ if (info->ssa.var_info[ssa_op->op1_use].range.overflow) {
+ tmp_range.overflow = 1;
+ tmp_range.max = ZEND_LONG_MAX;
+ } else {
+ tmp_range.max = MAX(tmp_range.max, info->ssa.var_info[ssa_op->op1_use].range.max);
+ }
+ }
+ } else if (!widening) {
+ tmp_has_range = 1;
+ tmp_range.underflow = 1;
+ tmp_range.min = ZEND_LONG_MIN;
+ tmp_range.max = ZEND_LONG_MAX;
+ tmp_range.overflow = 1;
+ }
+ } else {
+ tmp_has_range = 0;
+ }
+ }
+ }
+ }
+
+ if (!(op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) {
+ if (tmp_is_instanceof < 0) {
+ tmp_is_instanceof = 0;
+ tmp_ce = NULL;
+ }
+ if (tmp_has_range < 0) {
+ tmp_has_range = 0;
+ }
+ ret->type = tmp;
+ ret->ce = tmp_ce;
+ ret->is_instanceof = tmp_is_instanceof;
+ }
+ ret->range = tmp_range;
+ ret->has_range = tmp_has_range;
+}
+
+static int zend_infer_types(const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level)
+{
+ zend_ssa_var_info *ssa_var_info = ssa->var_info;
+ int ssa_vars_count = ssa->vars_count;
+ int j;
+ zend_bitset worklist;
+ ALLOCA_FLAG(use_heap);
+
+ worklist = do_alloca(sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count), use_heap);
+ memset(worklist, 0, sizeof(zend_ulong) * zend_bitset_len(ssa_vars_count));
+
+ /* Type Inference */
+ for (j = op_array->last_var; j < ssa_vars_count; j++) {
+ zend_bitset_incl(worklist, j);
+ ssa_var_info[j].type = 0;
+ }
+
+ if (zend_infer_types_ex(op_array, script, ssa, worklist, optimization_level) != SUCCESS) {
+ free_alloca(worklist, use_heap);
+ return FAILURE;
+ }
+
+ /* Narrowing integer initialization to doubles */
+ zend_type_narrowing(op_array, script, ssa, optimization_level);
+
+ if (ZEND_FUNC_INFO(op_array)) {
+ zend_func_return_info(op_array, script, 1, 0, &ZEND_FUNC_INFO(op_array)->return_info);
+ }
+
+ free_alloca(worklist, use_heap);
+ return SUCCESS;
+}
+
+ZEND_API int zend_ssa_inference(zend_arena **arena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level) /* {{{ */
+{
+ zend_ssa_var_info *ssa_var_info;
+ int i;
+
+ if (!ssa->var_info) {
+ ssa->var_info = zend_arena_calloc(arena, ssa->vars_count, sizeof(zend_ssa_var_info));
+ }
+ ssa_var_info = ssa->var_info;
+
+ if (!op_array->function_name) {
+ for (i = 0; i < op_array->last_var; i++) {
+ ssa_var_info[i].type = MAY_BE_UNDEF | MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_REF | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY | MAY_BE_ARRAY_OF_REF;
+ ssa_var_info[i].has_range = 0;
+ }
+ } else {
+ for (i = 0; i < op_array->last_var; i++) {
+ ssa_var_info[i].type = MAY_BE_UNDEF;
+ ssa_var_info[i].has_range = 0;
+ if (ssa->vars[i].alias) {
+ ssa_var_info[i].type |= get_ssa_alias_types(ssa->vars[i].alias);
+ }
+ }
+ }
+ for (i = op_array->last_var; i < ssa->vars_count; i++) {
+ ssa_var_info[i].type = 0;
+ ssa_var_info[i].has_range = 0;
+ }
+
+ if (zend_infer_ranges(op_array, ssa) != SUCCESS) {
+ return FAILURE;
+ }
+
+ if (zend_infer_types(op_array, script, ssa, optimization_level) != SUCCESS) {
+ return FAILURE;
+ }
+
+ return SUCCESS;
+}
+/* }}} */
+
+void zend_inference_check_recursive_dependencies(zend_op_array *op_array)
+{
+ zend_func_info *info = ZEND_FUNC_INFO(op_array);
+ zend_call_info *call_info;
+ zend_bitset worklist;
+ int worklist_len, i;
+ ALLOCA_FLAG(use_heap);
+
+ if (!info->ssa.var_info || !(info->flags & ZEND_FUNC_RECURSIVE)) {
+ return;
+ }
+ worklist_len = zend_bitset_len(info->ssa.vars_count);
+ worklist = do_alloca(sizeof(zend_ulong) * worklist_len, use_heap);
+ memset(worklist, 0, sizeof(zend_ulong) * worklist_len);
+ call_info = info->callee_info;
+ while (call_info) {
+ if (call_info->recursive && call_info->caller_call_opline &&
+ info->ssa.ops[call_info->caller_call_opline - op_array->opcodes].result_def >= 0) {
+ zend_bitset_incl(worklist, info->ssa.ops[call_info->caller_call_opline - op_array->opcodes].result_def);
+ }
+ call_info = call_info->next_callee;
+ }
+ WHILE_WORKLIST(worklist, worklist_len, i) {
+ if (!info->ssa.var_info[i].recursive) {
+ info->ssa.var_info[i].recursive = 1;
+ add_usages(op_array, &info->ssa, worklist, i);
+ }
+ } WHILE_WORKLIST_END();
+ free_alloca(worklist, use_heap);
+}
+
+ZEND_API int zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa, uint32_t t1, uint32_t t2)
+{
+ if (opline->op1_type == IS_CV) {
+ if (t1 & MAY_BE_UNDEF) {
+ switch (opline->opcode) {
+ case ZEND_UNSET_VAR:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ return 1;
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ case ZEND_ISSET_ISEMPTY_PROP_OBJ:
+ case ZEND_ASSIGN:
+ case ZEND_ASSIGN_DIM:
+ case ZEND_ASSIGN_REF:
+ case ZEND_BIND_GLOBAL:
+ case ZEND_BIND_STATIC:
+ case ZEND_FETCH_DIM_IS:
+ case ZEND_FETCH_OBJ_IS:
+ case ZEND_SEND_REF:
+ case ZEND_UNSET_CV:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_MAKE_REF:
+ case ZEND_FETCH_DIM_W:
+ break;
+ default:
+ /* undefined variable warning */
+ return 1;
+ }
+ }
+ } else if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
+ if ((t1 & MAY_BE_RC1)
+ && (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
+ switch (opline->opcode) {
+ case ZEND_CASE:
+ case ZEND_CASE_STRICT:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ case ZEND_FETCH_LIST_R:
+ case ZEND_QM_ASSIGN:
+ case ZEND_SEND_VAL:
+ case ZEND_SEND_VAL_EX:
+ case ZEND_SEND_VAR:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_SEND_VAR_NO_REF:
+ case ZEND_SEND_VAR_NO_REF_EX:
+ case ZEND_SEND_REF:
+ case ZEND_SEPARATE:
+ case ZEND_END_SILENCE:
+ case ZEND_MAKE_REF:
+ break;
+ default:
+ /* destructor may be called */
+ return 1;
+ }
+ }
+ }
+
+ if (opline->op2_type == IS_CV) {
+ if (t2 & MAY_BE_UNDEF) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN_REF:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ break;
+ default:
+ /* undefined variable warning */
+ return 1;
+ }
+ }
+ } else if (opline->op2_type & (IS_TMP_VAR|IS_VAR)) {
+ if ((t2 & MAY_BE_RC1)
+ && (t2 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
+ switch (opline->opcode) {
+ case ZEND_ASSIGN:
+ case ZEND_FE_FETCH_R:
+ case ZEND_FE_FETCH_RW:
+ break;
+ default:
+ /* destructor may be called */
+ return 1;
+ }
+ }
+ }
+
+ switch (opline->opcode) {
+ case ZEND_NOP:
+ case ZEND_IS_IDENTICAL:
+ case ZEND_IS_NOT_IDENTICAL:
+ case ZEND_QM_ASSIGN:
+ case ZEND_JMP:
+ case ZEND_CHECK_VAR:
+ case ZEND_MAKE_REF:
+ case ZEND_BEGIN_SILENCE:
+ case ZEND_END_SILENCE:
+ case ZEND_FREE:
+ case ZEND_FE_FREE:
+ case ZEND_SEPARATE:
+ case ZEND_TYPE_CHECK:
+ case ZEND_DEFINED:
+ case ZEND_ISSET_ISEMPTY_THIS:
+ case ZEND_COALESCE:
+ case ZEND_SWITCH_LONG:
+ case ZEND_SWITCH_STRING:
+ case ZEND_MATCH:
+ case ZEND_ISSET_ISEMPTY_VAR:
+ case ZEND_ISSET_ISEMPTY_CV:
+ case ZEND_FUNC_NUM_ARGS:
+ case ZEND_FUNC_GET_ARGS:
+ case ZEND_COPY_TMP:
+ case ZEND_CASE_STRICT:
+ case ZEND_JMP_NULL:
+ return 0;
+ case ZEND_SEND_VAR:
+ case ZEND_SEND_VAL:
+ case ZEND_SEND_REF:
+ case ZEND_SEND_VAR_EX:
+ case ZEND_SEND_FUNC_ARG:
+ case ZEND_CHECK_FUNC_ARG:
+ /* May throw for named params. */
+ return opline->op2_type == IS_CONST;
+ case ZEND_INIT_FCALL:
+ /* can't throw, because call is resolved at compile time */
+ return 0;
+ case ZEND_BIND_GLOBAL:
+ if ((opline+1)->opcode == ZEND_BIND_GLOBAL) {
+ return zend_may_throw(opline + 1, ssa_op + 1, op_array, ssa);
+ }
+ return 0;
+ case ZEND_ADD:
+ if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
+ && (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
+ return 0;
+ }
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_DIV:
+ case ZEND_MOD:
+ if (!OP2_HAS_RANGE() ||
+ (OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
+ /* Division by zero */
+ return 1;
+ }
+ /* break missing intentionally */
+ case ZEND_SUB:
+ case ZEND_MUL:
+ case ZEND_POW:
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_SL:
+ case ZEND_SR:
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ !OP2_HAS_RANGE() ||
+ OP2_MIN_RANGE() < 0;
+ case ZEND_CONCAT:
+ case ZEND_FAST_CONCAT:
+ return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
+ (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ case ZEND_BW_OR:
+ case ZEND_BW_AND:
+ case ZEND_BW_XOR:
+ if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
+ && (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
+ return 0;
+ }
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_BW_NOT:
+ return (t1 & (MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_TRUE|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_PRE_INC:
+ case ZEND_POST_INC:
+ case ZEND_PRE_DEC:
+ case ZEND_POST_DEC:
+ return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_BOOL_NOT:
+ case ZEND_JMPZ:
+ case ZEND_JMPNZ:
+ case ZEND_JMPZNZ:
+ case ZEND_JMPZ_EX:
+ case ZEND_JMPNZ_EX:
+ case ZEND_BOOL:
+ case ZEND_JMP_SET:
+ return (t1 & MAY_BE_OBJECT);
+ case ZEND_BOOL_XOR:
+ return (t1 & MAY_BE_OBJECT) || (t2 & MAY_BE_OBJECT);
+ case ZEND_IS_EQUAL:
+ case ZEND_IS_NOT_EQUAL:
+ case ZEND_IS_SMALLER:
+ case ZEND_IS_SMALLER_OR_EQUAL:
+ case ZEND_CASE:
+ case ZEND_SPACESHIP:
+ if ((t1 & MAY_BE_ANY) == MAY_BE_NULL
+ || (t2 & MAY_BE_ANY) == MAY_BE_NULL) {
+ return 0;
+ }
+ return (t1 & (MAY_BE_OBJECT|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT)) || (t2 & (MAY_BE_OBJECT|MAY_BE_ARRAY_OF_ARRAY|MAY_BE_ARRAY_OF_OBJECT));
+ case ZEND_ASSIGN_OP:
+ if (opline->extended_value == ZEND_ADD) {
+ if ((t1 & MAY_BE_ANY) == MAY_BE_ARRAY
+ && (t2 & MAY_BE_ANY) == MAY_BE_ARRAY) {
+ return 0;
+ }
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ } else if (opline->extended_value == ZEND_DIV ||
+ opline->extended_value == ZEND_MOD) {
+ if (!OP2_HAS_RANGE() ||
+ (OP2_MIN_RANGE() <= 0 && OP2_MAX_RANGE() >= 0)) {
+ /* Division by zero */
+ return 1;
+ }
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ } else if (opline->extended_value == ZEND_SUB ||
+ opline->extended_value == ZEND_MUL ||
+ opline->extended_value == ZEND_POW) {
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ } else if (opline->extended_value == ZEND_SL ||
+ opline->extended_value == ZEND_SR) {
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ !OP2_HAS_RANGE() ||
+ OP2_MIN_RANGE() < 0;
+ } else if (opline->extended_value == ZEND_CONCAT) {
+ return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) ||
+ (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ } else if (opline->extended_value == ZEND_BW_OR ||
+ opline->extended_value == ZEND_BW_AND ||
+ opline->extended_value == ZEND_BW_XOR) {
+ if ((t1 & MAY_BE_ANY) == MAY_BE_STRING
+ && (t2 & MAY_BE_ANY) == MAY_BE_STRING) {
+ return 0;
+ }
+ return (t1 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE)) ||
+ (t2 & (MAY_BE_STRING|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ }
+ return 1;
+ case ZEND_ASSIGN:
+ if (t1 & MAY_BE_REF) {
+ return 1;
+ }
+ case ZEND_BIND_STATIC:
+ case ZEND_UNSET_VAR:
+ return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY));
+ case ZEND_ASSIGN_DIM:
+ if ((opline+1)->op1_type == IS_CV) {
+ if (_ssa_op1_info(op_array, ssa, opline+1, ssa_op+1) & MAY_BE_UNDEF) {
+ return 1;
+ }
+ }
+ return (t1 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_TRUE|MAY_BE_STRING|MAY_BE_LONG|MAY_BE_DOUBLE)) || opline->op2_type == IS_UNUSED ||
+ (t2 & (MAY_BE_UNDEF|MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_ASSIGN_OBJ:
+ if (t1 & (MAY_BE_ANY-(MAY_BE_NULL|MAY_BE_FALSE|MAY_BE_OBJECT))) {
+ return 1;
+ }
+ if (ssa_op->op1_use) {
+ zend_ssa_var_info *var_info = ssa->var_info + ssa_op->op1_use;
+ zend_class_entry *ce = var_info->ce;
+
+ if (var_info->is_instanceof ||
+ !ce || ce->create_object || ce->__get || ce->__set || ce->parent) {
+ return 1;
+ }
+
+ if (op_array->scope != ce && ce->default_properties_count) {
+ zend_property_info *prop_info;
+
+ if (opline->op2_type == IS_CONST) {
+ prop_info = zend_hash_find_ptr(&ce->properties_info,
+ Z_STR_P(CRT_CONSTANT(opline->op2)));
+ if (prop_info && !(prop_info->flags & ZEND_ACC_PUBLIC)) {
+ return 1;
+ }
+ } else {
+ if (t2 & (MAY_BE_ANY-MAY_BE_STRING)) {
+ return 1;
+ }
+ ZEND_HASH_FOREACH_PTR(&ce->properties_info, prop_info) {
+ if (!(prop_info->flags & ZEND_ACC_PUBLIC)) {
+ return 1;
+ }
+ } ZEND_HASH_FOREACH_END();
+ }
+ }
+ return 0;
+ }
+ return 1;
+ case ZEND_ROPE_INIT:
+ case ZEND_ROPE_ADD:
+ case ZEND_ROPE_END:
+ return t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT);
+ case ZEND_INIT_ARRAY:
+ case ZEND_ADD_ARRAY_ELEMENT:
+ return (opline->op2_type != IS_UNUSED) && (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_STRLEN:
+ return (t1 & MAY_BE_ANY) != MAY_BE_STRING;
+ case ZEND_COUNT:
+ return (t1 & MAY_BE_ANY) != MAY_BE_ARRAY;
+ case ZEND_RECV_INIT:
+ if (Z_TYPE_P(CRT_CONSTANT(opline->op2)) == IS_CONSTANT_AST) {
+ return 1;
+ }
+ if (op_array->fn_flags & ZEND_ACC_HAS_TYPE_HINTS) {
+ uint32_t arg_num = opline->op1.num;
+ zend_arg_info *cur_arg_info;
+
+ if (EXPECTED(arg_num <= op_array->num_args)) {
+ cur_arg_info = &op_array->arg_info[arg_num-1];
+ } else if (UNEXPECTED(op_array->fn_flags & ZEND_ACC_VARIADIC)) {
+ cur_arg_info = &op_array->arg_info[op_array->num_args];
+ } else {
+ return 0;
+ }
+ return ZEND_TYPE_IS_SET(cur_arg_info->type);
+ } else {
+ return 0;
+ }
+ case ZEND_FETCH_IS:
+ return (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ case ZEND_ISSET_ISEMPTY_DIM_OBJ:
+ return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ case ZEND_FETCH_DIM_IS:
+ return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE));
+ case ZEND_CAST:
+ switch (opline->extended_value) {
+ case IS_LONG:
+ case IS_DOUBLE:
+ return (t1 & MAY_BE_OBJECT);
+ case IS_STRING:
+ return (t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT));
+ case IS_ARRAY:
+ return (t1 & MAY_BE_OBJECT);
+ case IS_OBJECT:
+ return 0;
+ EMPTY_SWITCH_DEFAULT_CASE()
+ }
+ case ZEND_ARRAY_KEY_EXISTS:
+ if ((t2 & MAY_BE_ANY) != MAY_BE_ARRAY) {
+ return 1;
+ }
+ if ((t1 & (MAY_BE_ARRAY|MAY_BE_OBJECT|MAY_BE_RESOURCE))) {
+ return 1;
+ }
+ return 0;
+ case ZEND_FE_RESET_R:
+ case ZEND_FE_RESET_RW:
+ if ((t1 & (MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) {
+ return 1;
+ }
+ return 0;
+ case ZEND_FE_FETCH_R:
+ if ((t1 & (MAY_BE_ANY|MAY_BE_REF)) != MAY_BE_ARRAY) {
+ return 1;
+ }
+ if (opline->op2_type == IS_CV
+ && (t2 & MAY_BE_RC1)
+ && (t2 & (MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_ARRAY_OF_OBJECT|MAY_BE_ARRAY_OF_RESOURCE|MAY_BE_ARRAY_OF_ARRAY))) {
+ return 1;
+ }
+ return 0;
+ case ZEND_FETCH_DIM_W:
+ case ZEND_FETCH_LIST_W:
+ if (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_STRING|MAY_BE_OBJECT|MAY_BE_RESOURCE|MAY_BE_REF)) {
+ return 1;
+ }
+ if (t2 & (MAY_BE_RESOURCE|MAY_BE_ARRAY|MAY_BE_OBJECT)) {
+ return 1;
+ }
+ if (opline->op2_type == IS_UNUSED) {
+ return 1;
+ }
+ return 0;
+ default:
+ return 1;
+ }
+}
+
+ZEND_API int zend_may_throw(const zend_op *opline, const zend_ssa_op *ssa_op, const zend_op_array *op_array, zend_ssa *ssa)
+{
+ return zend_may_throw_ex(opline, ssa_op, op_array, ssa, OP1_INFO(), OP2_INFO());
+}