From 842419af3d19e3625e4799ecd54298ff2a3aa10a Mon Sep 17 00:00:00 2001 From: Matt Lantz Date: Fri, 6 Nov 2026 14:36:15 -0500 Subject: [PATCH] Fix GH-13243: intval() does not detect the "0o" octal prefix intval() with base 0 already detects the "0b" binary prefix (and strtol() handles "0x"), but the explicit octal prefix "0o"/"0O" introduced in PHP 8.1 was not recognized, so intval("0o17", 0) and intval("0o17", 8) returned 0 while the literal 0o17 and octdec("0o17") give 15. Generalize the existing "0b" prefix handling to also detect "0o"/"0O" when base is 0 or 8. Numeric strings and (int) casts are unaffected, as specified by the explicit octal notation RFC. --- UPGRADING | 4 + .../tests/general_functions/gh13243.phpt | 105 ++++++++++++++++++ ext/standard/type.c | 24 ++-- 3 files changed, 126 insertions(+), 7 deletions(-) create mode 100644 ext/standard/tests/general_functions/gh13243.phpt diff --git a/UPGRADING b/UPGRADING index a1e30018cd69..5202d754d67b 100644 --- a/UPGRADING +++ b/UPGRADING @@ -106,6 +106,10 @@ PHP 8.7 UPGRADE NOTES ======================================== - Standard: + . intval() now recognizes the explicit octal prefix "0o"/"0O" when $base is + 0 or 8, consistent with octal integer literals and octdec(). For example, + intval("0o17", 0) now returns 15 instead of 0. Numeric strings and + (int) casts are unaffected. . stream_select() no longer fails once a watched descriptor exceeds FD_SETSIZE. The descriptor set now grows on demand on both POSIX (bitset) and Windows (winsock fd_set), so code watching many streams works without diff --git a/ext/standard/tests/general_functions/gh13243.phpt b/ext/standard/tests/general_functions/gh13243.phpt new file mode 100644 index 000000000000..878c82f110ca --- /dev/null +++ b/ext/standard/tests/general_functions/gh13243.phpt @@ -0,0 +1,105 @@ +--TEST-- +GH-13243 (intval() does not detect the explicit octal "0o" prefix) +--FILE-- + +--EXPECT-- +--- Good Inputs - Base = 0 --- +int(15) +int(15) +int(15) +int(-15) +int(15) +int(-15) +int(0) +int(0) +int(0) +int(7) +int(7) +bool(true) +bool(true) +--- Good Inputs - Base = 8 --- +int(15) +int(15) +int(15) +int(-15) +int(15) +int(-15) +int(0) +int(0) +int(0) +int(7) +int(7) +bool(true) +bool(true) +--- Bad Inputs - Base = 0 --- +int(0) +int(0) +int(0) +int(0) +int(0) +--- Prefix not applied for other bases or casts --- +int(0) +int(0) +int(0) +int(0) +int(0) +--- Existing prefixes unchanged --- +int(-5) +int(0) +int(-31) +int(-15) +int(15) diff --git a/ext/standard/type.c b/ext/standard/type.c index 2755f08d88da..0927c36bc327 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -151,7 +151,8 @@ PHP_FUNCTION(intval) } - if (base == 0 || base == 2) { + /* strtol() handles the "0x" prefix itself, but not "0b" or "0o" */ + if (base == 0 || base == 2 || base == 8) { char *strval = Z_STRVAL_P(num); size_t strlen = Z_STRLEN_P(num); @@ -167,24 +168,33 @@ PHP_FUNCTION(intval) offset = 1; } - if (strval[offset] == '0' && (strval[offset + 1] == 'b' || strval[offset + 1] == 'B')) { + int prefix_base = 0; + if (strval[offset] == '0') { + if (strval[offset + 1] == 'b' || strval[offset + 1] == 'B') { + prefix_base = 2; + } else if (strval[offset + 1] == 'o' || strval[offset + 1] == 'O') { + prefix_base = 8; + } + } + + if (prefix_base != 0 && (base == 0 || base == prefix_base)) { if (strval[0] != '-') { - /* Either "+0b", or "0b" */ - RETURN_LONG(ZEND_STRTOL(strval + 2 + offset, NULL, 2)); + /* Either "+0b"/"+0o", or "0b"/"0o" */ + RETURN_LONG(ZEND_STRTOL(strval + 2 + offset, NULL, prefix_base)); } char *tmpval; - strlen -= 2; /* Removing "0b" */ + strlen -= 2; /* Removing "0b"/"0o" */ tmpval = emalloc(strlen + 1); /* Place the unary symbol at pos 0 */ tmpval[0] = '-'; - /* Copy the data from after "0b" to the end of the buffer */ + /* Copy the data from after the prefix to the end of the buffer */ memcpy(tmpval + offset, strval + offset + 2, strlen - offset); tmpval[strlen] = 0; - RETVAL_LONG(ZEND_STRTOL(tmpval, NULL, 2)); + RETVAL_LONG(ZEND_STRTOL(tmpval, NULL, prefix_base)); efree(tmpval); return; }