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
3 changes: 2 additions & 1 deletion ext/standard/JitSessionLifecycleKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,8 @@ private static function implementStandaloneRuntime(Context $context): void
$context->builder->branchIf($isActive, $bbInactive, $bbCheckHeaders);

$context->builder->positionAtEnd($bbInactive);
SessionStart::emitWriteBool($context, $outPtr, false);
// php-src: session already active → true (+ E_NOTICE); not false.
SessionStart::emitWriteBool($context, $outPtr, true);
$context->builder->branch($bbDone);

$context->builder->positionAtEnd($bbCheckHeaders);
Expand Down
7 changes: 4 additions & 3 deletions ext/standard/JitSessionStorageKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -303,7 +303,6 @@ private static function implementEmitSetcookieBridge(Context $context, LlvmFunct

$i64 = $context->getTypeFromString('int64');
$i32 = $context->getTypeFromString('int32');
$strPtr = $context->getTypeFromString('__string__*');
$idLen = $context->builder->load(SessionStorageGlobals::$idLenGlobal);
$hasId = $context->builder->icmp(Builder::INT_SGT, $idLen, $i64->constInt(0, false));
$bbDone = BasicBlockHelper::append($context, 'ss_setcookie_bridge_done');
Expand All @@ -318,16 +317,18 @@ private static function implementEmitSetcookieBridge(Context $context, LlvmFunct
);
$valueStr = self::bufferToString($context, SessionStorageGlobals::$idBufGlobal, $idLen);
$pathStr = self::literalString($context, '/');
// NestedJIT PendingHeadersJitHelper::addSetcookie expects string, not null (#21892).
$emptyStr = self::literalString($context, '');
$context->builder->call(
$context->lookupFunction('__phpc_setcookie_add'),
$nameStr,
$valueStr,
$i64->constInt(0, false),
$pathStr,
$strPtr->constNull(),
$emptyStr,
$i32->constInt(0, false),
$i32->constInt(0, false),
$strPtr->constNull(),
$emptyStr,
$i32->constInt(0, false)
);
$context->builder->branch($bbDone);
Expand Down
78 changes: 66 additions & 12 deletions ext/standard/SessionCreateIdJitHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,32 +4,86 @@

namespace PHPCompiler\ext\standard;

use PHPCompiler\ext\session\SessionFileStorage;

/**
* session_create_id() semantics for compiled JIT/AOT modules (#9500, php-in-PHP).
*
* SSOT: {@see VmSession::createId}
* php-src: ext/session/session.c — php_session_create_id
* Nested-JIT must not call {@see VmSession::createId()} — static method return is
* mis-lowered to the VmSession class object (AOT abort in randomIdString; #21892).
* Keep generate/bin_to_readable logic here using {@see random_bytes()} (#1974).
*
* php-src: ext/session/session.c — php_session_create_id / bin_to_readable
*/
final class SessionCreateIdJitHelper
{
/** Default php.ini session.sid_length / session.sid_bits_per_character (#10864). */
private const SID_LENGTH = 26;

private const SID_BITS_PER_CHAR = 5;

/** php-src bin_to_readable alphabet (64 glyphs). */
private const BIN_MAP = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,-';

public static function randomIdString(): string
{
$result = VmSession::createId(null);
if (!\is_string($result)) {
throw new \LogicException('session random id must be string (#9500)');
}

return $result;
return self::generateId();
}

/** @return string|null null when php-src session_create_id() would return false */
public static function createIdNullable(?string $prefix): ?string
{
$result = VmSession::createId($prefix);
if (false === $result) {
return null;
if (null !== $prefix && '' !== $prefix) {
if (\strlen($prefix) > VmSession::MAX_ID_LEN) {
throw new \ValueError(
'session_create_id(): Argument #1 ($prefix) cannot be longer than '
.VmSession::MAX_ID_LEN.' characters'
);
}
if ($prefix !== SessionFileStorage::sanitizeId($prefix)) {
return null;
}
}
$generated = self::generateId();
if (null === $prefix || '' === $prefix) {
return $generated;
}

return $prefix.$generated;
}

private static function generateId(): string
{
return self::binToReadable(
\random_bytes(self::SID_LENGTH),
self::SID_LENGTH,
self::SID_BITS_PER_CHAR
);
}

/** php-src ext/session/session.c bin_to_readable(). */
private static function binToReadable(string $bytes, int $outLength, int $bitsPerChar): string
{
$map = self::BIN_MAP;
$out = '';
$byteLen = \strlen($bytes);
$p = 0;
$w = 0;
$have = 0;
$mask = (1 << $bitsPerChar) - 1;
for ($i = 0; $i < $outLength; ++$i) {
while ($have < $bitsPerChar) {
if ($p >= $byteLen) {
break;
}
$w |= (\ord($bytes[$p++]) << $have);
$have += 8;
}
$out .= $map[$w & $mask];
$w >>= $bitsPerChar;
$have -= $bitsPerChar;
}

return $result;
return $out;
}
}
19 changes: 5 additions & 14 deletions lib/JIT/Builtin/SessionStart.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,21 +26,12 @@ public static function implement(Context $context): void

public static function emitWriteBool(Context $context, Value $outPtr, bool $value): void
{
$valMap = $context->structFieldMap['__value__'];
$i8 = $context->getTypeFromString('int8');
$i32 = $context->getTypeFromString('int32');
$i64 = $context->getTypeFromString('int64');
$context->builder->store(
$i8->constInt(Variable::TYPE_NATIVE_BOOL, false),
$context->builder->structGep($outPtr, $valMap['type'])
);
$valueField = $context->builder->structGep($outPtr, $valMap['value']);
$firstByte = $context->builder->inBoundsGEP(
$valueField,
$i32->constInt(0, false),
$i64->constInt(0, false)
// Use the canonical value-box ABI (same as JitValueBox::writeBool) — #21892.
$context->builder->call(
$context->lookupFunction('__value__writeBool'),
$outPtr,
$context->getTypeFromString('int32')->constInt($value ? 1 : 0, false)
);
$context->builder->store($i8->constInt($value ? 1 : 0, false), $firstByte);
}

/**
Expand Down
26 changes: 24 additions & 2 deletions lib/JIT/JitValueBox.php
Original file line number Diff line number Diff line change
Expand Up @@ -541,13 +541,18 @@ private static function copyBetweenPointers(Context $context, Value $destPtr, Va
$context->builder->branchIf($isBool, $boolBlock, $afterBool);

$context->builder->positionAtEnd($boolBlock);
$boolLong = $context->builder->call($context->lookupFunction('__value__readLong'), $srcPtr);
// __value__readLong has no TYPE_NATIVE_BOOL arm (returns 0) — #21892 / JitZendScalarCast.
$boolByte = self::readBoolByte($context, $srcPtr);
$i32 = $context->getTypeFromString('int32');
$context->builder->call(
$context->lookupFunction('__value__writeBool'),
$destPtr,
$context->builder->zExt(
$context->builder->truncOrBitCast($boolLong, $context->getTypeFromString('int1')),
$context->builder->icmp(
Builder::INT_NE,
$boolByte,
$context->getTypeFromString('int8')->constInt(0, false)
),
$i32
)
);
Expand Down Expand Up @@ -576,6 +581,23 @@ private static function copyBetweenPointers(Context $context, Value $destPtr, Va
BasicBlockHelper::branchToFreshContinue($context, 'after_value_copy_'.$tag);
}

/**
* Read boxed bool payload (writeBool stores int8 at value[0]).
* Do not use {@see __value__readLong} — no NATIVE_BOOL arm (#21892).
*/
public static function readBoolByte(Context $context, Value $valuePtr): Value
{
$valuePtr = self::normalizeValuePtr($context, $valuePtr);
$map = $context->structFieldMap['__value__'];
$i8 = $context->getTypeFromString('int8');
$bytePtr = $context->builder->pointerCast(
$context->builder->structGep($valuePtr, $map['value']),
$i8->pointerType(0)
);

return $context->builder->load($bytePtr);
}

public static function writeBool(Context $context, Value $slot, Value $bool): void
{
$map = $context->structFieldMap['__value__'];
Expand Down
7 changes: 4 additions & 3 deletions test/unit/SessionCreateIdRuntimeShrinkTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,17 @@ public function testSessionCreateIdRuntimeUsesJitHelperNotLlvmEntropy(): void
public function testSessionCreateIdJitHelperMatchesVmSession(): void
{
$id = SessionCreateIdJitHelper::randomIdString();
$this->assertSame(32, \strlen($id));
$this->assertMatchesRegularExpression('/^[0-9a-f]{32}$/', $id);
// php-src defaults: session.sid_length=26, sid_bits_per_character=5 (#10864).
$this->assertSame(26, \strlen($id));
$this->assertMatchesRegularExpression('/^[0-9a-zA-Z,-]{26}$/', $id);

$prefixed = SessionCreateIdJitHelper::createIdNullable('app-');
$this->assertIsString($prefixed);
$this->assertStringStartsWith('app-', $prefixed);

$vm = VmSession::createId('app-');
$this->assertIsString($vm);
$this->assertSame(36, \strlen($vm));
$this->assertSame(30, \strlen($vm));
}

public function testSessionCreateIdJitHelperReturnsNullOnInvalidPrefix(): void
Expand Down