1

I wonder about how JVM or JDK runs exceptions. For example if we have array

int tab[] = {1, 2, 3};

and we try to access

tab[10]

JVM run ArrayIndexOutOfBoundsException it's ok. Is tt possible to see in which part of JDK or JVM execution of this exception? I mean throw new ArrayIndexOutOfBoundsException() or something like this? Is it impossible from the call for JNI calls?

Pushpesh Kumar Rajwanshi
  • 18,127
  • 2
  • 19
  • 36
lukassz
  • 3,135
  • 7
  • 32
  • 72

2 Answers2

1

ArrayIndexOutOfBoundsException is thrown by JVM. Bytecode does not check array index:

    int tab[] = {1};
    int x = tab[2];

bytecode for these 2 lines:

    ICONST_1
    NEWARRAY T_INT
    DUP
    ICONST_0
    ICONST_1
    IASTORE
    ASTORE 2
   L3
    LINENUMBER 26 L3
    ALOAD 2
    ICONST_1
    IALOAD   <-- we read element 1 which is out of bounds
    ISTORE 3

as you can see there is no index check, no exception thrown. It means it is done by JVM

Evgeniy Dorofeev
  • 133,369
  • 30
  • 199
  • 275
1

First, see this answer for a simple overview of how exceptions work in HotSpot JVM.

As to JVM code that throws ArrayIndexOutOfBoundsException, there are multiple places to see:

  1. Interpreter. The implementation of iaload bytecode in the interpreter (as well as other array access bytecodes) includes index check. If the check fails, the interpreter jumps to exception throwing stub. See templateTable_x86.cpp:

    void TemplateTable::iaload() {
      transition(itos, itos);
      // rax: index
      // rdx: array
      index_check(rdx, rax); // kills rbx                              --------------
      __ access_load_at(T_INT, IN_HEAP | IS_ARRAY, rax,                             |
                        Address(rdx, rax, Address::times_4,                         |
                                arrayOopDesc::base_offset_in_bytes(T_INT)),         |
                        noreg, noreg);                                              |
    }                                                                               |
                                                                                    |
    void TemplateTable::index_check(Register array, Register index) {             <--
      // Pop ptr into array                                                          
      __ pop_ptr(array);                                                             
      index_check_without_pop(array, index);                           --------------
    }                                                                               |
                                                                                    |
    void TemplateTable::index_check_without_pop(Register array, Register index) { <--
      // destroys rbx
      // check array
      __ null_check(array, arrayOopDesc::length_offset_in_bytes());
      // sign extend index for use by indexed load
      __ movl2ptr(index, index);
      // check index
      __ cmpl(index, Address(array, arrayOopDesc::length_offset_in_bytes()));
      if (index != rbx) {
        // ??? convention: move aberrant index into rbx for exception message
        assert(rbx != array, "different registers");
        __ movl(rbx, index);
      }
      Label skip;
      __ jccb(Assembler::below, skip);
      // Pass array to create more detailed exceptions.
      __ mov(NOT_LP64(rax) LP64_ONLY(c_rarg1), array);
      __ jump(ExternalAddress(Interpreter::_throw_ArrayIndexOutOfBoundsException_entry));  !!!
      __ bind(skip);
    }
    
  2. JIT compilers (C1 and C2). When a method is JIT-compiled, the compiler includes a similar index check sequence in the produced machine code. Sometimes, when a compiler can prove that no out-of-bounds condition may happen, it eliminates the redundant check.

    For example, C1 compiler first emits platform-independent range check in the intermediate representation, see c1_LIRGenerator.cpp:

    void LIRGenerator::array_range_check(LIR_Opr array, LIR_Opr index,
                                        CodeEmitInfo* null_check_info, CodeEmitInfo* range_check_info) {
      CodeStub* stub = new RangeCheckStub(range_check_info, index, array);
      if (index->is_constant()) {
        cmp_mem_int(lir_cond_belowEqual, array, arrayOopDesc::length_offset_in_bytes(),
                    index->as_jint(), null_check_info);
        __ branch(lir_cond_belowEqual, T_INT, stub); // forward branch
      } else {
        cmp_reg_mem(lir_cond_aboveEqual, index, array,
                    arrayOopDesc::length_offset_in_bytes(), T_INT, null_check_info);
        __ branch(lir_cond_aboveEqual, T_INT, stub); // forward branch
      }
    }
    

    Then during code generation RangeCheckStub is expanded to platform-dependent assembly which includes a jump to exception throwing stub, see c1_CodeStubs_x86.cpp:

      if (_throw_index_out_of_bounds_exception) {
        stub_id = Runtime1::throw_index_exception_id;
      } else {
        stub_id = Runtime1::throw_range_check_failed_id;
        ce->store_parameter(_array->as_pointer_register(), 1);
      }
      __ call(RuntimeAddress(Runtime1::entry_for(stub_id)));
    

    Finally this results in calling Exceptions::_throw function in JVM C++ code.

apangin
  • 92,924
  • 10
  • 193
  • 247