diff options
| author | 2016-06-29 12:33:23 +0000 | |
|---|---|---|
| committer | 2016-06-29 12:33:23 +0000 | |
| commit | fdbc39551e08fa6a17e1b4bf6904860ccc052aa2 (patch) | |
| tree | 0ade9d5225b067f4d0369e5369975c1e8d9f345f | |
| parent | 34bccc1d85787658a5d04d2caa52b6eeca18710f (diff) | |
| parent | e8e1127da3f154fae8d2eb16a94203544a182159 (diff) | |
Merge "Do checks on the fault address when we think it's an NPE."
| -rw-r--r-- | compiler/optimizing/nodes.h | 4 | ||||
| -rw-r--r-- | runtime/arch/arm/fault_handler_arm.cc | 13 | ||||
| -rw-r--r-- | runtime/arch/arm/quick_entrypoints_arm.S | 5 | ||||
| -rw-r--r-- | runtime/arch/arm64/fault_handler_arm64.cc | 13 | ||||
| -rw-r--r-- | runtime/arch/arm64/quick_entrypoints_arm64.S | 5 | ||||
| -rw-r--r-- | runtime/arch/mips/fault_handler_mips.cc | 13 | ||||
| -rw-r--r-- | runtime/arch/mips/quick_entrypoints_mips.S | 12 | ||||
| -rw-r--r-- | runtime/arch/mips64/fault_handler_mips64.cc | 14 | ||||
| -rw-r--r-- | runtime/arch/mips64/quick_entrypoints_mips64.S | 11 | ||||
| -rw-r--r-- | runtime/arch/x86/fault_handler_x86.cc | 19 | ||||
| -rw-r--r-- | runtime/arch/x86/quick_entrypoints_x86.S | 5 | ||||
| -rw-r--r-- | runtime/arch/x86_64/quick_entrypoints_x86_64.S | 5 | ||||
| -rw-r--r-- | runtime/common_throws.cc | 136 | ||||
| -rw-r--r-- | runtime/common_throws.h | 2 | ||||
| -rw-r--r-- | runtime/entrypoints/quick/quick_default_externs.h | 1 | ||||
| -rw-r--r-- | runtime/entrypoints/quick/quick_throw_entrypoints.cc | 12 | ||||
| -rw-r--r-- | runtime/fault_handler.h | 8 | ||||
| -rw-r--r-- | runtime/globals.h | 6 | ||||
| -rw-r--r-- | runtime/thread.h | 2 |
19 files changed, 256 insertions, 30 deletions
diff --git a/compiler/optimizing/nodes.h b/compiler/optimizing/nodes.h index 29df7c8ab8..6b2c33e668 100644 --- a/compiler/optimizing/nodes.h +++ b/compiler/optimizing/nodes.h @@ -5029,7 +5029,7 @@ class HInstanceFieldGet FINAL : public HExpression<1> { } bool CanDoImplicitNullCheckOn(HInstruction* obj) const OVERRIDE { - return (obj == InputAt(0)) && GetFieldOffset().Uint32Value() < kPageSize; + return (obj == InputAt(0)) && art::CanDoImplicitNullCheckOn(GetFieldOffset().Uint32Value()); } size_t ComputeHashCode() const OVERRIDE { @@ -5076,7 +5076,7 @@ class HInstanceFieldSet FINAL : public HTemplateInstruction<2> { } bool CanDoImplicitNullCheckOn(HInstruction* obj) const OVERRIDE { - return (obj == InputAt(0)) && GetFieldOffset().Uint32Value() < kPageSize; + return (obj == InputAt(0)) && art::CanDoImplicitNullCheckOn(GetFieldOffset().Uint32Value()); } const FieldInfo& GetFieldInfo() const { return field_info_; } diff --git a/runtime/arch/arm/fault_handler_arm.cc b/runtime/arch/arm/fault_handler_arm.cc index d81e0a9b96..d105c67d43 100644 --- a/runtime/arch/arm/fault_handler_arm.cc +++ b/runtime/arch/arm/fault_handler_arm.cc @@ -34,7 +34,7 @@ namespace art { -extern "C" void art_quick_throw_null_pointer_exception(); +extern "C" void art_quick_throw_null_pointer_exception_from_signal(); extern "C" void art_quick_throw_stack_overflow(); extern "C" void art_quick_implicit_suspend(); @@ -107,8 +107,10 @@ void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo ATTRIBUTE_UNUSED *out_return_pc = (sc->arm_pc + instr_size) | 1; } -bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED, - void* context) { +bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info, void* context) { + if (!IsValidImplicitCheck(info)) { + return false; + } // The code that looks for the catch location needs to know the value of the // ARM PC at the point of call. For Null checks we insert a GC map that is immediately after // the load/store instruction that might cause the fault. However the mapping table has @@ -122,7 +124,10 @@ bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIB uint32_t instr_size = GetInstructionSize(ptr); sc->arm_lr = (sc->arm_pc + instr_size) | 1; // LR needs to point to gc map location - sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception); + sc->arm_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal); + // Pass the faulting address as the first argument of + // art_quick_throw_null_pointer_exception_from_signal. + sc->arm_r0 = reinterpret_cast<uintptr_t>(info->si_addr); VLOG(signals) << "Generating null pointer exception"; return true; } diff --git a/runtime/arch/arm/quick_entrypoints_arm.S b/runtime/arch/arm/quick_entrypoints_arm.S index 27a41f09ad..0797def8e8 100644 --- a/runtime/arch/arm/quick_entrypoints_arm.S +++ b/runtime/arch/arm/quick_entrypoints_arm.S @@ -299,6 +299,11 @@ ONE_ARG_RUNTIME_EXCEPTION art_quick_deliver_exception, artDeliverExceptionFromCo NO_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception, artThrowNullPointerExceptionFromCode /* + * Call installed by a signal handler to create and deliver a NullPointerException. + */ +ONE_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception_from_signal, artThrowNullPointerExceptionFromSignal + + /* * Called by managed code to create and deliver an ArithmeticException. */ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_div_zero, artThrowDivZeroFromCode diff --git a/runtime/arch/arm64/fault_handler_arm64.cc b/runtime/arch/arm64/fault_handler_arm64.cc index 3e9ad0da62..f591fccde2 100644 --- a/runtime/arch/arm64/fault_handler_arm64.cc +++ b/runtime/arch/arm64/fault_handler_arm64.cc @@ -29,7 +29,7 @@ #include "thread-inl.h" extern "C" void art_quick_throw_stack_overflow(); -extern "C" void art_quick_throw_null_pointer_exception(); +extern "C" void art_quick_throw_null_pointer_exception_from_signal(); extern "C" void art_quick_implicit_suspend(); // @@ -84,8 +84,10 @@ void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo ATTRIBUTE_UNUSED *out_return_pc = sc->pc + 4; } -bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED, - void* context) { +bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info, void* context) { + if (!IsValidImplicitCheck(info)) { + return false; + } // The code that looks for the catch location needs to know the value of the // PC at the point of call. For Null checks we insert a GC map that is immediately after // the load/store instruction that might cause the fault. @@ -95,7 +97,10 @@ bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIB sc->regs[30] = sc->pc + 4; // LR needs to point to gc map location - sc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception); + sc->pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal); + // Pass the faulting address as the first argument of + // art_quick_throw_null_pointer_exception_from_signal. + sc->regs[0] = reinterpret_cast<uintptr_t>(info->si_addr); VLOG(signals) << "Generating null pointer exception"; return true; } diff --git a/runtime/arch/arm64/quick_entrypoints_arm64.S b/runtime/arch/arm64/quick_entrypoints_arm64.S index a6490aed33..10ee63f74f 100644 --- a/runtime/arch/arm64/quick_entrypoints_arm64.S +++ b/runtime/arch/arm64/quick_entrypoints_arm64.S @@ -406,6 +406,11 @@ ONE_ARG_RUNTIME_EXCEPTION art_quick_deliver_exception, artDeliverExceptionFromCo NO_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception, artThrowNullPointerExceptionFromCode /* + * Call installed by a signal handler to create and deliver a NullPointerException. + */ +ONE_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception_from_signal, artThrowNullPointerExceptionFromSignal + + /* * Called by managed code to create and deliver an ArithmeticException. */ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_div_zero, artThrowDivZeroFromCode diff --git a/runtime/arch/mips/fault_handler_mips.cc b/runtime/arch/mips/fault_handler_mips.cc index 8ea78eb900..754284c833 100644 --- a/runtime/arch/mips/fault_handler_mips.cc +++ b/runtime/arch/mips/fault_handler_mips.cc @@ -27,7 +27,7 @@ #include "thread-inl.h" extern "C" void art_quick_throw_stack_overflow(); -extern "C" void art_quick_throw_null_pointer_exception(); +extern "C" void art_quick_throw_null_pointer_exception_from_signal(); // // Mips specific fault handler functions. @@ -71,8 +71,10 @@ void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo, void* context, *out_return_pc = sc->sc_pc + 4; } -bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED, - void* context) { +bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info, void* context) { + if (!IsValidImplicitCheck(info)) { + return false; + } // The code that looks for the catch location needs to know the value of the // PC at the point of call. For Null checks we insert a GC map that is immediately after // the load/store instruction that might cause the fault. @@ -81,8 +83,11 @@ bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIB struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext); sc->sc_regs[31] = sc->sc_pc + 4; // RA needs to point to gc map location - sc->sc_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception); + sc->sc_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal); sc->sc_regs[25] = sc->sc_pc; // make sure T9 points to the function + // Pass the faulting address as the first argument of + // art_quick_throw_null_pointer_exception_from_signal. + sc->sc_regs[0] = reinterpret_cast<uintptr_t>(info->si_addr); VLOG(signals) << "Generating null pointer exception"; return true; } diff --git a/runtime/arch/mips/quick_entrypoints_mips.S b/runtime/arch/mips/quick_entrypoints_mips.S index bb89674caf..c1b8044be9 100644 --- a/runtime/arch/mips/quick_entrypoints_mips.S +++ b/runtime/arch/mips/quick_entrypoints_mips.S @@ -506,6 +506,18 @@ ENTRY art_quick_throw_null_pointer_exception move $a0, rSELF # pass Thread::Current END art_quick_throw_null_pointer_exception + + /* + * Call installed by a signal handler to create and deliver a NullPointerException. + */ + .extern artThrowNullPointerExceptionFromSignal +ENTRY art_quick_throw_null_pointer_exception_from_signal + SETUP_SAVE_ALL_CALLEE_SAVE_FRAME + la $t9, artThrowNullPointerExceptionFromSignal + jalr $zero, $t9 # artThrowNullPointerExceptionFromSignal(uintptr_t, Thread*) + move $a1, rSELF # pass Thread::Current +END art_quick_throw_null_pointer_exception_from_signal + /* * Called by managed code to create and deliver an ArithmeticException */ diff --git a/runtime/arch/mips64/fault_handler_mips64.cc b/runtime/arch/mips64/fault_handler_mips64.cc index 4abfcf12ff..c9a32ad7f9 100644 --- a/runtime/arch/mips64/fault_handler_mips64.cc +++ b/runtime/arch/mips64/fault_handler_mips64.cc @@ -27,7 +27,7 @@ #include "thread-inl.h" extern "C" void art_quick_throw_stack_overflow(); -extern "C" void art_quick_throw_null_pointer_exception(); +extern "C" void art_quick_throw_null_pointer_exception_from_signal(); // // Mips64 specific fault handler functions. @@ -71,8 +71,11 @@ void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo, void* context, *out_return_pc = sc->sc_pc + 4; } -bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIBUTE_UNUSED, - void* context) { +bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info, void* context) { + if (!IsValidImplicitCheck(info)) { + return false; + } + // The code that looks for the catch location needs to know the value of the // PC at the point of call. For Null checks we insert a GC map that is immediately after // the load/store instruction that might cause the fault. @@ -81,8 +84,11 @@ bool NullPointerHandler::Action(int sig ATTRIBUTE_UNUSED, siginfo_t* info ATTRIB struct sigcontext *sc = reinterpret_cast<struct sigcontext*>(&uc->uc_mcontext); sc->sc_regs[31] = sc->sc_pc + 4; // RA needs to point to gc map location - sc->sc_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception); + sc->sc_pc = reinterpret_cast<uintptr_t>(art_quick_throw_null_pointer_exception_from_signal); sc->sc_regs[25] = sc->sc_pc; // make sure T9 points to the function + // Pass the faulting address as the first argument of + // art_quick_throw_null_pointer_exception_from_signal. + sc->sc_regs[0] = reinterpret_cast<uintptr_t>(info->si_addr); VLOG(signals) << "Generating null pointer exception"; return true; } diff --git a/runtime/arch/mips64/quick_entrypoints_mips64.S b/runtime/arch/mips64/quick_entrypoints_mips64.S index 78ac748e32..ae6962076b 100644 --- a/runtime/arch/mips64/quick_entrypoints_mips64.S +++ b/runtime/arch/mips64/quick_entrypoints_mips64.S @@ -593,6 +593,17 @@ ENTRY art_quick_throw_null_pointer_exception END art_quick_throw_null_pointer_exception /* + * Call installed by a signal handler to create and deliver a NullPointerException + */ + .extern artThrowNullPointerExceptionFromSignal +ENTRY art_quick_throw_null_pointer_exception_from_signal + SETUP_SAVE_ALL_CALLEE_SAVE_FRAME + dla $t9, artThrowNullPointerExceptionFromSignal + jalr $zero, $t9 # artThrowNullPointerExceptionFromSignal(uinptr_t, Thread*) + move $a1, rSELF # pass Thread::Current +END art_quick_throw_null_pointer_exception + + /* * Called by managed code to create and deliver an ArithmeticException */ .extern artThrowDivZeroFromCode diff --git a/runtime/arch/x86/fault_handler_x86.cc b/runtime/arch/x86/fault_handler_x86.cc index d7c4cb182a..667d200732 100644 --- a/runtime/arch/x86/fault_handler_x86.cc +++ b/runtime/arch/x86/fault_handler_x86.cc @@ -71,12 +71,12 @@ namespace art { #if defined(__APPLE__) && defined(__x86_64__) // mac symbols have a prefix of _ on x86_64 -extern "C" void _art_quick_throw_null_pointer_exception(); +extern "C" void _art_quick_throw_null_pointer_exception_from_signal(); extern "C" void _art_quick_throw_stack_overflow(); extern "C" void _art_quick_test_suspend(); #define EXT_SYM(sym) _ ## sym #else -extern "C" void art_quick_throw_null_pointer_exception(); +extern "C" void art_quick_throw_null_pointer_exception_from_signal(); extern "C" void art_quick_throw_stack_overflow(); extern "C" void art_quick_test_suspend(); #define EXT_SYM(sym) sym @@ -292,7 +292,10 @@ void FaultManager::GetMethodAndReturnPcAndSp(siginfo_t* siginfo, void* context, *out_return_pc = reinterpret_cast<uintptr_t>(pc + instr_size); } -bool NullPointerHandler::Action(int, siginfo_t*, void* context) { +bool NullPointerHandler::Action(int, siginfo_t* sig, void* context) { + if (!IsValidImplicitCheck(sig)) { + return false; + } struct ucontext *uc = reinterpret_cast<struct ucontext*>(context); uint8_t* pc = reinterpret_cast<uint8_t*>(uc->CTX_EIP); uint8_t* sp = reinterpret_cast<uint8_t*>(uc->CTX_ESP); @@ -314,7 +317,15 @@ bool NullPointerHandler::Action(int, siginfo_t*, void* context) { *next_sp = retaddr; uc->CTX_ESP = reinterpret_cast<uintptr_t>(next_sp); - uc->CTX_EIP = reinterpret_cast<uintptr_t>(EXT_SYM(art_quick_throw_null_pointer_exception)); + uc->CTX_EIP = reinterpret_cast<uintptr_t>( + EXT_SYM(art_quick_throw_null_pointer_exception_from_signal)); + // Pass the faulting address as the first argument of + // art_quick_throw_null_pointer_exception_from_signal. +#if defined(__x86_64__) + uc->CTX_RDI = reinterpret_cast<uintptr_t>(sig->si_addr); +#else + uc->CTX_EAX = reinterpret_cast<uintptr_t>(sig->si_addr); +#endif VLOG(signals) << "Generating null pointer exception"; return true; } diff --git a/runtime/arch/x86/quick_entrypoints_x86.S b/runtime/arch/x86/quick_entrypoints_x86.S index b3dd4545f4..5851fbd804 100644 --- a/runtime/arch/x86/quick_entrypoints_x86.S +++ b/runtime/arch/x86/quick_entrypoints_x86.S @@ -284,6 +284,11 @@ END_MACRO NO_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception, artThrowNullPointerExceptionFromCode /* + * Call installed by a signal handler to create and deliver a NullPointerException. + */ +ONE_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception_from_signal, artThrowNullPointerExceptionFromSignal + + /* * Called by managed code to create and deliver an ArithmeticException. */ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_div_zero, artThrowDivZeroFromCode diff --git a/runtime/arch/x86_64/quick_entrypoints_x86_64.S b/runtime/arch/x86_64/quick_entrypoints_x86_64.S index 205307ce67..e777e6cfb2 100644 --- a/runtime/arch/x86_64/quick_entrypoints_x86_64.S +++ b/runtime/arch/x86_64/quick_entrypoints_x86_64.S @@ -309,6 +309,11 @@ END_MACRO NO_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception, artThrowNullPointerExceptionFromCode /* + * Call installed by a signal handler to create and deliver a NullPointerException. + */ +ONE_ARG_RUNTIME_EXCEPTION art_quick_throw_null_pointer_exception_from_signal, artThrowNullPointerExceptionFromSignal + + /* * Called by managed code to create and deliver an ArithmeticException. */ NO_ARG_RUNTIME_EXCEPTION art_quick_throw_div_zero, artThrowDivZeroFromCode diff --git a/runtime/common_throws.cc b/runtime/common_throws.cc index 4f705f2056..19387fb2f8 100644 --- a/runtime/common_throws.cc +++ b/runtime/common_throws.cc @@ -402,12 +402,124 @@ void ThrowNullPointerExceptionForMethodAccess(ArtMethod* method, dex_file, type); } -void ThrowNullPointerExceptionFromDexPC() { +static bool IsValidImplicitCheck(uintptr_t addr, ArtMethod* method, const Instruction& instr) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (!CanDoImplicitNullCheckOn(addr)) { + return false; + } + + switch (instr.Opcode()) { + case Instruction::INVOKE_DIRECT: + case Instruction::INVOKE_DIRECT_RANGE: + case Instruction::INVOKE_VIRTUAL: + case Instruction::INVOKE_VIRTUAL_RANGE: + case Instruction::INVOKE_INTERFACE: + case Instruction::INVOKE_INTERFACE_RANGE: + case Instruction::INVOKE_VIRTUAL_QUICK: + case Instruction::INVOKE_VIRTUAL_RANGE_QUICK: { + // Without inlining, we could just check that the offset is the class offset. + // However, when inlining, the compiler can (validly) merge the null check with a field access + // on the same object. Note that the stack map at the NPE will reflect the invoke's location, + // which is the caller. + return true; + } + + case Instruction::IGET: + case Instruction::IGET_WIDE: + case Instruction::IGET_OBJECT: + case Instruction::IGET_BOOLEAN: + case Instruction::IGET_BYTE: + case Instruction::IGET_CHAR: + case Instruction::IGET_SHORT: + case Instruction::IPUT: + case Instruction::IPUT_WIDE: + case Instruction::IPUT_OBJECT: + case Instruction::IPUT_BOOLEAN: + case Instruction::IPUT_BYTE: + case Instruction::IPUT_CHAR: + case Instruction::IPUT_SHORT: { + // Check that the fault address is at the offset of the field or null. The compiler + // can generate both. + ArtField* field = + Runtime::Current()->GetClassLinker()->ResolveField(instr.VRegC_22c(), method, false); + return (addr == 0) || (addr == field->GetOffset().Uint32Value()); + } + + case Instruction::IGET_QUICK: + case Instruction::IGET_BOOLEAN_QUICK: + case Instruction::IGET_BYTE_QUICK: + case Instruction::IGET_CHAR_QUICK: + case Instruction::IGET_SHORT_QUICK: + case Instruction::IGET_WIDE_QUICK: + case Instruction::IGET_OBJECT_QUICK: + case Instruction::IPUT_QUICK: + case Instruction::IPUT_BOOLEAN_QUICK: + case Instruction::IPUT_BYTE_QUICK: + case Instruction::IPUT_CHAR_QUICK: + case Instruction::IPUT_SHORT_QUICK: + case Instruction::IPUT_WIDE_QUICK: + case Instruction::IPUT_OBJECT_QUICK: { + // Check that the fault address is at the offset in the quickened instruction or null. + // The compiler can generate both. + return (addr == 0u) || (addr == instr.VRegC_22c()); + } + + case Instruction::AGET: + case Instruction::AGET_WIDE: + case Instruction::AGET_OBJECT: + case Instruction::AGET_BOOLEAN: + case Instruction::AGET_BYTE: + case Instruction::AGET_CHAR: + case Instruction::AGET_SHORT: + case Instruction::APUT: + case Instruction::APUT_WIDE: + case Instruction::APUT_OBJECT: + case Instruction::APUT_BOOLEAN: + case Instruction::APUT_BYTE: + case Instruction::APUT_CHAR: + case Instruction::APUT_SHORT: { + // The length access should crash. We currently do not do implicit checks on + // the array access itself. + return (addr == 0u) || (addr == mirror::Array::LengthOffset().Uint32Value()); + } + + case Instruction::FILL_ARRAY_DATA: { + // The length access should crash. We currently do not do implicit checks on + // the array access itself. + return (addr == 0u) || (addr == mirror::Array::LengthOffset().Uint32Value()); + } + + case Instruction::ARRAY_LENGTH: { + // The length access should crash. + return (addr == 0u) || (addr == mirror::Array::LengthOffset().Uint32Value()); + } + + default: { + // We have covered all the cases where an NPE could occur. + // Note that this must be kept in sync with the compiler, and adding + // any new way to do implicit checks in the compiler should also update + // this code. + return false; + } + } +} + +void ThrowNullPointerExceptionFromDexPC(bool check_address, uintptr_t addr) { uint32_t throw_dex_pc; ArtMethod* method = Thread::Current()->GetCurrentMethod(&throw_dex_pc); const DexFile::CodeItem* code = method->GetCodeItem(); CHECK_LT(throw_dex_pc, code->insns_size_in_code_units_); const Instruction* instr = Instruction::At(&code->insns_[throw_dex_pc]); + if (check_address && !IsValidImplicitCheck(addr, method, *instr)) { + const DexFile* dex_file = method->GetDeclaringClass()->GetDexCache()->GetDexFile(); + LOG(FATAL) << "Invalid address for an implicit NullPointerException check: " + << "0x" << std::hex << addr << std::dec + << ", at " + << instr->DumpString(dex_file) + << " in " + << PrettyMethod(method); + } + switch (instr->Opcode()) { case Instruction::INVOKE_DIRECT: ThrowNullPointerExceptionForMethodAccess(instr->VRegB_35c(), kDirect); @@ -530,14 +642,26 @@ void ThrowNullPointerExceptionFromDexPC() { ThrowException("Ljava/lang/NullPointerException;", nullptr, "Attempt to get length of null array"); break; + case Instruction::FILL_ARRAY_DATA: { + ThrowException("Ljava/lang/NullPointerException;", nullptr, + "Attempt to write to null array"); + break; + } + case Instruction::INVOKE_LAMBDA: + case Instruction::BOX_LAMBDA: + case Instruction::UNBOX_LAMBDA: + case Instruction::LIBERATE_VARIABLE: { + ThrowException("Ljava/lang/NullPointerException;", nullptr, + "Using a null lambda"); + break; + } default: { - // TODO: We should have covered all the cases where we expect a NPE above, this - // message/logging is so we can improve any cases we've missed in the future. const DexFile* dex_file = method->GetDeclaringClass()->GetDexCache()->GetDexFile(); - ThrowException("Ljava/lang/NullPointerException;", nullptr, - StringPrintf("Null pointer exception during instruction '%s'", - instr->DumpString(dex_file).c_str()).c_str()); + LOG(FATAL) << "NullPointerException at an unexpected instruction: " + << instr->DumpString(dex_file) + << " in " + << PrettyMethod(method); break; } } diff --git a/runtime/common_throws.h b/runtime/common_throws.h index 7a335859e5..cbd338d315 100644 --- a/runtime/common_throws.h +++ b/runtime/common_throws.h @@ -195,7 +195,7 @@ void ThrowNullPointerExceptionForMethodAccess(ArtMethod* method, InvokeType type) SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; -void ThrowNullPointerExceptionFromDexPC() +void ThrowNullPointerExceptionFromDexPC(bool check_address = false, uintptr_t addr = 0) SHARED_REQUIRES(Locks::mutator_lock_) COLD_ATTR; void ThrowNullPointerException(const char* msg) diff --git a/runtime/entrypoints/quick/quick_default_externs.h b/runtime/entrypoints/quick/quick_default_externs.h index 771e14396e..d0dad34789 100644 --- a/runtime/entrypoints/quick/quick_default_externs.h +++ b/runtime/entrypoints/quick/quick_default_externs.h @@ -120,6 +120,7 @@ extern "C" void art_quick_throw_array_bounds(int32_t index, int32_t limit); extern "C" void art_quick_throw_div_zero(); extern "C" void art_quick_throw_no_such_method(int32_t method_idx); extern "C" void art_quick_throw_null_pointer_exception(); +extern "C" void art_quick_throw_null_pointer_exception_from_signal(uintptr_t address); extern "C" void art_quick_throw_stack_overflow(void*); extern "C" void art_quick_throw_string_bounds(int32_t index, int32_t limit); diff --git a/runtime/entrypoints/quick/quick_throw_entrypoints.cc b/runtime/entrypoints/quick/quick_throw_entrypoints.cc index 2778e32ece..ea9f7b0826 100644 --- a/runtime/entrypoints/quick/quick_throw_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_throw_entrypoints.cc @@ -52,8 +52,18 @@ extern "C" NO_RETURN void artDeliverExceptionFromCode(mirror::Throwable* excepti extern "C" NO_RETURN void artThrowNullPointerExceptionFromCode(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) { ScopedQuickEntrypointChecks sqec(self); + // We come from an explicit check in the generated code. This path is triggered + // only if the object is indeed null. + ThrowNullPointerExceptionFromDexPC(/* check_address */ false, 0U); + self->QuickDeliverException(); +} + +// Installed by a signal handler to throw a NPE exception. +extern "C" NO_RETURN void artThrowNullPointerExceptionFromSignal(uintptr_t addr, Thread* self) + SHARED_REQUIRES(Locks::mutator_lock_) { + ScopedQuickEntrypointChecks sqec(self); self->NoteSignalBeingHandled(); - ThrowNullPointerExceptionFromDexPC(); + ThrowNullPointerExceptionFromDexPC(/* check_address */ true, addr); self->NoteSignalHandlerDone(); self->QuickDeliverException(); } diff --git a/runtime/fault_handler.h b/runtime/fault_handler.h index 625b1e8cc6..56e0fb78c1 100644 --- a/runtime/fault_handler.h +++ b/runtime/fault_handler.h @@ -96,6 +96,14 @@ class NullPointerHandler FINAL : public FaultHandler { bool Action(int sig, siginfo_t* siginfo, void* context) OVERRIDE; + static bool IsValidImplicitCheck(siginfo_t* siginfo) { + // Our implicit NPE checks always limit the range to a page. + // Note that the runtime will do more exhaustive checks (that we cannot + // reasonably do in signal processing code) based on the dex instruction + // faulting. + return CanDoImplicitNullCheckOn(reinterpret_cast<uintptr_t>(siginfo->si_addr)); + } + private: DISALLOW_COPY_AND_ASSIGN(NullPointerHandler); }; diff --git a/runtime/globals.h b/runtime/globals.h index 477cbdf5d4..0b44c47092 100644 --- a/runtime/globals.h +++ b/runtime/globals.h @@ -40,6 +40,12 @@ static constexpr size_t kStackAlignment = 16; // compile-time constant so the compiler can generate better code. static constexpr int kPageSize = 4096; +// Returns whether the given memory offset can be used for generating +// an implicit null check. +static inline bool CanDoImplicitNullCheckOn(uintptr_t offset) { + return offset < kPageSize; +} + // Required object alignment static constexpr size_t kObjectAlignment = 8; static constexpr size_t kLargeObjectAlignment = kPageSize; diff --git a/runtime/thread.h b/runtime/thread.h index 9b6a20ec59..ab2462535c 100644 --- a/runtime/thread.h +++ b/runtime/thread.h @@ -1060,6 +1060,8 @@ class Thread { return tlsPtr_.mterp_alt_ibase; } + // Notify that a signal is being handled. This is to protect us from doing recursive + // NPE handling after a SIGSEGV. void NoteSignalBeingHandled() { if (tls32_.handling_signal_) { LOG(FATAL) << "Detected signal while processing a signal"; |