Skip to content
Merged
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
9 changes: 5 additions & 4 deletions lib/JIT.php
Original file line number Diff line number Diff line change
Expand Up @@ -8550,6 +8550,7 @@ private function compileBlockInternal(
case OpCode::TYPE_ARRAY_DIM_FETCH_WRITE:
$forWrite = OpCode::TYPE_ARRAY_DIM_FETCH_WRITE === $op->type;
$fetchIs = !$forWrite && $op->arrayDimFetchIs;
$warnUndefKeyIncDec = $forWrite && $this->varFetchDestUsedAsIncDec($block, $i, (int) $op->arg1);
$value = $this->context->getVariableFromOp($block->getOperand($op->arg2));
// Zend: E_NOTICE + continue on non-object __get temp (#29231, re-#4673).
if (
Expand Down Expand Up @@ -8888,7 +8889,7 @@ private function compileBlockInternal(
break;
}
}
$fetched = $value->dimFetch($dim, $resultOp->type, $forWrite, $emitFloatKeyDeprecation);
$fetched = $value->dimFetch($dim, $resultOp->type, $forWrite, $emitFloatKeyDeprecation, $warnUndefKeyIncDec);
if ($forWrite) {
$this->context->setVariableOp($resultOp, $fetched);
} elseif ($forceBranchMerge) {
Expand Down Expand Up @@ -8936,7 +8937,7 @@ private function compileBlockInternal(
break;
}
}
$fetched = $value->dimFetch($dim, $resultOp->type, $forWrite, $emitFloatKeyDeprecation);
$fetched = $value->dimFetch($dim, $resultOp->type, $forWrite, $emitFloatKeyDeprecation, $warnUndefKeyIncDec);
if ($forWrite) {
$this->context->setVariableOp($resultOp, $fetched);
} elseif ($forceBranchMerge) {
Expand Down Expand Up @@ -8980,7 +8981,7 @@ private function compileBlockInternal(
Variable::KIND_VALUE,
JIT\JitValueBox::alloc($this->context)
);
$fetched = $boxed->dimFetch($dim, $resultOp->type, $forWrite);
$fetched = $boxed->dimFetch($dim, $resultOp->type, $forWrite, true, $warnUndefKeyIncDec);
if ($forWrite) {
$this->context->setVariableOp($resultOp, $fetched);
} elseif ($forceBranchMerge) {
Expand Down Expand Up @@ -9024,7 +9025,7 @@ private function compileBlockInternal(
$this->context->constantFromInteger($value->nextFreeElement)
);
}
$fetched = $value->dimFetch($dim, $resultOp->type, $forWrite);
$fetched = $value->dimFetch($dim, $resultOp->type, $forWrite, true, $warnUndefKeyIncDec);
if ($forceBranchMerge && !$forWrite) {
$this->assignOperand($resultOp, $fetched, true);
} else {
Expand Down
6 changes: 6 additions & 0 deletions lib/JIT/HashTableHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,12 @@ public static function offsetIsSetDim(Context $context, Value $ht, Variable $dim
return HashTableReadLlvm::offsetIsSetDim($context, $ht, $dim);
}

/** ++/-- FETCH_DIM_W: warn when key missing (#30078). */
public static function emitUndefinedArrayKeyWarningIfMissing(Context $context, Value $ht, Variable $dim): void
{
HashTableReadLlvm::emitUndefinedArrayKeyWarningIfMissing($context, $ht, $dim);
}

/**
* Read an element into a stack {@see __value__} slot (string/int/object/boxed keys; issue #86).
*
Expand Down
124 changes: 124 additions & 0 deletions lib/JIT/HashTableReadLlvm.php
Original file line number Diff line number Diff line change
Expand Up @@ -621,6 +621,130 @@ static function (Value $index) use ($context, $ht, $slot, $done): void {
);
}

/**
* Zend E_WARNING when ++/-- FETCH_DIM_W reads a missing key (#30078, zend_vm_def.h).
*/
public static function emitUndefinedArrayKeyWarningIfMissing(Context $context, Value $ht, Variable $dim): void
{
$exists = self::offsetIsSetDim($context, $ht, $dim);
$tag = 'diuw'.(string) self::nextSeq();
$hasKey = BasicBlockHelper::append($context, 'dim_inc_ukey_has_'.$tag);
$missKey = BasicBlockHelper::append($context, 'dim_inc_ukey_miss_'.$tag);
$done = BasicBlockHelper::append($context, 'dim_inc_ukey_done_'.$tag);
$context->builder->branchIf($exists, $hasKey, $missKey);
$context->builder->positionAtEnd($missKey);
self::emitUndefinedArrayKeyWarningForDim($context, $dim);
$context->builder->branch($done);
$context->builder->positionAtEnd($hasKey);
$context->builder->branch($done);
$context->builder->positionAtEnd($done);
}

/** Emit {@see __compiler_undefined_array_key_warning_*} for a dim operand (#30078). */
private static function emitUndefinedArrayKeyWarningForDim(Context $context, Variable $dim): void
{
$savedInsert = BasicBlockHelper::tryGetInsertBlock($context);
\PHPCompiler\ext\standard\StringTriggerErrorJit::implement($context);
if (null !== $savedInsert) {
BasicBlockHelper::restoreInsertBlock($context, $savedInsert);
} else {
BasicBlockHelper::ensureOpenInsertBlock($context, 'dim_inc_ukey_warn_setup');
}

if (Variable::TYPE_NATIVE_LONG === $dim->type) {
$context->builder->call(
$context->lookupFunction('__compiler_undefined_array_key_warning_long'),
$context->helper->loadValue($dim)
);

return;
}
if (Variable::TYPE_STRING === $dim->type) {
$keyStr = $context->helper->loadValue($dim);
$strMap = $context->structFieldMap['__string__'];
$i8p = $context->getTypeFromString('int8*');
$keyLen = $context->builder->load($context->builder->structGep($keyStr, $strMap['length']));
$keyBytes = $context->builder->structGep($keyStr, $strMap['value']);
$keyCStr = $context->builder->pointerCast($keyBytes, $i8p);
$context->builder->call(
$context->lookupFunction('__compiler_undefined_array_key_warning_cstr'),
$keyCStr,
$keyLen
);

return;
}
if (Variable::TYPE_VALUE === $dim->type) {
$valPtr = self::valuePtrFromDim($context, $dim);
$valueMap = $context->structFieldMap['__value__'];
$i8 = $context->getTypeFromString('int8');
$typeByte = $context->builder->load(
$context->builder->structGep($valPtr, $valueMap['type'])
);
$fn = $context->builder->getInsertBlock()->getParent();
$strBb = $fn->appendBasicBlock('dim_inc_ukey_str');
$longBb = $fn->appendBasicBlock('dim_inc_ukey_long');
$doneBb = $fn->appendBasicBlock('dim_inc_ukey_warn_done');
$afterStr = $fn->appendBasicBlock('dim_inc_ukey_after_str');
$context->builder->branchIf(
$context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_STRING, false)
),
$strBb,
$afterStr
);
$context->builder->positionAtEnd($strBb);
$keyStr = $context->builder->call($context->lookupFunction('__value__readString'), $valPtr);
$strMap = $context->structFieldMap['__string__'];
$keyLen = $context->builder->load($context->builder->structGep($keyStr, $strMap['length']));
$keyBytes = $context->builder->structGep($keyStr, $strMap['value']);
$i8p = $context->getTypeFromString('int8*');
$keyCStr = $context->builder->pointerCast($keyBytes, $i8p);
$context->builder->call(
$context->lookupFunction('__compiler_undefined_array_key_warning_cstr'),
$keyCStr,
$keyLen
);
$context->builder->branch($doneBb);
$context->builder->positionAtEnd($afterStr);
$context->builder->branchIf(
$context->builder->icmp(
Builder::INT_EQ,
$typeByte,
$i8->constInt(Variable::TYPE_NATIVE_LONG, false)
),
$longBb,
$doneBb
);
$context->builder->positionAtEnd($longBb);
$longKey = $context->builder->call($context->lookupFunction('__value__readLong'), $valPtr);
$context->builder->call(
$context->lookupFunction('__compiler_undefined_array_key_warning_long'),
$longKey
);
$context->builder->branch($doneBb);
$context->builder->positionAtEnd($doneBb);

return;
}
if (Variable::TYPE_NULL === $dim->type) {
DynamicPropertyDeprecationGuard::emitNullArrayOffset($context);
$emptyKey = $context->builder->load($context->constantStringFromString(''));
$strMap = $context->structFieldMap['__string__'];
$i8p = $context->getTypeFromString('int8*');
$keyLen = $context->builder->load($context->builder->structGep($emptyKey, $strMap['length']));
$keyBytes = $context->builder->structGep($emptyKey, $strMap['value']);
$keyCStr = $context->builder->pointerCast($keyBytes, $i8p);
$context->builder->call(
$context->lookupFunction('__compiler_undefined_array_key_warning_cstr'),
$keyCStr,
$keyLen
);
}
}

/**
* Read an element into a stack {@see __value__} slot (string/int/object/boxed keys; #10031 v4).
*
Expand Down
17 changes: 15 additions & 2 deletions lib/JIT/Variable.php
Original file line number Diff line number Diff line change
Expand Up @@ -984,7 +984,8 @@ public function dimFetch(
self $dim,
?Type $expectedType = null,
bool $forWrite = false,
bool $emitFloatKeyDeprecation = true
bool $emitFloatKeyDeprecation = true,
bool $warnUndefinedKeyForIncDec = false
): Variable {
switch ($this->type) {
case self::TYPE_STRING:
Expand Down Expand Up @@ -1076,6 +1077,10 @@ public function dimFetch(
$ht = HashTableHelper::loadHashtablePointer($this->context, $container);
if (self::TYPE_VALUE === $dim->type) {
if ($forWrite) {
if ($warnUndefinedKeyForIncDec) {
HashTableHelper::emitUndefinedArrayKeyWarningIfMissing($this->context, $ht, $dim);
}

return HashTableHelper::prepareValueBoxKeyWrite($this->context, $ht, $dim);
}

Expand Down Expand Up @@ -1103,6 +1108,10 @@ public function dimFetch(
if (self::TYPE_STRING === $dim->type) {
$key = $this->context->helper->loadValue($dim);
if ($forWrite && (null === $expectedType || Type::TYPE_ARRAY !== $expectedType->type)) {
if ($warnUndefinedKeyForIncDec) {
HashTableHelper::emitUndefinedArrayKeyWarningIfMissing($this->context, $ht, $dim);
}

return HashTableHelper::prepareStringKeyWrite($this->context, $ht, $key);
}
if ('_FILES' === $container->superglobalName && !$forWrite) {
Expand Down Expand Up @@ -1153,6 +1162,10 @@ public function dimFetch(
// must return the live child HT so the inner write persists (#24011; string keys
// already branch on TYPE_ARRAY above — zend_execute.c ZEND_FETCH_DIM_W).
if ($forWrite && (null === $expectedType || Type::TYPE_ARRAY !== $expectedType->type)) {
if ($warnUndefinedKeyForIncDec) {
HashTableHelper::emitUndefinedArrayKeyWarningIfMissing($this->context, $ht, $dim);
}

return HashTableHelper::prepareIndexWrite($this->context, $ht, $index);
}
if ($forWrite && null !== $expectedType && Type::TYPE_ARRAY === $expectedType->type) {
Expand Down Expand Up @@ -1211,7 +1224,7 @@ public function dimFetch(
);
$htVar->borrowedHashtable = true;

return $htVar->dimFetch($dim, $expectedType, $forWrite);
return $htVar->dimFetch($dim, $expectedType, $forWrite, $emitFloatKeyDeprecation, $warnUndefinedKeyForIncDec);
default:
if (!($this->type & self::IS_NATIVE_ARRAY)) {
throw new \LogicException("Unsupported dim fetch on " . self::getStringType($this->type));
Expand Down
7 changes: 6 additions & 1 deletion lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -5385,7 +5385,12 @@ static function () use ($arg3, $writeTarget, $dnfCtx, $strict): void {
}
$table = $container->toArray();
try {
if (!$forWrite && !$fetchIs && !$table->keyExists($arg3, false, $frame, false)) {
// ++/-- FETCH_DIM_W: warn on missing key then treat as null (#30078, zend_vm_def.h).
$forIncDec = $forWrite && $this->propertyFetchDestUsedAsIncDec($frame, $op);
if (
(!$forWrite && !$fetchIs || $forIncDec)
&& !$table->keyExists($arg3, false, $frame, false)
) {
$this->context->errors->undefinedArrayKey(
$arg3,
$this->context,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
--TEST--
Language: $a[missing]++ emits Undefined array key Warning then stores 1 (#30078, zend_vm_def.h)
--FILE--
<?php
function warn_capture(int $errno, string $message): bool
{
echo 'W:', $message, "\n";

return true;
}
set_error_handler('warn_capture');

$x = [];
$x[0]++;
echo "done\n";
var_export($x);
echo "\n";

$f = false;
$f[0]++;
var_export($f);
echo "\n";
--EXPECT--
W:Undefined array key 0
done
array (
0 => 1,
)
W:Automatic conversion of false to array is deprecated
W:Undefined array key 0
array (
0 => 1,
)
Loading