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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Zend/Optimizer/block_pass.c
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,13 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array
}
}
break;

case ZEND_HAS_TYPE:
if (opline->op1_type & (IS_TMP_VAR|IS_VAR)) {
/* Variable will be deleted later by FREE, so we can't optimize it */
Tsource[VAR_NUM(opline->op1.var)] = NULL;
}
break;
}

/* get variable source */
Expand Down
6 changes: 5 additions & 1 deletion Zend/Optimizer/compact_literals.c
Original file line number Diff line number Diff line change
Expand Up @@ -276,7 +276,11 @@ void zend_optimizer_compact_literals(zend_op_array *op_array, zend_optimizer_ctx
for (uint32_t i = 0; i < op_array->last_literal; i++) {
if (!info[i].num_related) {
/* unset literal */
zval_ptr_dtor_nogc(&op_array->literals[i]);
if (Z_TYPE(op_array->literals[i]) == IS_TYPE) {
zend_type_release(*(zend_type*)Z_PTR_P(&op_array->literals[i]), false);
} else {
zval_ptr_dtor_nogc(&op_array->literals[i]);
}
continue;
}
switch (Z_TYPE(op_array->literals[i])) {
Expand Down
1 change: 1 addition & 0 deletions Zend/Optimizer/dce.c
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ static inline bool may_have_side_effects(
case ZEND_FUNC_GET_ARGS:
case ZEND_ARRAY_KEY_EXISTS:
case ZEND_COPY_TMP:
case ZEND_HAS_TYPE:
/* No side effects */
return false;
case ZEND_FREE:
Expand Down
25 changes: 25 additions & 0 deletions Zend/Optimizer/sccp.c
Original file line number Diff line number Diff line change
Expand Up @@ -1235,6 +1235,31 @@ static void sccp_visit_instr(scdf_ctx *scdf, zend_op *opline, zend_ssa_op *ssa_o
ssa_op++;
SET_RESULT_BOT(op1);
break;
case ZEND_HAS_TYPE: {
zend_ssa *ssa = ctx->scdf.ssa;
zend_ssa_var_info *info = &ssa->var_info[ssa_op->result_def];
if (info->type == MAY_BE_TRUE) {
ZVAL_TRUE(&zv);
SET_RESULT(result, &zv);
return;
} else if (info->type == MAY_BE_FALSE) {
ZVAL_FALSE(&zv);
SET_RESULT(result, &zv);
return;
}

if (!IS_BOT(op1)) {
SKIP_IF_TOP(op1);
zend_type *type = Z_PTR_P(op2);
// FIXME: Abusing internal/return type flags to achieve strict type check
ZVAL_BOOL(&zv, zend_check_type_ex(type, op1, NULL, true, true));
SET_RESULT(result, &zv);
return;
}

SET_RESULT_BOT(result);
return;
}
}

if ((op1 && IS_BOT(op1)) || (op2 && IS_BOT(op2))) {
Expand Down
7 changes: 7 additions & 0 deletions Zend/Optimizer/zend_dump.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,13 @@ void zend_dump_const(const zval *zv)
case IS_ARRAY:
fprintf(stderr, " array(...)");
break;
case IS_TYPE: {
zend_type *type = Z_PTR_P(zv);
zend_string *type_str = zend_type_to_string(*type);
fprintf(stderr, " type(%s)", ZSTR_VAL(type_str));
zend_string_release(type_str);
break;
}
default:
fprintf(stderr, " zval(type=%d)", Z_TYPE_P(zv));
break;
Expand Down
35 changes: 30 additions & 5 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -2130,7 +2130,7 @@ ZEND_API uint32_t ZEND_FASTCALL zend_array_type_info(const zval *zv)
}


ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, bool write, bool insert)
ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, bool write, bool insert, bool is)
{
uint32_t tmp = 0;

Expand All @@ -2149,7 +2149,7 @@ ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, bool wri
if (insert) {
tmp |= MAY_BE_NULL;
} else {
tmp |= MAY_BE_NULL | ((t1 & MAY_BE_ARRAY_OF_ANY) >> MAY_BE_ARRAY_SHIFT);
tmp |= (is ? MAY_BE_UNDEF : 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;
}
Expand Down Expand Up @@ -2760,7 +2760,7 @@ static zend_always_inline zend_result _zend_update_type_info(
tmp |= MAY_BE_REF;
}
orig = t1;
t1 = zend_array_element_type(t1, opline->op1_type, 1, 0);
t1 = zend_array_element_type(t1, opline->op1_type, 1, 0, false);
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);
Expand Down Expand Up @@ -3741,12 +3741,13 @@ static zend_always_inline zend_result _zend_update_type_info(
opline->op1_type,
opline->opcode != ZEND_FETCH_DIM_R && opline->opcode != ZEND_FETCH_DIM_IS
&& opline->opcode != ZEND_FETCH_LIST_R,
opline->op2_type == IS_UNUSED);
opline->op2_type == IS_UNUSED,
opline->opcode == ZEND_FETCH_DIM_IS);
if (opline->opcode == ZEND_FETCH_DIM_FUNC_ARG && (t1 & (MAY_BE_TRUE|MAY_BE_LONG|MAY_BE_DOUBLE|MAY_BE_RESOURCE))) {
tmp |= MAY_BE_NULL;
}
if (opline->opcode == ZEND_FETCH_DIM_IS && (t1 & MAY_BE_STRING)) {
tmp |= MAY_BE_NULL;
tmp |= MAY_BE_UNDEF;
}
if ((tmp & (MAY_BE_RC1|MAY_BE_RCN)) == MAY_BE_RCN && opline->result_type == IS_TMP_VAR) {
/* refcount may be indirectly decremented. Make an exception if the result is used in the next instruction */
Expand Down Expand Up @@ -4042,6 +4043,29 @@ static zend_always_inline zend_result _zend_update_type_info(
case ZEND_FETCH_GLOBALS:
UPDATE_SSA_TYPE(MAY_BE_ARRAY|MAY_BE_ARRAY_KEY_ANY|MAY_BE_ARRAY_OF_ANY|MAY_BE_ARRAY_OF_REF|MAY_BE_RC1|MAY_BE_RCN, ssa_op->result_def);
break;
case ZEND_HAS_TYPE: {
t1 &= MAY_BE_ANY;
if (t1 & MAY_BE_UNDEF) {
t1 |= MAY_BE_NULL;
}

zend_type *type = Z_PTR_P(CRT_CONSTANT(opline->op2));
uint32_t expected = ZEND_TYPE_PURE_MASK(*type);
if (ZEND_TYPE_HAS_NAME(*type)) {
// FIXME: Implement
UPDATE_SSA_TYPE(MAY_BE_BOOL, ssa_op->result_def);
break;
}

if (!(t1 & ~expected)) {
UPDATE_SSA_TYPE(MAY_BE_TRUE, ssa_op->result_def);
} else if (!(expected & ~t1)) {
UPDATE_SSA_TYPE(MAY_BE_FALSE, ssa_op->result_def);
} else {
UPDATE_SSA_TYPE(MAY_BE_BOOL, ssa_op->result_def);
}
break;
}
default:
#ifdef ZEND_DEBUG_TYPE_INFERENCE
if (ssa_op->result_def >= 0) {
Expand Down Expand Up @@ -5033,6 +5057,7 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op
case ZEND_COPY_TMP:
case ZEND_JMP_NULL:
case ZEND_JMP_FRAMELESS:
case ZEND_HAS_TYPE:
return 0;
case ZEND_IS_IDENTICAL:
case ZEND_IS_NOT_IDENTICAL:
Expand Down
4 changes: 3 additions & 1 deletion Zend/Optimizer/zend_inference.h
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ static zend_always_inline uint32_t _const_op_type(const zval *zv) {
return MAY_BE_RC1 | MAY_BE_RCN | MAY_BE_ANY | MAY_BE_ARRAY_KEY_ANY | MAY_BE_ARRAY_OF_ANY;
} else if (Z_TYPE_P(zv) == IS_ARRAY) {
return zend_array_type_info(zv);
} else if (Z_TYPE_P(zv) == IS_TYPE) {
return MAY_BE_ANY;
} else {
uint32_t tmp = (1 << Z_TYPE_P(zv));

Expand Down Expand Up @@ -221,7 +223,7 @@ ZEND_API void zend_ssa_find_false_dependencies(const zend_op_array *op_array, co
ZEND_API void zend_ssa_find_sccs(const zend_op_array *op_array, zend_ssa *ssa);
ZEND_API zend_result zend_ssa_inference(zend_arena **raena, const zend_op_array *op_array, const zend_script *script, zend_ssa *ssa, zend_long optimization_level);

ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, bool write, bool insert);
ZEND_API uint32_t zend_array_element_type(uint32_t t1, uint8_t op_type, bool write, bool insert, bool is);

ZEND_API bool zend_inference_propagate_range(const zend_op_array *op_array, const zend_ssa *ssa, const zend_op *opline, const zend_ssa_op* ssa_op, int var, zend_ssa_range *tmp);

Expand Down
29 changes: 29 additions & 0 deletions Zend/tests/pattern_matching/is/and.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
And pattern
--FILE--
<?php

interface A {}
interface B {}
interface C {}
class Foo implements A, B {}

var_dump(1 is int & 1);
var_dump(2 is int & (1|2));
var_dump(3 is float & 1);
var_dump(4 is int & float);
var_dump([] is [] & [...]);
var_dump('foo' is string & 'bar');
var_dump(new Foo() is A&B);
var_dump(new Foo() is (A&C));

?>
--EXPECT--
bool(true)
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
bool(true)
bool(false)
30 changes: 30 additions & 0 deletions Zend/tests/pattern_matching/is/array.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
--TEST--
Array pattern
--FILE--
<?php

var_dump([] is []);
var_dump(42 is []);
var_dump('foo' is []);
var_dump([42] is [42]);
var_dump([42] is []);
var_dump([42] is [43]);
var_dump([42] is ['0' => 42]);
var_dump([42, 43] is [42]);
var_dump([42, 43] is [42, ...]);
var_dump([42] is [$a]);
var_dump($a);

?>
--EXPECT--
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
bool(false)
bool(true)
bool(false)
bool(true)
bool(true)
int(42)
10 changes: 10 additions & 0 deletions Zend/tests/pattern_matching/is/array_mixed_keys.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
--TEST--
Array pattern with mixed implicit and explicit keys
--FILE--
<?php

var_dump([] is ['foo', 1 => 'bar']);

?>
--EXPECTF--
Fatal error: Must not mix implicit and explicit array keys in array pattern in %s on line %d
32 changes: 32 additions & 0 deletions Zend/tests/pattern_matching/is/bail.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
--TEST--
Object pattern matching
--FILE--
<?php

(function () {
$o = new stdClass();

try {
var_dump($o is self);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
var_dump($o is parent);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}

try {
var_dump($o is static);
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), "\n";
}
})();

?>
--EXPECT--
Error: Cannot access "self" when no class scope is active
Error: Cannot access "parent" when no class scope is active
Error: Cannot access "static" when no class scope is active
87 changes: 87 additions & 0 deletions Zend/tests/pattern_matching/is/binding.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
--TEST--
Binding pattern
--FILE--
<?php

class Box {
public function __construct(
public $value,
) {}
}

class NotBox {
public function __construct(
public $value,
) {}
}

class Many {
public function __construct(
public $a = 1,
public $b = 2,
public $c = 3,
public $d = 4,
public $e = 5,
public $f = 6,
public $g = 7,
public $h = 8,
public $i = 9,
public $j = 10,
) {}
}

var_dump(10 is $a);
var_dump($a);

var_dump('Hello world' is $a);
var_dump($a);

var_dump(new Box(42) is Box(value: $a));
var_dump($a);

var_dump(new NotBox(43) is Box(value: $a));
var_dump($a);

var_dump(43 is $a & int);
var_dump($a);

var_dump([] is $a & string);
var_dump($a);

var_dump(new Many() is Many(:$a, :$b, :$c, :$d));
var_dump($a, $b, $c, $d, isset($e));

var_dump(new Many() is Many(:$a, :$b, :$c, :$d, :$e, :$f, :$g, :$h, :$i, :$j));
var_dump($a, $b, $c, $d, $e, $f, $g, $h, $i, $j);

?>
--EXPECT--
bool(true)
int(10)
bool(true)
string(11) "Hello world"
bool(true)
int(42)
bool(false)
int(42)
bool(true)
int(43)
bool(false)
int(43)
bool(true)
int(1)
int(2)
int(3)
int(4)
bool(false)
bool(true)
int(1)
int(2)
int(3)
int(4)
int(5)
int(6)
int(7)
int(8)
int(9)
int(10)
Loading
Loading