Skip to content

Commit 6d37564

Browse files
committed
Add #[\NoDiscard] attribute
1 parent 80dcbc6 commit 6d37564

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1089
-22
lines changed

Zend/Optimizer/optimize_func_calls.c

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli
7878

7979
static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func)
8080
{
81+
const uint32_t no_discard = RETURN_VALUE_USED(opline) ? 0 : ZEND_ACC_NODISCARD;
82+
8183
if (func->type == ZEND_USER_FUNCTION
82-
&& !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_DEPRECATED))
84+
&& !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_DEPRECATED|no_discard))
8385
/* TODO: function copied from trait may be inconsistent ??? */
8486
&& !(func->op_array.fn_flags & (ZEND_ACC_TRAIT_CLONE))
8587
&& fcall->extended_value >= func->op_array.required_num_args
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
--TEST--
2+
#[\NoDiscard]: Basic test.
3+
--FILE--
4+
<?php
5+
6+
#[\NoDiscard]
7+
function test(): int {
8+
return 0;
9+
}
10+
11+
#[\NoDiscard("this is important")]
12+
function test2(): int {
13+
return 0;
14+
}
15+
16+
#[\NoDiscard]
17+
function test3(...$args): int {
18+
return 0;
19+
}
20+
21+
class Clazz {
22+
#[\NoDiscard]
23+
function test(): int {
24+
return 0;
25+
}
26+
27+
#[\NoDiscard("this is important")]
28+
function test2(): int {
29+
return 0;
30+
}
31+
32+
#[\NoDiscard]
33+
static function test3(): int {
34+
return 0;
35+
}
36+
}
37+
38+
$closure = #[\NoDiscard] function(): int {
39+
return 0;
40+
};
41+
42+
$closure2 = #[\NoDiscard] function(): int {
43+
return 0;
44+
};
45+
46+
test();
47+
test2();
48+
test3(1, 2, named: 3);
49+
call_user_func("test");
50+
$fcc = test(...);
51+
$fcc();
52+
53+
$cls = new Clazz();
54+
$cls->test();
55+
$cls->test2();
56+
Clazz::test3();
57+
58+
call_user_func([$cls, "test"]);
59+
60+
$closure();
61+
62+
$closure2();
63+
64+
?>
65+
--EXPECTF--
66+
Warning: (F)The return value of function test() is expected to be consumed in %s on line %d
67+
68+
Warning: (F)The return value of function test2() is expected to be consumed, this is important in %s on line %d
69+
70+
Warning: (E)The return value of function test3() is expected to be consumed in %s on line %d
71+
72+
Warning: (F)The return value of function test() is expected to be consumed in %s on line %d
73+
74+
Warning: (F)The return value of function test() is expected to be consumed in %s on line %d
75+
76+
Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d
77+
78+
Warning: (F)The return value of method Clazz::test2() is expected to be consumed, this is important in %s on line %d
79+
80+
Warning: (F)The return value of method Clazz::test3() is expected to be consumed in %s on line %d
81+
82+
Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d
83+
84+
Warning: (F)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d
85+
86+
Warning: (F)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
--TEST--
2+
#[\NoDiscard]: __call(), __callStatic(), and __invoke().
3+
--FILE--
4+
<?php
5+
6+
class Clazz {
7+
#[\NoDiscard]
8+
public function __call(string $name, array $args): int {
9+
echo "__call({$name})", PHP_EOL;
10+
11+
return strlen($name);
12+
}
13+
14+
#[\NoDiscard]
15+
public static function __callStatic(string $name, array $args): int {
16+
echo "__callStatic({$name})", PHP_EOL;
17+
18+
return strlen($name);
19+
}
20+
21+
#[\NoDiscard]
22+
public function __invoke(string $param): int {
23+
echo "__invoke({$param})", PHP_EOL;
24+
25+
return strlen($param);
26+
}
27+
}
28+
29+
$cls = new Clazz();
30+
$cls->test();
31+
Clazz::test();
32+
$cls('foo');
33+
34+
?>
35+
--EXPECTF--
36+
__call(test)
37+
38+
Warning: (F)The return value of method Clazz::__call() is expected to be consumed in %s on line %d
39+
__callStatic(test)
40+
41+
Warning: (F)The return value of method Clazz::__callStatic() is expected to be consumed in %s on line %d
42+
__invoke(foo)
43+
44+
Warning: (F)The return value of method Clazz::__invoke() is expected to be consumed in %s on line %d
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
#[\NoDiscard]: Taken from trait.
3+
--FILE--
4+
<?php
5+
6+
trait T {
7+
#[\NoDiscard]
8+
function test(): int {
9+
return 0;
10+
}
11+
}
12+
13+
class Clazz {
14+
use T;
15+
}
16+
17+
$cls = new Clazz();
18+
$cls->test();
19+
20+
?>
21+
--EXPECTF--
22+
Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
--TEST--
2+
#[\NoDiscard]: Native function and method.
3+
--FILE--
4+
<?php
5+
6+
$f = tmpfile();
7+
flock($f, LOCK_SH | LOCK_NB);
8+
fclose($f);
9+
10+
$date = new DateTimeImmutable('now');
11+
$date->setTimestamp(0);
12+
13+
?>
14+
--EXPECTF--
15+
Warning: (C)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d
16+
17+
Warning: (A)The return value of method DateTimeImmutable::setTimestamp() is expected to be consumed, as DateTimeImmutable::setTimestamp() does not modify the object itself in %s on line %d
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
--TEST--
2+
#[\NoDiscard]: execute_ex overwritten
3+
--EXTENSIONS--
4+
zend_test
5+
--INI--
6+
zend_test.replace_zend_execute_ex=1
7+
opcache.jit=disable
8+
--FILE--
9+
<?php
10+
11+
#[\NoDiscard]
12+
function test(): int {
13+
return 0;
14+
}
15+
16+
test();
17+
18+
?>
19+
--EXPECTF--
20+
Warning: (G)The return value of function test() is expected to be consumed in %s on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
#[\NoDiscard]: execute_internal overwritten
3+
--EXTENSIONS--
4+
zend_test
5+
--INI--
6+
zend_test.observer.execute_internal=1
7+
--FILE--
8+
<?php
9+
10+
$f = tmpfile();
11+
flock($f, LOCK_SH | LOCK_NB);
12+
fclose($f);
13+
14+
?>
15+
--EXPECTF--
16+
<!-- internal enter tmpfile() -->
17+
<!-- internal enter flock() -->
18+
<!-- internal enter NoDiscard::__construct() -->
19+
20+
Warning: (A)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d
21+
<!-- internal enter fclose() -->
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
--TEST--
2+
#[\NoDiscard]: Combining with #[\Deprecated].
3+
--FILE--
4+
<?php
5+
6+
#[\NoDiscard]
7+
#[\Deprecated]
8+
function test(): int {
9+
return 0;
10+
}
11+
12+
test();
13+
14+
?>
15+
--EXPECTF--
16+
Deprecated: Function test() is deprecated in %s on line %d
17+
18+
Warning: (F)The return value of function test() is expected to be consumed in %s on line %d
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
#[\NoDiscard]: Combining with #[\Deprecated] (Internal).
3+
--EXTENSIONS--
4+
zend_test
5+
--FILE--
6+
<?php
7+
8+
zend_test_deprecated_nodiscard();
9+
10+
?>
11+
--EXPECTF--
12+
Deprecated: Function zend_test_deprecated_nodiscard() is deprecated, custom message in %s on line %d
13+
14+
Warning: (B)The return value of function zend_test_deprecated_nodiscard() is expected to be consumed, custom message 2 in %s on line %d
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
--TEST--
2+
#[\NoDiscard]: Code is E_USER_WARNING.
3+
--FILE--
4+
<?php
5+
6+
set_error_handler(function (int $errno, string $errstr, ?string $errfile = null, ?int $errline = null) {
7+
var_dump($errno, E_USER_WARNING, $errno === E_USER_WARNING);
8+
});
9+
10+
#[\NoDiscard]
11+
function test(): int {
12+
return 0;
13+
}
14+
15+
test();
16+
17+
?>
18+
--EXPECT--
19+
int(512)
20+
int(512)
21+
bool(true)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
--TEST--
2+
#[\NoDiscard]: NoDiscard::$message is readonly.
3+
--FILE--
4+
<?php
5+
6+
$d = new \NoDiscard("foo");
7+
$d->message = 'bar';
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Uncaught Error: Cannot modify readonly property NoDiscard::$message in %s:%d
12+
Stack trace:
13+
#0 {main}
14+
thrown in %s on line %d
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
--TEST--
2+
#[\NoDiscard]: __construct() respects that properties are readonly.
3+
--FILE--
4+
<?php
5+
6+
$d = new \NoDiscard("foo");
7+
$d->__construct("bar");
8+
9+
?>
10+
--EXPECTF--
11+
Fatal error: Uncaught Error: Cannot modify readonly property NoDiscard::$message in %s:%d
12+
Stack trace:
13+
#0 %s(%d): NoDiscard->__construct('bar')
14+
#1 {main}
15+
thrown in %s on line %d
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
--TEST--
2+
#[\NoDiscard]: Assigning to variable suppresses.
3+
--FILE--
4+
<?php
5+
6+
#[\NoDiscard]
7+
function test(): int {
8+
return 0;
9+
}
10+
11+
#[\NoDiscard("this is important")]
12+
function test2(): int {
13+
return 0;
14+
}
15+
16+
#[\NoDiscard]
17+
function test3(...$args): int {
18+
return 0;
19+
}
20+
21+
class Clazz {
22+
#[\NoDiscard]
23+
function test(): int {
24+
return 0;
25+
}
26+
27+
#[\NoDiscard("this is important")]
28+
function test2(): int {
29+
return 0;
30+
}
31+
32+
#[\NoDiscard]
33+
static function test3(): int {
34+
return 0;
35+
}
36+
}
37+
38+
$closure = #[\NoDiscard] function(): int {
39+
return 0;
40+
};
41+
42+
$closure2 = #[\NoDiscard] function(): int {
43+
return 0;
44+
};
45+
46+
$_ = test();
47+
$_ = test2();
48+
$_ = test3(1, 2, named: 3);
49+
$_ = call_user_func("test");
50+
$fcc = test(...);
51+
$_ = $fcc();
52+
53+
$cls = new Clazz();
54+
$_ = $cls->test();
55+
$_ = $cls->test2();
56+
$_ = call_user_func([$cls, "test"]);
57+
$_ = Clazz::test3();
58+
59+
$_ = $closure();
60+
61+
$_ = $closure2();
62+
63+
?>
64+
DONE
65+
--EXPECT--
66+
DONE

0 commit comments

Comments
 (0)