diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 3b3cf0cd..fe160707 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.php @@ -5087,6 +5087,12 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont $type = $this->getVarType($name); if ($this->isNativeType($type)) { $this->warning($var, "Variable of native type `\${$name}` cannot be unset"); + } elseif ($type === self::TYPE_REF) { + // Reinitialize as a fresh empty Ref so subsequent writes + // to this variable don't access freed memory. + // Do NOT change localVars type — it must stay TYPE_REF + // for foreach-by-ref to work correctly. + $lines[] = $name . ' = ' . self::TYPE_REF . '();'; } else { $lines[] = "{$name}.unset();"; } @@ -5977,7 +5983,11 @@ class CompilerBase extends \PhpAot\Core\Translator implements PropertyAccessCont $fn = $this->getChainedFunc($op); $expr = $node; if ($this->isVarExpr($expr)) { - return $fn . '(' . $this->parseExpr($expr) . ')'; + if (!$getValue) { + return $fn . '(' . $this->parseExpr($expr) . ')'; + } + // $getValue is true: fall through to use the chain+result mechanism, + // which ensures the result type is TYPE_VAR (compatible with ternaries). } // 单属性读取(非链式) if ($this->isPropertyFetch($expr) and $this->isVarExpr($expr->var) and $this->isIdExpr($expr->name)) { diff --git a/tests/aot/coalesce/unset-ref-coalesce.phpt b/tests/aot/coalesce/unset-ref-coalesce.phpt new file mode 100644 index 00000000..40c7ab9f --- /dev/null +++ b/tests/aot/coalesce/unset-ref-coalesce.phpt @@ -0,0 +1,96 @@ +--TEST-- +Null coalescing (??) on unset reference variable +--FILE-- + +--EXPECT-- +int(2) +int(2) +int(2) +bool(false) +int(123) +string(5) "world" +string(5) "world" +string(5) "world" +bool(false) +string(7) "default" +int(42) +string(8) "fallback" +int(30) +int(20) +int(200) +int(300) +int(100) +int(777) +bool(false) +int(-1)