Skip to content
Draft
Show file tree
Hide file tree
Changes from 4 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
24 changes: 24 additions & 0 deletions Zend/Optimizer/zend_inference.c
Original file line number Diff line number Diff line change
Expand Up @@ -4042,6 +4042,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 +5056,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
2 changes: 2 additions & 0 deletions 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
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->getMessage(), "\n";
}

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

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

?>
--EXPECT--
Cannot access "self" when no class scope is active
Cannot access "parent" when no class scope is active
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)
28 changes: 28 additions & 0 deletions Zend/tests/pattern_matching/is/binding_destruct.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--TEST--
Object pattern matching destructor
--FILE--
<?php

class Foo {
public function __destruct() {
throw new Exception('Here');
}
}

$foo = new Foo();
$bar = 'bar';

try {
42 is $foo & $bar;
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}

var_dump($foo);
var_dump($bar);

?>
--EXPECT--
Here
int(42)
string(3) "bar"
8 changes: 8 additions & 0 deletions Zend/tests/pattern_matching/is/binding_in_or_001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Must not bind to variables in | pattern
--FILE--
<?php
var_dump(42 is $foo|$bar);
?>
--EXPECTF--
Fatal error: Must not bind to variables inside | pattern in %s on line %d
8 changes: 8 additions & 0 deletions Zend/tests/pattern_matching/is/binding_in_or_002.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
--TEST--
Must not bind to variables in | pattern
--FILE--
<?php
var_dump(42 is 42|(43 & $bar));
?>
--EXPECTF--
Fatal error: Must not bind to variables inside | pattern in %s on line %d
15 changes: 15 additions & 0 deletions Zend/tests/pattern_matching/is/bug001.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Pattern matching: Bug 001
--FILE--
<?php

function test($a) {
$a is 42|43;
}

test(42);

?>
===DONE===
--EXPECT--
===DONE===
Loading
Loading