paragonie/random_compat · error · TypeError
RandomCompat_substr(): Second argument should be an integer
Error message
RandomCompat_substr(): Second argument should be an integer
What it means
RandomCompat_substr()'s second parameter $start must be an integer byte offset. When it is not (float, string numeric like "5", null, bool), the function throws TypeError. This strictness exists because float/string offsets would silently truncate or coerce in native substr and break the deterministic byte-slicing random_compat relies on.
Solutions
- Cast explicitly with (int) $start once you have confirmed the value is numeric, e.g. $start = (int) $rawStart.
- Use is_int($start) validation at your boundary and reject or coerce non-ints.
- If the value derives from float math, round intentionally: $start = (int) floor($x).
- Provide defaults for optional values: $start = $opts['start'] ?? 0.
Example fix
// before $start = $data['offset']; // string "5" from JSON RandomCompat_substr($str, $start); // after $start = (int) $data['offset']; RandomCompat_substr($str, $start);
Defensive patterns
Strategy: validation
Validate before calling
$start = $opts['start'] ?? 0;
if (!is_int($start)) {
$start = (int) $start; // only safe if numeric; validate first if untrusted
}
RandomCompat_substr($str, $start); Type guard
function isIntOffset($value): bool {
return is_int($value) && $value >= 0;
}
if (isIntOffset($start)) {
$chunk = RandomCompat_substr($str, $start);
} Try / catch
try {
$chunk = RandomCompat_substr($str, $start);
} catch (TypeError $e) {
// $start was not an int; coerce or fail
$chunk = RandomCompat_substr($str, (int) $start);
} Prevention
- Cast config/JSON-sourced offsets with (int) before use.
- Remember ceil()/floor() return floats — cast after rounding.
- Provide ?? 0 defaults for optional offset values.
- Enable strict_types to surface float/int confusion at call time.
When it happens
Trigger: Calling RandomCompat_substr($str, $start) where $start came from float math (e.g. strlen()/2), from a string config value, from JSON with a numeric string, or is null because an optional parameter was never set. Also triggered when using random_int()'s output stored through a middleware that casted to string.
Common situations: Dividing lengths to find a midpoint produces a float (32/2 is int in PHP, but ceil()/floor() results are floats); config files (YAML/JSON/env) providing "0" as a string; null from optional array keys accessed without defaults; database columns typed as decimal.
Understand the failure class
Background: "must be a positive integer", "cannot be empty", "invalid argument": how invalid-argument errors work across open-source libraries — this error's family across 33 libraries.
Related errors
- RandomCompat_substr(): Third argument should be an integer…
- RandomCompat_substr(): First argument should be a string
- RandomCompat_strlen() expects a string
- Expected an integer.
- random_bytes(): $bytes must be an integer
AI-assisted analysis of paragonie/random_compat@b5d188cc9d (2026-09-13).
Data as JSON: /api/errors/3218e2bd3020ff88.
Report an issue: GitHub.
Appendix: source
Thrown at lib/byte_safe_strings.php:112
*
* @param string $binary_string
* @param int $start
* @param int|null $length (optional)
*
* @throws TypeError
*
* @return string
*/
function RandomCompat_substr($binary_string, $start, $length = null)
{
if (!is_string($binary_string)) {
throw new TypeError(
'RandomCompat_substr(): First argument should be a string'
);
}
if (!is_int($start)) {
throw new TypeError(
'RandomCompat_substr(): Second argument should be an integer'
);
}
if ($length === null) {
/**
* mb_substr($str, 0, NULL, '8bit') returns an empty string on
* PHP 5.3, so we have to find the length ourselves.
*/
/** @var int $length */
$length = RandomCompat_strlen($binary_string) - $start;
} elseif (!is_int($length)) {
throw new TypeError(
'RandomCompat_substr(): Third argument should be an integer, or omitted'
);
}
// Consistency with PHP's behaviorView on GitHub (pinned to b5d188cc9d)