paragonie/random_compat · error · TypeError

RandomCompat_substr(): Third argument should be an integer…

Error message

RandomCompat_substr(): Third argument should be an integer, or omitted

What it means

RandomCompat_substr()'s third parameter $length must be an integer or omitted (null, meaning 'rest of string'). When a non-int, non-null value (float, numeric string, bool) is supplied, the function throws TypeError. Note the library computes $length itself when null, because mb_substr($str, 0, NULL, '8bit') returns '' on PHP 5.3 — so passing null is always safe and correct.

Solutions

  1. Cast deliberately: $length = (int) $len after verifying it is numeric.
  2. Use round()/floor() then cast when deriving lengths from division: $length = (int) ceil($size / 2).
  3. Omit the parameter entirely (pass null) to mean 'to end of string' — the library computes it safely.
  4. Validate with is_int($length) at the caller boundary before invoking.

Example fix

// before
$length = ceil(strlen($str) / 2); // float
$half = RandomCompat_substr($str, 0, $length);
// after
$length = (int) ceil(RandomCompat_strlen($str) / 2);
$half = RandomCompat_substr($str, 0, $length);
Defensive patterns

Strategy: validation

Validate before calling

if ($length !== null && !is_int($length)) {
    $length = (int) $length; // verify numeric-ness first for untrusted input
}
$part = RandomCompat_substr($str, $start, $length);

Type guard

function isValidLength($value): bool {
    return $value === null || is_int($value);
}
$part = isValidLength($length)
    ? RandomCompat_substr($str, $start, $length)
    : RandomCompat_substr($str, $start);

Try / catch

try {
    $part = RandomCompat_substr($str, $start, $length);
} catch (TypeError $e) {
    // non-int length; fall back to rest-of-string
    $part = RandomCompat_substr($str, $start);
}

Prevention

When it happens

Trigger: Calling RandomCompat_substr($str, $start, $length) where $length is a float from arithmetic (e.g. ceil(strlen($s)/2)), a numeric string from config/JSON ("16"), or a bool from a failed comparison. Only the elseif branch fires — $start was already a valid int.

Common situations: Computing half-lengths or chunk sizes with division producing floats; JSON/env config delivering lengths as strings; passing true/false from a conditional expression by accident; older PHP returning floats from certain math functions.

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


AI-assisted analysis of paragonie/random_compat@b5d188cc9d (2026-09-13). Data as JSON: /api/errors/060346f8c88e6f04. Report an issue: GitHub.

Appendix: source

Thrown at lib/byte_safe_strings.php:125

                    '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 behavior
            if ($start === RandomCompat_strlen($binary_string) && $length === 0) {
                return '';
            }
            if ($start > RandomCompat_strlen($binary_string)) {
                return '';
            }

            return (string) mb_substr(
                (string) $binary_string,
                (int) $start,
                (int) $length,
                '8bit'
            );

View on GitHub (pinned to b5d188cc9d)