rfc:constant_redefinition

PHP RFC: Throw Exception on Attempt of Constant Redefinition

Introduction

PHP allows multiple statements that define the same constant, they are executed at run-tinw and only the first one actually define the constant value. Others just produce a notice.

<?php
const A = 1;
const A = 2;  // Notice: Constant A already defined
var_dump(A)   // int(1)
?>

Note: that constant definitions statement may be placed in different files or even set through php.ini auto_prepend_file directive. Taking all this into account, we can never be sure in constant value.

<?php
const WORLD = "World";
echo "Hello ".WORLD."\n"; // This may print anything...
                          // e.g. define("WORLD", file_get_contents("/etc/passwd"));
?>

Except of inconsistency and some vulnerability smell, this prevents us from obvious Constant Propagation Optimisation.

Proposal

I propose to throw Error exception on attempt of constant redefinition. This would protect unintended constant redefinition and would catch dangerous cases before usage of unexpected value.

On the other hand it'll be possible to support old redefinition behavior “op purpose”, wrapping second constant definition in try/catch.

<?php
const A = 1;
try {
    define("A", 2);
} catch (Throwable $e) {
    echo "Exception:" . $e->getMessage . "\n";
}
var_dump(A); // int(1)
?>

Proposed PHP Version(s)

PHP 8.0

Proposed Voting Choices

The vote is a straight Yes/No vote, that requires a 2/3 majority.

Patches and Tests

The change is too simple.

diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c
index 3e09f0c..66addfb 100644
--- a/Zend/zend_constants.c
+++ b/Zend/zend_constants.c
@@ -513,7 +513,11 @@ ZEND_API int zend_register_constant(zend_constant *c)
 		if (ZSTR_VAL(c->name)[0] == '\0' && ZSTR_LEN(c->name) > sizeof("\0__COMPILER_HALT_OFFSET__")-1
 			&& memcmp(ZSTR_VAL(name), "\0__COMPILER_HALT_OFFSET__", sizeof("\0__COMPILER_HALT_OFFSET__")) == 0) {
 		}
-		zend_error(E_NOTICE,"Constant %s already defined", ZSTR_VAL(name));
+		if (EG(current_execute_data)) {
+			zend_throw_error(NULL, "Constant %s already defined", ZSTR_VAL(name));
+		} else {
+			zend_error(E_NOTICE,"Constant %s already defined", ZSTR_VAL(name));
+		}
 		zend_string_release(c->name);
 		if (!(c->flags & CONST_PERSISTENT)) {
 			zval_dtor(&c->value);

The complete patch is at PR 1947

Implementation

After the project is implemented, this section should contain

  1. the version(s) it was merged to
  2. a link to the git commit(s)
  3. a link to the PHP manual entry for the feature

References

rfc/constant_redefinition.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1