apple/pkl · error

charIndexOutOfRange

charIndexOutOfRange

Error message

charIndexOutOfRange

What it means

String.substring(start, exclusiveEnd) takes code-point offsets. This error is thrown when the `start` offset does not fall on a code-point boundary or is beyond the string's code-point count, i.e. the start index is out of the valid range 0..length. The error message includes the offending start, the valid maximum, and the string itself.

Source

Thrown at pkl-core/src/main/java/org/pkl/core/stdlib/base/StringNodes.java:241

      var charIndex = VmUtils.codePointOffsetToCharOffset(self, index);
      if (charIndex == -1 || charIndex == self.length()) return VmNull.withoutDefault();

      if (Character.isHighSurrogate(self.charAt(charIndex))
          && charIndex < self.length() - 1
          && Character.isLowSurrogate(self.charAt(charIndex + 1))) {
        return self.substring(charIndex, charIndex + 2);
      }
      return self.substring(charIndex, charIndex + 1);
    }
  }

  public abstract static class substring extends ExternalMethod2Node {
    @TruffleBoundary
    @Specialization
    protected String eval(String self, long start, long exclusiveEnd) {
      var charStart = VmUtils.codePointOffsetToCharOffset(self, start);
      if (charStart == -1) {
        throw exceptionBuilder()
            .evalError("charIndexOutOfRange", start, 0, self.codePointCount(0, self.length()))
            .withProgramValue("String", self)
            .build();
      }

      var charExclusiveEnd =
          VmUtils.codePointOffsetToCharOffset(self, exclusiveEnd - start, charStart);
      if (charExclusiveEnd < charStart) {
        throw exceptionBuilder()
            .evalError(
                "charIndexOutOfRange", exclusiveEnd, start, self.codePointCount(0, self.length()))
            .withProgramValue("String", self)
            .build();
      }

      return self.substring(charStart, charExclusiveEnd);
    }
  }

View on GitHub (pinned to f3efcbfc9b)

Solutions

  1. Validate `0 <= start <= str.length` (Pkl code-point length) before calling substring.
  2. Use take/drop, takeLast/dropLast or substringOrNull when bounds may be uncertain — they clamp or return null instead of throwing.
  3. Compute offsets with code-point-aware APIs (e.g. indexOf results) rather than byte/UTF-16 indices.
  4. If input is external, sanitize/trim it first so offsets derive from the final string.

Example fix

// before
name.substring(start, start + 3) // start may exceed length
// after
name.substringOrNull(start, start + 3) ?? "" // null-safe slice
Defensive patterns

Strategy: validation

Validate before calling

// Pkl
function validStart(s: String, start: Int): Boolean = start >= 0 && start <= s.length
// call site
if (validStart(str, start)) str.substringOrNull(start, end) else ""

Type guard

function inRange(i: Int, len: Int): Boolean = i >= 0 && i <= len

Try / catch

try { str.substring(start, end) } catch (e: PklError) { fallbackSlice(str, start, end) }

Prevention

When it happens

Trigger: Calling substring with start < 0, start > string length, or start landing inside a surrogate pair (slicing computed from UTF-16/byte indices instead of code points, e.g. after using .length on a UTF-16 view of the value).

Common situations: Slicing strings containing emoji or other astral characters using byte or UTF-16 positions; off-by-one loops computing start from endIndex of a previous substring; config-driven offsets that exceed the actual value length.

Related errors


AI-assisted analysis of apple/pkl@f3efcbfc9b (2026-09-08). Data as JSON: /api/errors/efb19c621b306751. Report an issue: GitHub.