Skip to content
Open
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
14 changes: 14 additions & 0 deletions Zend/tests/return_types/025_2.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Return type of self is allowed in closure but $this return value must be checked as closure might not be bound to a class
--FILE--
<?php

$c = function(): self { return $this; };
try {
var_dump($c());
} catch (Throwable $e) {
echo $e::class, ': ', $e->getMessage(), PHP_EOL;
}
?>
--EXPECT--
Error: Using $this when not in object context
62 changes: 62 additions & 0 deletions Zend/zend_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -2642,6 +2642,58 @@ static void zend_compile_memoized_expr(znode *result, zend_ast *expr, uint32_t t
}
/* }}} */

static bool zend_is_this_instance_of_name(const zend_string *type_name)
{
if (zend_string_equals_ci(CG(active_class_entry)->name, type_name)) {
return true;
}
if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_SELF))) {
return true;
}
if (zend_string_equals_ci(type_name, ZSTR_KNOWN(ZEND_STR_PARENT))) {
return true;
}

ZEND_ASSERT((CG(active_class_entry)->ce_flags & ZEND_ACC_LINKED) == 0);
if (CG(active_class_entry)->num_interfaces) {
for (uint32_t i = 0; i < CG(active_class_entry)->num_interfaces; i++) {
if (zend_string_equals_ci(CG(active_class_entry)->interface_names[i].lc_name, type_name)) {
return true;
}
}
}
const zend_string *parent_name = CG(active_class_entry)->parent_name;
if (parent_name && zend_string_equals_ci(parent_name, type_name)) {
return true;
}

return false;
}

static bool zend_is_this_valid_for_return_type(zend_type type)
{
/* Closures can be bound to a class scope, however it might not and this must type error */
if (CG(active_op_array)->fn_flags & ZEND_ACC_CLOSURE) {
return false;
}

if (ZEND_TYPE_FULL_MASK(type) & (MAY_BE_OBJECT|MAY_BE_STATIC)) {
return true;
}

const zend_type *single_type;
ZEND_TYPE_FOREACH(type, single_type) {
if (ZEND_TYPE_HAS_NAME(*single_type)) {
const zend_string *name = ZEND_TYPE_NAME(*single_type);
if (zend_is_this_instance_of_name(name)) {
return true;
}
}
} ZEND_TYPE_FOREACH_END();

return false;
}

static void zend_emit_return_type_check(
znode *expr, const zend_arg_info *return_info, bool implicit) /* {{{ */
{
Expand Down Expand Up @@ -2697,6 +2749,16 @@ static void zend_emit_return_type_check(
return;
}

/* If return type contains static and we are returning $this
* (determined by checking if the previous opcode is ZEND_FETCH_THIS)
* then we don't need to check the return type */
const zend_op_array *op_array = CG(active_op_array);
if (expr && op_array->last >= 1
&& op_array->opcodes[op_array->last-1].opcode == ZEND_FETCH_THIS
&& zend_is_this_valid_for_return_type(type)) {
return;
}

opline = zend_emit_op(NULL, ZEND_VERIFY_RETURN_TYPE, expr, NULL);
if (expr && expr->op_type == IS_CONST) {
opline->result_type = expr->op_type = IS_TMP_VAR;
Expand Down
100 changes: 100 additions & 0 deletions ext/opcache/tests/opt/elide_self_verify_return_type_for_this.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
--TEST--
Return type check elision
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.opt_debug_level=0x20000
opcache.preload=
--EXTENSIONS--
opcache
--FILE--
<?php

class C1 {
public function foo(): self {
return $this;
}
}

class C2 {}
class C3 extends C2 {
public function foo(): C3 {
return $this;
}
}

interface I1 {}

class C4 implements I1 {
public function foo(): I1 {
return $this;
}
}

interface I2 extends I1 {}

class C5 implements I2 {
public function foo(): I1 {
return $this;
}
}

class C6 extends C5 {
public function foo(): I2 {
return $this;
}
}

?>
--EXPECTF--
$_main:
; (lines=5, args=0, vars=0, tmps=0)
; (after optimizer)
; %s:1-39
0000 DECLARE_CLASS string("c4")
0001 DECLARE_CLASS string("i2")
0002 DECLARE_CLASS string("c5")
0003 DECLARE_CLASS_DELAYED string("c6") string("c5")
0004 RETURN int(1)

C1::foo:
; (lines=2, args=0, vars=0, tmps=1)
; (after optimizer)
; %s:4-6
0000 T0 = FETCH_THIS
0001 RETURN T0

C3::foo:
; (lines=2, args=0, vars=0, tmps=1)
; (after optimizer)
; %s:11-13
0000 T0 = FETCH_THIS
0001 RETURN T0

C4::foo:
; (lines=2, args=0, vars=0, tmps=1)
; (after optimizer)
; %s:19-21
0000 T0 = FETCH_THIS
0001 RETURN T0

C5::foo:
; (lines=3, args=0, vars=0, tmps=1)
; (after optimizer)
; %s:27-29
0000 T0 = FETCH_THIS
0001 VERIFY_RETURN_TYPE T0
0002 RETURN T0
LIVE RANGES:
0: 0001 - 0002 (tmp/var)

C6::foo:
; (lines=3, args=0, vars=0, tmps=1)
; (after optimizer)
; %s:33-35
0000 T0 = FETCH_THIS
0001 VERIFY_RETURN_TYPE T0
0002 RETURN T0
LIVE RANGES:
0: 0001 - 0002 (tmp/var)
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
Return type check elision
--INI--
opcache.enable=1
opcache.enable_cli=1
opcache.optimization_level=-1
opcache.opt_debug_level=0x20000
opcache.preload=
--EXTENSIONS--
opcache
--FILE--
<?php

class C {
public function returnStatic(): static {
return $this;
}
}

?>
--EXPECTF--
$_main:
; (lines=1, args=0, vars=0, tmps=0)
; (after optimizer)
; %s:1-10
0000 RETURN int(1)

C::returnStatic:
; (lines=2, args=0, vars=0, tmps=1)
; (after optimizer)
; %s:4-6
0000 T0 = FETCH_THIS
0001 RETURN T0
Loading