From f412bc611aea1aa2354af86dd3c69383b6af680d Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Thu, 17 Jun 2021 17:18:36 +0100 Subject: [PATCH 1/2] Fix Bug #81160: isset/empty doesn't throw a TypeError on invalid string offset Co-authored-by: Ilija Tovilo --- Zend/Optimizer/zend_inference.c | 3 +- Zend/tests/bug60362.phpt | 75 ------- Zend/tests/bug62680.phpt | 11 - Zend/tests/bug69889.phpt | 21 -- Zend/tests/empty_str_offset.phpt | 135 ------------ Zend/tests/isset_str_offset.phpt | 129 ----------- .../array_isset_empty_coalese_errors.phpt | 100 +++++++++ .../{ => offsets_isset_empty}/bug31098.phpt | 29 ++- Zend/tests/offsets_isset_empty/bug60362.phpt | 99 +++++++++ Zend/tests/offsets_isset_empty/bug62680.phpt | 22 ++ Zend/tests/offsets_isset_empty/bug69889.phpt | 34 +++ Zend/tests/offsets_isset_empty/bug81160.phpt | 110 ++++++++++ .../offsets_isset_empty/empty_str_offset.phpt | 207 ++++++++++++++++++ .../offsets_isset_empty/isset_str_offset.phpt | 201 +++++++++++++++++ Zend/zend_execute.c | 166 ++++++++------ ext/opcache/jit/zend_jit_helpers.c | 98 ++++++--- tests/strings/offsets_chaining_5.phpt | 16 +- tests/strings/offsets_general.phpt | 10 +- 18 files changed, 985 insertions(+), 481 deletions(-) delete mode 100644 Zend/tests/bug60362.phpt delete mode 100644 Zend/tests/bug62680.phpt delete mode 100644 Zend/tests/bug69889.phpt delete mode 100644 Zend/tests/empty_str_offset.phpt delete mode 100644 Zend/tests/isset_str_offset.phpt create mode 100644 Zend/tests/offsets_isset_empty/array_isset_empty_coalese_errors.phpt rename Zend/tests/{ => offsets_isset_empty}/bug31098.phpt (69%) create mode 100644 Zend/tests/offsets_isset_empty/bug60362.phpt create mode 100644 Zend/tests/offsets_isset_empty/bug62680.phpt create mode 100644 Zend/tests/offsets_isset_empty/bug69889.phpt create mode 100644 Zend/tests/offsets_isset_empty/bug81160.phpt create mode 100644 Zend/tests/offsets_isset_empty/empty_str_offset.phpt create mode 100644 Zend/tests/offsets_isset_empty/isset_str_offset.phpt diff --git a/Zend/Optimizer/zend_inference.c b/Zend/Optimizer/zend_inference.c index e971b3ba2e9f8..f4e8d2114c31c 100644 --- a/Zend/Optimizer/zend_inference.c +++ b/Zend/Optimizer/zend_inference.c @@ -5115,7 +5115,8 @@ ZEND_API bool zend_may_throw_ex(const zend_op *opline, const zend_ssa_op *ssa_op return 0; } case ZEND_FETCH_IS: - return (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT)); + return (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT)) + || (t1 & MAY_BE_STRING && t2 & MAY_BE_STRING); case ZEND_ISSET_ISEMPTY_DIM_OBJ: return (t1 & MAY_BE_OBJECT) || (t2 & (MAY_BE_ARRAY|MAY_BE_OBJECT)); case ZEND_FETCH_DIM_IS: diff --git a/Zend/tests/bug60362.phpt b/Zend/tests/bug60362.phpt deleted file mode 100644 index 51a47760a26ef..0000000000000 --- a/Zend/tests/bug60362.phpt +++ /dev/null @@ -1,75 +0,0 @@ ---TEST-- -Bug #60362: non-existent sub-sub keys should not have values ---FILE-- - 'foz'); - -if (isset($arr['exists']['non_existent'])) { - echo "sub-key 'non_existent' is set: "; - var_dump($arr['exists']['non_existent']); -} else { - echo "sub-key 'non_existent' is not set.\n"; -} -if (isset($arr['exists'][1])) { - echo "sub-key 1 is set: "; - var_dump($arr['exists'][1]); -} else { - echo "sub-key 1 is not set.\n"; -} - -echo "-------------------\n"; -if (isset($arr['exists']['non_existent']['sub_sub'])) { - echo "sub-key 'sub_sub' is set: "; - var_dump($arr['exists']['non_existent']['sub_sub']); -} else { - echo "sub-sub-key 'sub_sub' is not set.\n"; -} -if (isset($arr['exists'][1][0])) { - echo "sub-sub-key 0 is set: "; - var_dump($arr['exists'][1][0]); -} else { - echo "sub-sub-key 0 is not set.\n"; -} - -echo "-------------------\n"; -if (empty($arr['exists']['non_existent'])) { - echo "sub-key 'non_existent' is empty.\n"; -} else { - echo "sub-key 'non_existent' is not empty: "; - var_dump($arr['exists']['non_existent']); -} -if (empty($arr['exists'][1])) { - echo "sub-key 1 is empty.\n"; -} else { - echo "sub-key 1 is not empty: "; - var_dump($arr['exists'][1]); -} - -echo "-------------------\n"; -if (empty($arr['exists']['non_existent']['sub_sub'])) { - echo "sub-sub-key 'sub_sub' is empty.\n"; -} else { - echo "sub-sub-key 'sub_sub' is not empty: "; - var_dump($arr['exists']['non_existent']['sub_sub']); -} -if (empty($arr['exists'][1][0])) { - echo "sub-sub-key 0 is empty.\n"; -} else { - echo "sub-sub-key 0 is not empty: "; - var_dump($arr['exists'][1][0]); -} -echo "DONE"; -?> ---EXPECT-- -sub-key 'non_existent' is not set. -sub-key 1 is set: string(1) "o" -------------------- -sub-sub-key 'sub_sub' is not set. -sub-sub-key 0 is set: string(1) "o" -------------------- -sub-key 'non_existent' is empty. -sub-key 1 is not empty: string(1) "o" -------------------- -sub-sub-key 'sub_sub' is empty. -sub-sub-key 0 is not empty: string(1) "o" -DONE diff --git a/Zend/tests/bug62680.phpt b/Zend/tests/bug62680.phpt deleted file mode 100644 index e2a2366e7a924..0000000000000 --- a/Zend/tests/bug62680.phpt +++ /dev/null @@ -1,11 +0,0 @@ ---TEST-- -Bug #62680 (Function isset() throws fatal error on set array if non-existent key depth >= 3) ---FILE-- - ---EXPECT-- -bool(false) -bool(false) diff --git a/Zend/tests/bug69889.phpt b/Zend/tests/bug69889.phpt deleted file mode 100644 index dd555ab407a46..0000000000000 --- a/Zend/tests/bug69889.phpt +++ /dev/null @@ -1,21 +0,0 @@ ---TEST-- -Bug #69889: Null coalesce operator doesn't work for string offsets ---FILE-- - ---EXPECT-- -string(1) "t" -string(7) "default" -string(7) "default" -string(7) "default" -string(7) "default" diff --git a/Zend/tests/empty_str_offset.phpt b/Zend/tests/empty_str_offset.phpt deleted file mode 100644 index 268c0d4869b69..0000000000000 --- a/Zend/tests/empty_str_offset.phpt +++ /dev/null @@ -1,135 +0,0 @@ ---TEST-- -Testing empty() with string offsets ---FILE-- - ---EXPECTF-- -- empty --- -bool(false) -bool(true) -bool(true) -bool(false) -bool(false) -bool(true) -bool(false) -bool(true) -bool(true) -- string literal --- -bool(false) -bool(true) -bool(false) -bool(false) -bool(true) -bool(true) -bool(true) -bool(true) -- string variable --- -bool(false) -bool(true) -bool(false) -bool(false) -bool(true) -bool(true) -bool(true) -bool(true) -- bool --- -bool(false) -bool(false) -bool(true) -- null --- -bool(false) -- double --- - -Deprecated: Implicit conversion from float -1.1 to int loses precision in %s on line %d -bool(false) - -Deprecated: Implicit conversion from float -10.5 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float -4.1 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float -0.8 to int loses precision in %s on line %d -bool(false) - -Deprecated: Implicit conversion from float -0.1 to int loses precision in %s on line %d -bool(false) - -Deprecated: Implicit conversion from float 0.2 to int loses precision in %s on line %d -bool(false) - -Deprecated: Implicit conversion from float 0.9 to int loses precision in %s on line %d -bool(false) - -Deprecated: Implicit conversion from float 3.141592653589793 to int loses precision in %s on line %d -bool(false) - -Deprecated: Implicit conversion from float 100.5001 to int loses precision in %s on line %d -bool(true) -- array --- -bool(true) -bool(true) -- object --- -bool(true) -- resource --- -bool(true) -done diff --git a/Zend/tests/isset_str_offset.phpt b/Zend/tests/isset_str_offset.phpt deleted file mode 100644 index f819c9dfe1391..0000000000000 --- a/Zend/tests/isset_str_offset.phpt +++ /dev/null @@ -1,129 +0,0 @@ ---TEST-- -Testing isset with string offsets ---FILE-- - ---EXPECTF-- -- isset --- -bool(true) -bool(false) -bool(true) -bool(true) -bool(true) -bool(true) -bool(false) -bool(false) -- string literal --- -bool(true) -bool(false) -bool(true) -bool(true) -bool(true) -bool(false) -bool(false) -bool(false) -- string variable --- -bool(true) -bool(false) -bool(true) -bool(true) -bool(true) -bool(false) -bool(false) -bool(false) -- bool --- -bool(true) -bool(true) -bool(false) -- null --- -bool(true) -- double --- - -Deprecated: Implicit conversion from float -1.1 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float -10.5 to int loses precision in %s on line %d -bool(false) - -Deprecated: Implicit conversion from float -0.8 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float -0.1 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float 0.2 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float 0.9 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float 3.141592653589793 to int loses precision in %s on line %d -bool(true) - -Deprecated: Implicit conversion from float 100.5001 to int loses precision in %s on line %d -bool(false) -- array --- -bool(false) -bool(false) -- object --- -bool(false) -- resource --- -bool(false) -done diff --git a/Zend/tests/offsets_isset_empty/array_isset_empty_coalese_errors.phpt b/Zend/tests/offsets_isset_empty/array_isset_empty_coalese_errors.phpt new file mode 100644 index 0000000000000..bda26329a3f61 --- /dev/null +++ b/Zend/tests/offsets_isset_empty/array_isset_empty_coalese_errors.phpt @@ -0,0 +1,100 @@ +--TEST-- +Invalid array offset types throws TypeError on isset/empty/coalese +--FILE-- +getMessage(), "\n"; +} +try { + var_dump(empty($v[$o])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($v[$o] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +echo 'Array ($variable) as offsets:', "\n"; +try { + var_dump(isset($v[$a])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(empty($v[$a])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($v[$a] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +echo "Array (constant empty array) as offsets:\n"; +try { + var_dump(isset($v[[]])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(empty($v[[]])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($v[[]] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +echo "Resource as offsets:\n"; +try { + var_dump(isset($v[$r])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(empty($v[$r])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($v[$r] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Objects as offsets: +Cannot access offset of type stdClass in isset or empty +Cannot access offset of type stdClass in isset or empty +Cannot access offset of type stdClass on array +Array ($variable) as offsets: +Cannot access offset of type array in isset or empty +Cannot access offset of type array in isset or empty +Cannot access offset of type array on array +Array (constant empty array) as offsets: +Cannot access offset of type array in isset or empty +Cannot access offset of type array in isset or empty +Cannot access offset of type array on array +Resource as offsets: + +Warning: Resource ID#3 used as offset, casting to integer (3) in %s on line %d +bool(false) + +Warning: Resource ID#3 used as offset, casting to integer (3) in %s on line %d +bool(true) + +Warning: Resource ID#3 used as offset, casting to integer (3) in %s on line %d +string(7) "default" + diff --git a/Zend/tests/bug31098.phpt b/Zend/tests/offsets_isset_empty/bug31098.phpt similarity index 69% rename from Zend/tests/bug31098.phpt rename to Zend/tests/offsets_isset_empty/bug31098.phpt index 862fc6fc46dce..cb25cd6b41fe4 100644 --- a/Zend/tests/bug31098.phpt +++ b/Zend/tests/offsets_isset_empty/bug31098.phpt @@ -9,11 +9,26 @@ var_dump(isset($a->b)); $a = '0'; var_dump(isset($a->b)); $a = ''; -var_dump(isset($a['b'])); + +try { + var_dump(isset($a['b'])); +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} $a = 'a'; -var_dump(isset($a['b'])); + +try { + var_dump(isset($a['b'])); +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} $a = '0'; -var_dump(isset($a['b'])); + +try { + var_dump(isset($a['b'])); +} catch (\TypeError $e) { + echo $e->getMessage(), "\n"; +} $simpleString = "Bogus String Text"; echo isset($simpleString->wrong)?"bug\n":"ok\n"; @@ -46,10 +61,18 @@ echo $simpleString["0"] === "B"?"ok\n":"bug\n"; bool(false) bool(false) bool(false) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d bool(false) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d bool(false) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d bool(false) ok + +Warning: Cannot access offset of type string in isset or empty in %s on line %d ok ok ok diff --git a/Zend/tests/offsets_isset_empty/bug60362.phpt b/Zend/tests/offsets_isset_empty/bug60362.phpt new file mode 100644 index 0000000000000..88473bef3149b --- /dev/null +++ b/Zend/tests/offsets_isset_empty/bug60362.phpt @@ -0,0 +1,99 @@ +--TEST-- +Bug #60362: non-existent sub-sub keys should not have values +--FILE-- + 'foz'); + +try { + if (isset($arr['exists']['non_existent'])) { + echo "sub-key 'non_existent' is set: "; + var_dump($arr['exists']['non_existent']); + } else { + echo "sub-key 'non_existent' is not set.\n"; + } +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +if (isset($arr['exists'][1])) { + echo "sub-key 1 is set: "; + var_dump($arr['exists'][1]); +} else { + echo "sub-key 1 is not set.\n"; +} + +echo "-------------------\n"; +try { + if (isset($arr['exists']['non_existent']['sub_sub'])) { + echo "sub-key 'sub_sub' is set: "; + var_dump($arr['exists']['non_existent']['sub_sub']); + } else { + echo "sub-sub-key 'sub_sub' is not set.\n"; + } +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +if (isset($arr['exists'][1][0])) { + echo "sub-sub-key 0 is set: "; + var_dump($arr['exists'][1][0]); +} else { + echo "sub-sub-key 0 is not set.\n"; +} + +echo "-------------------\n"; +try { + if (empty($arr['exists']['non_existent'])) { + echo "sub-key 'non_existent' is empty.\n"; + } else { + echo "sub-key 'non_existent' is not empty: "; + var_dump($arr['exists']['non_existent']); + } +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +if (empty($arr['exists'][1])) { + echo "sub-key 1 is empty.\n"; +} else { + echo "sub-key 1 is not empty: "; + var_dump($arr['exists'][1]); +} + +echo "-------------------\n"; +try { + if (empty($arr['exists']['non_existent']['sub_sub'])) { + echo "sub-sub-key 'sub_sub' is empty.\n"; + } else { + echo "sub-sub-key 'sub_sub' is not empty: "; + var_dump($arr['exists']['non_existent']['sub_sub']); + } +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +if (empty($arr['exists'][1][0])) { + echo "sub-sub-key 0 is empty.\n"; +} else { + echo "sub-sub-key 0 is not empty: "; + var_dump($arr['exists'][1][0]); +} +echo "DONE"; +?> +--EXPECTF-- +Warning: Cannot access offset of type string in isset or empty in %s on line %d +sub-key 'non_existent' is not set. +sub-key 1 is set: string(1) "o" +------------------- + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +sub-sub-key 'sub_sub' is not set. +sub-sub-key 0 is set: string(1) "o" +------------------- + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +sub-key 'non_existent' is empty. +sub-key 1 is not empty: string(1) "o" +------------------- + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +sub-sub-key 'sub_sub' is empty. +sub-sub-key 0 is not empty: string(1) "o" +DONE diff --git a/Zend/tests/offsets_isset_empty/bug62680.phpt b/Zend/tests/offsets_isset_empty/bug62680.phpt new file mode 100644 index 0000000000000..05bdc24650b0e --- /dev/null +++ b/Zend/tests/offsets_isset_empty/bug62680.phpt @@ -0,0 +1,22 @@ +--TEST-- +Bug #62680 (Function isset() throws fatal error on set array if non-existent key depth >= 3) +--FILE-- +getMessage(), \PHP_EOL; +} +try { + var_dump(isset($array[0]["a"]["b"]["c"])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +?> +--EXPECTF-- +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(false) diff --git a/Zend/tests/offsets_isset_empty/bug69889.phpt b/Zend/tests/offsets_isset_empty/bug69889.phpt new file mode 100644 index 0000000000000..fcc5132dad944 --- /dev/null +++ b/Zend/tests/offsets_isset_empty/bug69889.phpt @@ -0,0 +1,34 @@ +--TEST-- +Bug #69889: Null coalesce operator doesn't work for string offsets +--FILE-- +getMessage(), \PHP_EOL; +} + +try { + var_dump(isset($foo["str"]) ? $foo["str"] : "default"); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} + +?> +--EXPECTF-- +string(1) "t" +string(7) "default" +string(7) "default" + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +string(7) "default" + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +string(7) "default" diff --git a/Zend/tests/offsets_isset_empty/bug81160.phpt b/Zend/tests/offsets_isset_empty/bug81160.phpt new file mode 100644 index 0000000000000..80271a5474503 --- /dev/null +++ b/Zend/tests/offsets_isset_empty/bug81160.phpt @@ -0,0 +1,110 @@ +--TEST-- +Bug #81160: isset/empty doesn't throw a TypeError on invalid string offset which is inconsistent compared to arrays +--FILE-- +getMessage(), "\n"; +} +try { + var_dump(empty($s[$o])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($s[$o] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +echo 'Array ($variable) as offsets:', "\n"; +try { + var_dump(isset($s[$a])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(empty($s[$a])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($s[$a] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +echo "Array (constant empty array) as offsets:\n"; +try { + var_dump(isset($s[[]])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(empty($s[[]])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($s[[]] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +echo "Resource as offsets:\n"; +try { + var_dump(isset($s[$r])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump(empty($s[$r])); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} +try { + var_dump($s[$r] ?? 'default'); +} catch (\Throwable $e) { + echo $e->getMessage(), "\n"; +} + +?> +--EXPECTF-- +Objects as offsets: + +Warning: Cannot access offset of type stdClass in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type stdClass in isset or empty in %s on line %d +bool(true) +Cannot access offset of type stdClass in isset or empty +Array ($variable) as offsets: + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(true) +Cannot access offset of type array in isset or empty +Array (constant empty array) as offsets: + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(true) +Cannot access offset of type array in isset or empty +Resource as offsets: + +Warning: Cannot access offset of type resource in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type resource in isset or empty in %s on line %d +bool(true) +Cannot access offset of type resource in isset or empty + diff --git a/Zend/tests/offsets_isset_empty/empty_str_offset.phpt b/Zend/tests/offsets_isset_empty/empty_str_offset.phpt new file mode 100644 index 0000000000000..99f315c210bc2 --- /dev/null +++ b/Zend/tests/offsets_isset_empty/empty_str_offset.phpt @@ -0,0 +1,207 @@ +--TEST-- +Testing empty() with string offsets +--FILE-- +getMessage(), \PHP_EOL; +} +try { + var_dump(empty($str['good'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(empty($str['3 and a half'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- string variable ---\n"; +var_dump(empty($str[$key = '-1'])); // 3 +var_dump(empty($str[$key = '-10'])); +var_dump(empty($str[$key = '0'])); +var_dump(empty($str[$key = '1'])); +var_dump(empty($str[$key = '4'])); // 0 +try { + var_dump(empty($str[$key = '1.5'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(empty($str[$key = 'good'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(empty($str[$key = '3 and a half'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- bool ---\n"; +var_dump(empty($str[true])); +var_dump(empty($str[false])); +echo "Sub-keys:\n"; +var_dump(empty($str[false][true])); +print "- null ---\n"; +var_dump(empty($str[null])); +print "- double ---\n"; +var_dump(empty($str[-1.1])); +var_dump(empty($str[-10.5])); +var_dump(empty($str[-4.1])); +var_dump(empty($str[-0.8])); +var_dump(empty($str[-0.1])); +var_dump(empty($str[0.2])); +var_dump(empty($str[0.9])); +var_dump(empty($str[M_PI])); +var_dump(empty($str[100.5001])); +print "- array ---\n"; +try { + var_dump(empty($str[array()])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(empty($str[array(1,2,3)])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- object ---\n"; +try { + var_dump(empty($str[new stdClass()])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- resource ---\n"; +$f = fopen(__FILE__, 'r'); +try { + var_dump(empty($str[$f])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "done\n"; + +?> +--EXPECTF-- +- empty --- +bool(false) +bool(true) +bool(true) +bool(false) +bool(false) +bool(true) +bool(false) +bool(true) +bool(true) +- string literal --- +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(true) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(true) + +Warning: Illegal string offset "3 and a half" in %s on line %d +bool(true) +- string variable --- +bool(false) +bool(true) +bool(false) +bool(false) +bool(true) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(true) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(true) + +Warning: Illegal string offset "3 and a half" in %s on line %d +bool(true) +- bool --- + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(false) +Sub-keys: + +Warning: String offset cast occurred in %s on line %d + +Warning: String offset cast occurred in %s on line %d +bool(true) +- null --- + +Warning: String offset cast occurred in %s on line %d +bool(false) +- double --- + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(true) +- array --- + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(true) + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(true) +- object --- + +Warning: Cannot access offset of type stdClass in isset or empty in %s on line %d +bool(true) +- resource --- + +Warning: Cannot access offset of type resource in isset or empty in %s on line %d +bool(true) +done diff --git a/Zend/tests/offsets_isset_empty/isset_str_offset.phpt b/Zend/tests/offsets_isset_empty/isset_str_offset.phpt new file mode 100644 index 0000000000000..6516cca9c56f8 --- /dev/null +++ b/Zend/tests/offsets_isset_empty/isset_str_offset.phpt @@ -0,0 +1,201 @@ +--TEST-- +Testing isset with string offsets +--FILE-- +getMessage(), \PHP_EOL; +} +try { + var_dump(isset($str['good'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(isset($str['3 and a half'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- string variable ---\n"; +var_dump(isset($str[$key = '-1'])); // 3 +var_dump(isset($str[$key = '-10'])); +var_dump(isset($str[$key = '0'])); +var_dump(isset($str[$key = '1'])); +var_dump(isset($str[$key = '4'])); // 0 +try { + var_dump(isset($str[$key = '1.5'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(isset($str[$key = 'good'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(isset($str[$key = '3 and a half'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- bool ---\n"; +var_dump(isset($str[true])); +var_dump(isset($str[false])); +echo "Sub-keys:\n"; +var_dump(isset($str[false][true])); +print "- null ---\n"; +var_dump(isset($str[null])); +print "- double ---\n"; +var_dump(isset($str[-1.1])); +var_dump(isset($str[-10.5])); +var_dump(isset($str[-0.8])); +var_dump(isset($str[-0.1])); +var_dump(isset($str[0.2])); +var_dump(isset($str[0.9])); +var_dump(isset($str[M_PI])); +var_dump(isset($str[100.5001])); +print "- array ---\n"; +try { + var_dump(isset($str[array()])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +try { + var_dump(isset($str[array(1,2,3)])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- object ---\n"; +try { + var_dump(isset($str[new stdClass()])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "- resource ---\n"; +$f = fopen(__FILE__, 'r'); +try { + var_dump(isset($str[$f])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} +print "done\n"; + +?> +--EXPECTF-- +- isset --- +bool(true) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +- string literal --- +bool(true) +bool(false) +bool(true) +bool(true) +bool(true) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(false) + +Warning: Illegal string offset "3 and a half" in %s on line %d +bool(false) +- string variable --- +bool(true) +bool(false) +bool(true) +bool(true) +bool(true) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type string in isset or empty in %s on line %d +bool(false) + +Warning: Illegal string offset "3 and a half" in %s on line %d +bool(false) +- bool --- + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(true) +Sub-keys: + +Warning: String offset cast occurred in %s on line %d + +Warning: String offset cast occurred in %s on line %d +bool(false) +- null --- + +Warning: String offset cast occurred in %s on line %d +bool(true) +- double --- + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(false) + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(true) + +Warning: String offset cast occurred in %s on line %d +bool(false) +- array --- + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(false) + +Warning: Cannot access offset of type array in isset or empty in %s on line %d +bool(false) +- object --- + +Warning: Cannot access offset of type stdClass in isset or empty in %s on line %d +bool(false) +- resource --- + +Warning: Cannot access offset of type resource in isset or empty in %s on line %d +bool(false) +done diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 1819d0b671dc8..26a322597226e 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1677,6 +1677,60 @@ static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type return zval_get_long_func(dim, /* is_strict */ false); } +/* This is a copy of zend_check_string_offset() use for BP_VAR_IS operations. + * Compared to the behaviour of array offsets, isset()/empty() did not throw + * TypeErrors for invalid offsets, or warn on type coercions. + * The coalesce operator did throw on invalid offset types but not for type coercions. */ +static zend_never_inline zend_long zend_check_string_offset_is_ops(zval *dim, bool *is_type_valid, bool is_coalesce EXECUTE_DATA_DC) +{ + zend_long offset; + *is_type_valid = true; + +try_again: + switch(Z_TYPE_P(dim)) { + case IS_LONG: + return Z_LVAL_P(dim); + case IS_STRING: + { + bool trailing_data = false; + /* For BC reasons we allow errors so that we can warn on leading numeric string */ + if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL, + /* allow errors */ true, NULL, &trailing_data)) { + if (UNEXPECTED(trailing_data)) { + *is_type_valid = false; + zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim)); + } + return offset; + } + *is_type_valid = false; + zend_error(E_WARNING, "Cannot access offset of type %s in isset or empty", zend_zval_type_name(dim)); + return 0; + } + case IS_UNDEF: + ZVAL_UNDEFINED_OP2(); + ZEND_FALLTHROUGH; + case IS_DOUBLE: + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + zend_error(E_WARNING, "String offset cast occurred"); + break; + case IS_REFERENCE: + dim = Z_REFVAL_P(dim); + goto try_again; + default: + *is_type_valid = false; + if (is_coalesce) { + zend_illegal_string_offset(dim, BP_VAR_IS); + } else { + zend_error(E_WARNING, "Cannot access offset of type %s in isset or empty", zend_zval_type_name(dim)); + } + return 0; + } + + return zval_get_long_func(dim, /* is_strict */ false); +} + ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void) { const char *msg = NULL; @@ -2747,73 +2801,35 @@ static zend_always_inline void zend_fetch_dimension_address_read(zval *result, z zend_string *str = Z_STR_P(container); zend_long offset; -try_string_offset: if (UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) { - switch (Z_TYPE_P(dim)) { - case IS_STRING: - { - bool trailing_data = false; - /* For BC reasons we allow errors so that we can warn on leading numeric string */ - if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, - NULL, /* allow errors */ true, NULL, &trailing_data)) { - if (UNEXPECTED(trailing_data)) { - zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim)); - } - goto out; - } - if (type == BP_VAR_IS) { - ZVAL_NULL(result); - return; - } - zend_illegal_string_offset(dim, BP_VAR_R); - ZVAL_NULL(result); - return; - } - case IS_UNDEF: - /* The string may be destroyed while throwing the notice. - * Temporarily increase the refcount to detect this situation. */ - if (!(GC_FLAGS(str) & IS_STR_INTERNED)) { - GC_ADDREF(str); - } - ZVAL_UNDEFINED_OP2(); - if (!(GC_FLAGS(str) & IS_STR_INTERNED) && UNEXPECTED(GC_DELREF(str) == 0)) { - zend_string_efree(str); - ZVAL_NULL(result); - return; - } - ZEND_FALLTHROUGH; - case IS_DOUBLE: - case IS_NULL: - case IS_FALSE: - case IS_TRUE: - if (type != BP_VAR_IS) { - /* The string may be destroyed while throwing the notice. - * Temporarily increase the refcount to detect this situation. */ - if (!(GC_FLAGS(str) & IS_STR_INTERNED)) { - GC_ADDREF(str); - } - zend_error(E_WARNING, "String offset cast occurred"); - if (!(GC_FLAGS(str) & IS_STR_INTERNED) && UNEXPECTED(GC_DELREF(str) == 0)) { - zend_string_efree(str); - ZVAL_NULL(result); - return; - } - } - break; - case IS_REFERENCE: - dim = Z_REFVAL_P(dim); - goto try_string_offset; - default: - zend_illegal_string_offset(dim, BP_VAR_R); - ZVAL_NULL(result); - return; + /* The string may be destroyed while throwing the notice. + * Temporarily increase the refcount to detect this situation. */ + if (!(GC_FLAGS(str) & IS_STR_INTERNED)) { + GC_ADDREF(str); + } + /* Coalesce operator didn't behave like isset()/empty() in that a + * TypeError was thrown if the offset was of type array/resource/object + * However, null/bool/float type coercion warnings were suppressed. */ + bool is_type_valid = true; + if (type == BP_VAR_IS) { + offset = zend_check_string_offset_is_ops(dim, &is_type_valid, /* is_coalesce */ true EXECUTE_DATA_CC); + } else { + offset = zend_check_string_offset(dim, dim_type EXECUTE_DATA_CC); } - offset = zval_get_long_func(dim, /* is_strict */ false); + if (!(GC_FLAGS(str) & IS_STR_INTERNED) && UNEXPECTED(GC_DELREF(str) == 0)) { + zend_string_efree(str); + ZVAL_NULL(result); + return; + } + /* Illegal offset assignment */ + if (!is_type_valid || UNEXPECTED(EG(exception) != NULL)) { + ZVAL_NULL(result); + return; + } } else { offset = Z_LVAL_P(dim); } - out: if (UNEXPECTED(ZSTR_LEN(str) < ((offset < 0) ? -(size_t)offset : ((size_t)offset + 1)))) { if (type != BP_VAR_IS) { @@ -2957,15 +2973,17 @@ static zend_never_inline bool ZEND_FASTCALL zend_isset_dim_slow(zval *container, /*if (OP2_TYPE & (IS_CV|IS_VAR)) {*/ ZVAL_DEREF(offset); /*}*/ - if (Z_TYPE_P(offset) < IS_STRING /* simple scalar types */ - || (Z_TYPE_P(offset) == IS_STRING /* or numeric string */ - && IS_LONG == is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), NULL, NULL, 0))) { - lval = zval_get_long_ex(offset, /* is_strict */ true); - goto str_offset; + + bool is_type_valid = true; + /* For BC we currently emit E_WARNINGs */ + lval = zend_check_string_offset_is_ops(offset, &is_type_valid, /* is_coalesce */ false EXECUTE_DATA_CC); + if (!is_type_valid || UNEXPECTED(EG(exception) != NULL)) { + return false; } - return 0; + goto str_offset; } } else { + /* Container is invalid, TODO deprecate this? */ return 0; } } @@ -2996,15 +3014,17 @@ static zend_never_inline bool ZEND_FASTCALL zend_isempty_dim_slow(zval *containe /*if (OP2_TYPE & (IS_CV|IS_VAR)) {*/ ZVAL_DEREF(offset); /*}*/ - if (Z_TYPE_P(offset) < IS_STRING /* simple scalar types */ - || (Z_TYPE_P(offset) == IS_STRING /* or numeric string */ - && IS_LONG == is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), NULL, NULL, 0))) { - lval = zval_get_long_ex(offset, /* is_strict */ true); - goto str_offset; + + bool is_type_valid = true; + /* For BC we currently emit E_WARNINGs */ + lval = zend_check_string_offset_is_ops(offset, &is_type_valid, /* is_coalesce */ false EXECUTE_DATA_CC); + if (!is_type_valid || UNEXPECTED(EG(exception) != NULL)) { + return true; } - return 1; + goto str_offset; } } else { + /* Container is invalid, TODO deprecate this? */ return 1; } } diff --git a/ext/opcache/jit/zend_jit_helpers.c b/ext/opcache/jit/zend_jit_helpers.c index 7c80de916aac4..84371770979cd 100644 --- a/ext/opcache/jit/zend_jit_helpers.c +++ b/ext/opcache/jit/zend_jit_helpers.c @@ -1058,6 +1058,60 @@ static zend_never_inline zend_long zend_check_string_offset(zval *dim, int type) return zval_get_long_func(dim, /* is_strict */ false); } +/* This is a copy of zend_check_string_offset() use for BP_VAR_IS operations. + * Compared to the behaviour of array offsets, isset()/empty() did not throw + * TypeErrors for invalid offsets, or warn on type coercions. + * The coalesce operator did throw on invalid offset types but not for type coercions. */ +static zend_never_inline zend_long zend_check_string_offset_is_ops(zval *dim, bool *is_type_valid, bool is_coalesce) +{ + zend_long offset; + *is_type_valid = true; + +try_again: + switch(Z_TYPE_P(dim)) { + case IS_LONG: + return Z_LVAL_P(dim); + case IS_STRING: + { + bool trailing_data = false; + /* For BC reasons we allow errors so that we can warn on leading numeric string */ + if (IS_LONG == is_numeric_string_ex(Z_STRVAL_P(dim), Z_STRLEN_P(dim), &offset, NULL, + /* allow errors */ true, NULL, &trailing_data)) { + if (UNEXPECTED(trailing_data)) { + *is_type_valid = false; + zend_error(E_WARNING, "Illegal string offset \"%s\"", Z_STRVAL_P(dim)); + } + return offset; + } + *is_type_valid = false; + zend_error(E_WARNING, "Cannot access offset of type %s in isset or empty", zend_zval_type_name(dim)); + return 0; + } + case IS_UNDEF: + zend_jit_undefined_op_helper(EG(current_execute_data)->opline->op2.var); + ZEND_FALLTHROUGH; + case IS_DOUBLE: + case IS_NULL: + case IS_FALSE: + case IS_TRUE: + zend_error(E_WARNING, "String offset cast occurred"); + break; + case IS_REFERENCE: + dim = Z_REFVAL_P(dim); + goto try_again; + default: + *is_type_valid = false; + if (is_coalesce) { + zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_STRING), dim, BP_VAR_IS); + } else { + zend_error(E_WARNING, "Cannot access offset of type %s in isset or empty", zend_zval_type_name(dim)); + } + return 0; + } + + return zval_get_long_func(dim, /* is_strict */ false); +} + static zend_always_inline zend_string* zend_jit_fetch_dim_str_offset(zend_string *str, zend_long offset) { if (UNEXPECTED((zend_ulong)offset >= (zend_ulong)ZSTR_LEN(str))) { @@ -1105,32 +1159,17 @@ static void ZEND_FASTCALL zend_jit_fetch_dim_str_is_helper(zend_string *str, zva { zend_long offset; -try_string_offset: if (UNEXPECTED(Z_TYPE_P(dim) != IS_LONG)) { - switch (Z_TYPE_P(dim)) { - /* case IS_LONG: */ - case IS_STRING: - if (IS_LONG == is_numeric_string(Z_STRVAL_P(dim), Z_STRLEN_P(dim), NULL, NULL, false)) { - break; - } - ZVAL_NULL(result); - return; - case IS_UNDEF: - zend_jit_undefined_op_helper(EG(current_execute_data)->opline->op2.var); - case IS_DOUBLE: - case IS_NULL: - case IS_FALSE: - case IS_TRUE: - break; - case IS_REFERENCE: - dim = Z_REFVAL_P(dim); - goto try_string_offset; - default: - zend_illegal_container_offset(ZSTR_KNOWN(ZEND_STR_STRING), dim, BP_VAR_IS); - break; + bool is_type_valid = true; + /* Coalesce operator didn't behave like isset()/empty() in that a + * TypeError was thrown if the offset was of type array/resource/object + * However, null/bool/float type coercion warnings were suppressed. */ + offset = zend_check_string_offset_is_ops(dim, &is_type_valid, /* is_coalesce */ true); + /* Illegal offset */ + if (!is_type_valid || UNEXPECTED(EG(exception) != NULL)) { + ZVAL_NULL(result); + return; } - - offset = zval_get_long_func(dim, /* is_strict */ false); } else { offset = Z_LVAL_P(dim); } @@ -1736,12 +1775,13 @@ static int ZEND_FASTCALL zend_jit_isset_dim_helper(zval *container, zval *offset } } else { ZVAL_DEREF(offset); - if (Z_TYPE_P(offset) < IS_STRING /* simple scalar types */ - || (Z_TYPE_P(offset) == IS_STRING /* or numeric string */ - && IS_LONG == is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), NULL, NULL, false))) { - lval = zval_get_long_ex(offset, /* is_strict */ true); - goto isset_str_offset; + bool is_type_valid = true; + /* For BC we currently emit E_WARNINGs */ + lval = zend_check_string_offset_is_ops(offset, &is_type_valid, /* is_coalesce */ false); + if (!is_type_valid || UNEXPECTED(EG(exception) != NULL)) { + return 0; } + goto isset_str_offset; } } return 0; diff --git a/tests/strings/offsets_chaining_5.phpt b/tests/strings/offsets_chaining_5.phpt index 49f062463f1e6..02da34820bc81 100644 --- a/tests/strings/offsets_chaining_5.phpt +++ b/tests/strings/offsets_chaining_5.phpt @@ -5,18 +5,30 @@ testing the behavior of string offset chaining $array = array('expected_array' => "foobar"); var_dump(isset($array['expected_array'])); var_dump($array['expected_array']); -var_dump(isset($array['expected_array']['foo'])); +try { + var_dump(isset($array['expected_array']['foo'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} var_dump($array['expected_array']['0foo']); -var_dump(isset($array['expected_array']['foo']['bar'])); +try { + var_dump(isset($array['expected_array']['foo']['bar'])); +} catch (\TypeError $e) { + echo $e->getMessage(), \PHP_EOL; +} var_dump($array['expected_array']['0foo']['0bar']); ?> --EXPECTF-- bool(true) string(6) "foobar" + +Warning: Cannot access offset of type string in isset or empty in %s on line %d bool(false) Warning: Illegal string offset "0foo" in %s on line %d string(1) "f" + +Warning: Cannot access offset of type string in isset or empty in %s on line %d bool(false) Warning: Illegal string offset "0foo" in %s on line %d diff --git a/tests/strings/offsets_general.phpt b/tests/strings/offsets_general.phpt index 16960eac95228..1cc37f0c72b9f 100644 --- a/tests/strings/offsets_general.phpt +++ b/tests/strings/offsets_general.phpt @@ -14,14 +14,20 @@ try { } catch (\TypeError $e) { echo $e->getMessage() . \PHP_EOL; } -var_dump(isset($string["foo"]["bar"])); +try { + var_dump(isset($string["foo"]["bar"])); +} catch (\TypeError $e) { + echo $e->getMessage() . \PHP_EOL; +} ?> ---EXPECT-- +--EXPECTF-- string(1) "B" string(1) "f" string(1) "o" bool(true) bool(true) Cannot access offset of type string on string + +Warning: Cannot access offset of type string in isset or empty in %s on line %d bool(false) From 19f643db893eb509da1c5cef215db9a9d84a1a89 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Fri, 17 Nov 2023 00:43:42 +0000 Subject: [PATCH 2/2] Upgrading --- UPGRADING | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/UPGRADING b/UPGRADING index c0f1771eb8a93..d12deec6dbf3a 100644 --- a/UPGRADING +++ b/UPGRADING @@ -19,6 +19,16 @@ PHP 8.4 UPGRADE NOTES 1. Backward Incompatible Changes ======================================== +- Core: + . isset() and empty() now check that the offset for string values is of the + correct type i.e. numeric, similarly to array and objects. + This means code similar to: + $a = 'string'; + var_dump(isset($a['b'])); + var_dump(empty($a['b'])); + will now warn with: "Cannot access offset of type string on string" + when previously it silently returned false. + - DOM: . New methods and constants were added to some DOM classes. If you inherit from these and you happen to have a method or property with the same name,