Skip to content

[POC] Support calling functions from constant expressions #5139

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
12 changes: 12 additions & 0 deletions Zend/tests/call_in_const/backticks_forbidden.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Cannot use backtick shorthand for shell_exec in constant expressions
--FILE--
<?php
// Make sure that this is not accidentally allowed by future changes to PHP.
// During compilation, this gets converted to a regular function call opcode.
class Example {
const TEST_CONST = [`echo test`];
}
?>
--EXPECTF--
Fatal error: Constant expression contains invalid operations in %s on line 5
15 changes: 15 additions & 0 deletions Zend/tests/call_in_const/class_const01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Can call internal functions from class constants
--FILE--
<?php
class Example {
const X = cos(0);

public static function main() {
echo "X is " . self::X . "\n";
}
}
Example::main();
?>
--EXPECT--
X is 1
27 changes: 27 additions & 0 deletions Zend/tests/call_in_const/class_const02.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
--TEST--
Can call internal functions from class constants
--FILE--
<?php
class Example {
const X = [
'key1' => 'value1',
'key2' => 'value2',
];
const Y = array_flip(self::X);
const Z = array_flip(self::Y);
}
var_export(Example::Z);
var_export(Example::Z);
var_export(Example::Y);
?>
--EXPECT--
array (
'key1' => 'value1',
'key2' => 'value2',
)array (
'key1' => 'value1',
'key2' => 'value2',
)array (
'value1' => 'key1',
'value2' => 'key2',
)
24 changes: 24 additions & 0 deletions Zend/tests/call_in_const/class_const03.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
Cannot call functions accessing the variable scope in class constants
--FILE--
<?php
class Example {
const X = func_get_args();

public static function main($value) {
try {
var_export(self::X);
} catch (Error $e) {
printf("Caught %s on line %d\n", $e->getMessage(), $e->getLine());
}
try {
var_export(self::X);
} catch (Error $e) {
printf("Caught %s on line %d\n", $e->getMessage(), $e->getLine());
}
}
}
Example::main('test');
?>
--EXPECTF--
Fatal error: Constant expression uses function func_get_args() which is not in get_const_expr_functions() in %s on line 3
29 changes: 29 additions & 0 deletions Zend/tests/call_in_const/class_const04.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
--TEST--
Handling call_user_func and static in class constants.
--FILE--
<?php
class MyClass {
// There's only one constant declaration that gets shared by all of the classes.
// So act as though the class scope is MyClass no matter where it gets called.
const X = call_user_func('static::example');

public static function example() {
return static::class;
}
public static function main() {
echo static::X . "\n";
}
}
class SubClass extends MyClass {
public static function example() {
return "Other\n";
}
}
// This wouldn't work without further updates to the implementation.
echo MyClass::X;
SubClass::main();
MyClass::main();

?>
--EXPECTF--
Fatal error: Constant expression uses function call_user_func() which is not in get_const_expr_functions() in %s on line 5
11 changes: 11 additions & 0 deletions Zend/tests/call_in_const/global_const01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Can call internal functions from global constants
--FILE--
<?php
const VALUES = RANGE(1, 100);
const SUM = array_sum(VALUES);

echo "SUM is " . SUM . "\n";
?>
--EXPECT--
SUM is 5050
11 changes: 11 additions & 0 deletions Zend/tests/call_in_const/global_const02.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Cannot declare constants with function calls that contain objects
--FILE--
<?php
function make_object_array() {
return [new stdClass()];
}
const OBJECT_VALUES = make_object_array();
?>
--EXPECTF--
Fatal error: Constant expression uses function make_object_array() which is not in get_const_expr_functions() in %s on line 5
19 changes: 19 additions & 0 deletions Zend/tests/call_in_const/methods_forbidden.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Cannot call methods in constant expressions
--FILE--
<?php
// Currently, php will evaluate all of the instance property defaults at once and cache them
// the first time a class gets instantiated, in _object_and_properties_init.
//
// Authors of future RFCs may wish to change PHP
// to allow `count()` or `generate_unique_id()` or `public $fields = new stdClass();`
// as the defaults of instance properties.
class MyClass {
public static function example() {
return 'value';
}
const X = 'MyClass::example'();
}
?>
--EXPECTF--
Fatal error: Constant expression contains invalid name for function call in %s on line 12
17 changes: 17 additions & 0 deletions Zend/tests/call_in_const/param_defaults01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Can call internal functions from parameter default
--FILE--
<?php
namespace NS;
use function max;
function test_default($x = max(1, 3, 2)) {
echo "x is $x\n";
}
test_default();
test_default(2);
test_default();
?>
--EXPECT--
x is 3
x is 2
x is 3
26 changes: 26 additions & 0 deletions Zend/tests/call_in_const/param_defaults02.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
Cannot access variable scope in parameter defaults
--FILE--
<?php

namespace NS;

use Error;

function test_default($x = func_get_args()) {
var_dump($x);
}
try {
test_default();
} catch (Error $e) {
printf("Caught %s on line %d\n", $e->getMessage(), $e->getLine());
}
test_default('overriding the default');
try {
test_default();
} catch (Error $e) {
printf("Caught %s on line %d\n", $e->getMessage(), $e->getLine());
}
?>
--EXPECTF--
Fatal error: Constant expression uses function NS\func_get_args() which is not in get_const_expr_functions() in %s on line 7
20 changes: 20 additions & 0 deletions Zend/tests/call_in_const/param_defaults03.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
User-defined functions are evaluated every time in param defaults (not callable)
--FILE--
<?php

function generate_new_id() : int {
static $id = 100;
++$id;
return $id;
}

function test_id(int $id = generate_new_id()) {
echo "id is $id\n";
}
test_id();
test_id(-1);
test_id();
?>
--EXPECTF--
Fatal error: Constant expression uses function generate_new_id() which is not in get_const_expr_functions() in %s on line 9
14 changes: 14 additions & 0 deletions Zend/tests/call_in_const/param_defaults04.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Functions must be unambiguously in the whitelist
--FILE--
<?php
namespace NS;
function test_default($x = max(1, 3, 2)) {
echo "x is $x\n";
}
test_default();
test_default(2);
test_default();
?>
--EXPECTF--
Fatal error: Constant expression uses function NS\max() which is not in get_const_expr_functions() in %s on line 3
20 changes: 20 additions & 0 deletions Zend/tests/call_in_const/param_ref.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
--TEST--
Warn when calling function expecting a reference as an argument
--INI--
error_reporting=E_ALL
--FILE--
<?php
class Example {
const VALUES = [];
const IS_MATCH = preg_match('/test/', 'testing');
const IS_MATCH_V2 = preg_match('/test/', 'testing', self::VALUES);

public static function main() {
echo "X is " . self::X . "\n";
}
}
var_dump(Example::IS_MATCH);
var_dump(Example::IS_MATCH_V2);
var_dump(Example::VALUES);
--EXPECTF--
Fatal error: Constant expression uses function preg_match() which is not in get_const_expr_functions() in %s on line 4
22 changes: 22 additions & 0 deletions Zend/tests/call_in_const/property_default01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
--TEST--
Can call user-defined functions from defaults of static properties
--FILE--
<?php
namespace NS;
function log_call($arg) {
echo "log_call(" . var_export($arg, true) . ")\n";
return $arg;
}
class MyClass {
public static $DEBUG = log_call(true);
public static $DEBUG2 = namespace\log_call(range(1,2));
}
echo "Start\n";
var_export(MyClass::$DEBUG); echo "\n";
var_export(MyClass::$DEBUG2); echo "\n";
var_export(MyClass::$DEBUG); echo "\n";
MyClass::$DEBUG = "New value";
echo MyClass::$DEBUG . "\n";
?>
--EXPECTF--
Fatal error: Constant expression uses function NS\log_call() which is not in get_const_expr_functions() in %s on line 8
16 changes: 16 additions & 0 deletions Zend/tests/call_in_const/property_default02.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
Cannot call functions from defaults of instance properties
--FILE--
<?php
// Currently, php will evaluate all of the instance property defaults at once and cache them
// the first time a class gets instantiated, in _object_and_properties_init.
//
// Authors of future RFCs may wish to change PHP
// to allow `count()` or `generate_unique_id()` or `public $fields = new stdClass();`
// as the defaults of instance properties.
class MyClass {
public $v1 = count([]);
}
?>
--EXPECTF--
Fatal error: Default value for instance property MyClass::$v1 cannot contain function calls in %s on line 9
35 changes: 35 additions & 0 deletions Zend/tests/call_in_const/recursion.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Recursion in calls in class constants causes an error
--FILE--
<?php
function x_plus_1() {
echo "Computing X + 1\n";
return Recursion::X + 1;
}
class Recursion {
const X = x_plus_1();
const MISSING = MISSING_GLOBAL + 1;
}
try {
echo "Recursion::X=" . Recursion::X . "\n";
} catch (Error $e) {
printf("Caught %s: %s\n", get_class($e), $e->getMessage());
}
try {
echo "Recursion::X=" . Recursion::X . "\n";
} catch (Error $e) {
printf("Second call caught %s: %s\n", get_class($e), $e->getMessage());
}
try {
echo "Recursion::MISSING=" . Recursion::MISSING;
} catch (Error $e) {
printf("Caught %s: %s\n", get_class($e), $e->getMessage());
}
try {
echo "Recursion::MISSING=" . Recursion::MISSING;
} catch (Error $e) {
printf("Second call caught %s: %s\n", get_class($e), $e->getMessage());
}
?>
--EXPECTF--
Fatal error: Constant expression uses function x_plus_1() which is not in get_const_expr_functions() in %s on line 7
14 changes: 14 additions & 0 deletions Zend/tests/call_in_const/static_default01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Can call internal functions from defaults of static variables
--FILE--
<?php
function main() {
static $call = SPRINTF("%s!", sprintf("Hello, %s", "World"));
echo "$call\n";
}
main();
main();
// Note: It may be possible to allowing literally any call or value in static variables, but this is outside of the scope of this RFC.
?>
--EXPECTF--
Fatal error: Constant expression uses function SPRINTF() which is not in get_const_expr_functions() in %s on line 3
18 changes: 18 additions & 0 deletions Zend/tests/call_in_const/static_default02.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Cannot call user-defined functions from defaults of static variables
--FILE--
<?php
function log_call(string $arg) {
echo "log_call('$arg')\n";
return $arg;
}

$f = function () {
static $call = log_call(sprintf("Hello, %s", "World"));
echo "$call\n";
};
$f();
$f();
?>
--EXPECTF--
Fatal error: Constant expression uses function log_call() which is not in get_const_expr_functions() in %s on line 8
18 changes: 18 additions & 0 deletions Zend/tests/call_in_const/static_default03.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--TEST--
Can call internal functions from defaults of static variables
--FILE--
<?php
function main() {
static $call = missing_sprintf("%s!", sprintf("Hello, %s", "World"));
echo "$call\n";
}
for ($i = 0; $i < 2; $i++) {
try {
main();
} catch (Error $e) {
printf("Caught %s: %s at line %d\n", get_class($e), $e->getMessage(), $e->getLine());
}
}
?>
--EXPECTF--
Fatal error: Constant expression uses function missing_sprintf() which is not in get_const_expr_functions() in %s on line 3
Loading