oracle/graal · error · IllegalArgumentException

hashcode must be > 0

Error message

hashcode must be > 0

What it means

IllegalArgumentException from EspressoExternalConstantReflectionProvider.makeIdentityHashCode: the requested identity-hashcode value was <= 0. The JVM only ever assigns positive identity hashcodes, and the underlying VM helper (invokeJVMCIHelper("makeIdentityHashCode", ...)) requires a positive value, so the reflection provider validates before crossing into the VM. This method is the JVMCI bridge behind IdentityHashCodes.set during continuation deserialization.

Source

Thrown at espresso-compiler-stub/src/com.oracle.truffle.espresso.vmaccess/src/com/oracle/truffle/espresso/vmaccess/EspressoExternalConstantReflectionProvider.java:351

            throw new IllegalArgumentException("Constant has unexpected type " + constant.getClass() + ": " + constant);
        }
        return objectConstant.guestHashCode();
    }

    @Override
    public int makeIdentityHashCode(JavaConstant constant, int requestedValue) {
        JavaKind kind = Objects.requireNonNull(constant).getJavaKind();
        if (kind != JavaKind.Object) {
            throw new IllegalArgumentException("Constant has unexpected kind " + kind + ": " + constant);
        }
        if (constant.isNull()) {
            throw new NullPointerException();
        }
        if (!(constant instanceof EspressoExternalObjectConstant objectConstant)) {
            throw new IllegalArgumentException("Constant has unexpected type " + constant.getClass() + ": " + constant);
        }
        if (requestedValue <= 0) {
            throw new IllegalArgumentException("hashcode must be > 0");
        }
        return access.invokeJVMCIHelper("makeIdentityHashCode", objectConstant.getValue(), requestedValue).asInt();
    }

    @Override
    public AbstractEspressoResolvedInstanceType getTypeForStaticBase(JavaConstant staticBase) {
        if (!(staticBase instanceof EspressoExternalObjectConstant objectConstant)) {
            return null;
        }
        Value type = access.invokeJVMCIHelper("getTypeForStaticBase", objectConstant.getValue());
        if (type.isNull()) {
            return null;
        }
        return new EspressoExternalResolvedInstanceType(access, type);
    }
}

View on GitHub (pinned to a66e9ccd1d)

Solutions

  1. Pass a strictly positive int; validate requestedValue > 0 at the call site before invoking the provider.
  2. Fix the persistence encoding so recorded hashcodes round-trip as unsigned/positive 31-bit ints.
  3. If no hashcode was recorded for the object, skip the call entirely instead of passing 0.
  4. Check for integer sign-extension bugs when the recorded value traveled through smaller integer types.

Example fix

// before
provider.makeIdentityHashCode(constant, (short) recordedHash); // sign-extended, can be <= 0

// after
if (recordedHash > 0) {
    provider.makeIdentityHashCode(constant, recordedHash);
} // else: no recorded hashcode, let the VM assign one
Defensive patterns

Strategy: validation

Validate before calling

if (requestedValue <= 0) {
    throw new IllegalArgumentException("requestedValue must be a positive int, got " + requestedValue);
}
provider.makeIdentityHashCode(constant, requestedValue);

Prevention

When it happens

Trigger: Calling makeIdentityHashCode(constant, 0), with a negative value, or with a recorded hashcode that was decoded/sign-truncated to <= 0 (e.g. stored in a signed byte/short field somewhere in the persistence layer).

Common situations: Identity hashcodes persisted through a channel that cannot represent the full positive int range (ProtoBuf zigzag mishandling, JSON as signed int16); defaulting the 'requested value' parameter to 0 when no recording exists.

Related errors


AI-assisted analysis of oracle/graal@a66e9ccd1d (2026-08-14). Data as JSON: /api/errors/f6e0293d2c9719bb. Report an issue: GitHub.