paragonie/random_compat · error · TypeError

RandomCompat_substr(): First argument should be a string

Error message

RandomCompat_substr(): First argument should be a string

What it means

RandomCompat_substr() is a binary-safe replacement for substr($str, $start, $length) used by random_compat internals. Its first parameter must be a binary string; when anything else (int, null, bool, array, object) is passed, the function throws TypeError immediately rather than relying on PHP's silent coercion, which would hide bugs in byte-level string slicing.

Solutions

  1. Check is_string($input) before calling, and handle the false/null error case of whatever produced the value.
  2. If the value is a Stringable object, call (string) $obj explicitly.
  3. Verify argument order — first argument is the string, second the int start offset.
  4. Prefer public API random_bytes()/random_int(); RandomCompat_* internals are not part of the supported surface.

Example fix

// before
$chunk = RandomCompat_substr($fileData, 0, 32); // $fileData = file_get_contents() returning false
// after
$fileData = file_get_contents($path);
if (!is_string($fileData)) {
    throw new RuntimeException('Could not read file');
}
$chunk = RandomCompat_substr($fileData, 0, 32);
Defensive patterns

Strategy: type-guard

Validate before calling

if (!is_string($binaryString)) {
    throw new InvalidArgumentException(
        'RandomCompat_substr first argument must be a string, got ' . gettype($binaryString)
    );
}
$chunk = RandomCompat_substr($binaryString, $start, $length);

Type guard

function canSubstring($value, $start, $length = null): bool {
    return is_string($value) && is_int($start) && ($length === null || is_int($length));
}
if (canSubstring($input, 0, 16)) {
    $chunk = RandomCompat_substr($input, 0, 16);
}

Try / catch

try {
    $chunk = RandomCompat_substr($input, $start, $length);
} catch (TypeError $e) {
    throw new InvalidArgumentException('Invalid substr input: ' . $e->getMessage(), 0, $e);
}

Prevention

When it happens

Trigger: Calling RandomCompat_substr($value, ...) where $value is not a string — e.g. passing the result of random_bytes() through a function that returned false on error, passing null from an uninitialized variable, or passing an integer that you intended as a length/offset by mistake (wrong argument order).

Common situations: Mishandled error returns: calling substr-style helpers on the result of a function that can return false/null (file reads, DB fetches). Swapped arguments: RandomCompat_substr($start, $string). Frameworks/HALs returning objects (e.g. Stringable wrappers) rather than raw strings. JSON decoding yielding numbers where strings were expected.

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/86192fa0cc5d5eea. Report an issue: GitHub.

Appendix: source

Thrown at lib/byte_safe_strings.php:106

    ) {
        /**
         * substr() implementation that isn't brittle to mbstring.func_overload
         *
         * This version uses mb_substr() in '8bit' mode to treat strings as raw
         * binary rather than UTF-8, ISO-8859-1, etc
         *
         * @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)) {

View on GitHub (pinned to b5d188cc9d)