diff options
21 files changed, 481 insertions, 75 deletions
diff --git a/build/Android.common_build.mk b/build/Android.common_build.mk index 288bdddfe7..b50712429e 100644 --- a/build/Android.common_build.mk +++ b/build/Android.common_build.mk @@ -300,9 +300,17 @@ ifeq ($(ART_HEAP_POISONING),true) art_asflags += -DART_HEAP_POISONING=1 endif +# +# Used to change the read barrier type. Valid values are BAKER, BROOKS, TABLELOOKUP. +# The default is BAKER. +# +ART_READ_BARRIER_TYPE ?= BAKER + ifeq ($(ART_USE_READ_BARRIER),true) art_cflags += -DART_USE_READ_BARRIER=1 + art_cflags += -DART_READ_BARRIER_TYPE_IS_$(ART_READ_BARRIER_TYPE)=1 art_asflags += -DART_USE_READ_BARRIER=1 + art_asflags += -DART_READ_BARRIER_TYPE_IS_$(ART_READ_BARRIER_TYPE)=1 endif ifeq ($(ART_USE_TLAB),true) @@ -392,7 +400,6 @@ art_debug_cflags := art_non_debug_cflags := art_host_non_debug_cflags := art_target_non_debug_cflags := -art_default_gc_type := art_default_gc_type_cflags := ART_HOST_LDLIBS := diff --git a/compiler/optimizing/intrinsics_mips64.cc b/compiler/optimizing/intrinsics_mips64.cc index 1b4d1614f1..b60905d682 100644 --- a/compiler/optimizing/intrinsics_mips64.cc +++ b/compiler/optimizing/intrinsics_mips64.cc @@ -404,6 +404,29 @@ static void GenMinMax(LocationSummary* locations, GpuRegister rhs = locations->InAt(1).AsRegister<GpuRegister>(); GpuRegister out = locations->Out().AsRegister<GpuRegister>(); + // Some architectures, such as ARM and MIPS (prior to r6), have a + // conditional move instruction which only changes the target + // (output) register if the condition is true (MIPS prior to r6 had + // MOVF, MOVT, and MOVZ). The SELEQZ and SELNEZ instructions always + // change the target (output) register. If the condition is true the + // output register gets the contents of the "rs" register; otherwise, + // the output register is set to zero. One consequence of this is + // that to implement something like "rd = c==0 ? rs : rt" MIPS64r6 + // needs to use a pair of SELEQZ/SELNEZ instructions. After + // executing this pair of instructions one of the output registers + // from the pair will necessarily contain zero. Then the code ORs the + // output registers from the SELEQZ/SELNEZ instructions to get the + // final result. + // + // The initial test to see if the output register is same as the + // first input register is needed to make sure that value in the + // first input register isn't clobbered before we've finished + // computing the output value. The logic in the corresponding else + // clause performs the same task but makes sure the second input + // register isn't clobbered in the event that it's the same register + // as the output register; the else clause also handles the case + // where the output register is distinct from both the first, and the + // second input registers. if (out == lhs) { __ Slt(AT, rhs, lhs); if (is_min) { @@ -512,13 +535,12 @@ void IntrinsicLocationsBuilderMIPS64::VisitMathFloor(HInvoke* invoke) { CreateFPToFP(arena_, invoke); } -// 0x200 - +zero -// 0x040 - +infinity -// 0x020 - -zero -// 0x004 - -infinity -// 0x002 - quiet NaN -// 0x001 - signaling NaN -const constexpr uint16_t CLASS_MASK = 0x267; +const constexpr uint16_t kFPLeaveUnchanged = kPositiveZero | + kPositiveInfinity | + kNegativeZero | + kNegativeInfinity | + kQuietNaN | + kSignalingNaN; void IntrinsicCodeGeneratorMIPS64::VisitMathFloor(HInvoke* invoke) { LocationSummary* locations = invoke->GetLocations(); @@ -534,7 +556,7 @@ void IntrinsicCodeGeneratorMIPS64::VisitMathFloor(HInvoke* invoke) { // } __ ClassD(out, in); __ Dmfc1(AT, out); - __ Andi(AT, AT, CLASS_MASK); // +0.0 | +Inf | -0.0 | -Inf | qNaN | sNaN + __ Andi(AT, AT, kFPLeaveUnchanged); // +0.0 | +Inf | -0.0 | -Inf | qNaN | sNaN __ MovD(out, in); __ Bnezc(AT, &done); @@ -583,7 +605,7 @@ void IntrinsicCodeGeneratorMIPS64::VisitMathCeil(HInvoke* invoke) { // } __ ClassD(out, in); __ Dmfc1(AT, out); - __ Andi(AT, AT, CLASS_MASK); // +0.0 | +Inf | -0.0 | -Inf | qNaN | sNaN + __ Andi(AT, AT, kFPLeaveUnchanged); // +0.0 | +Inf | -0.0 | -Inf | qNaN | sNaN __ MovD(out, in); __ Bnezc(AT, &done); diff --git a/compiler/utils/mips64/assembler_mips64.h b/compiler/utils/mips64/assembler_mips64.h index c170313728..d083eb4306 100644 --- a/compiler/utils/mips64/assembler_mips64.h +++ b/compiler/utils/mips64/assembler_mips64.h @@ -46,6 +46,20 @@ enum StoreOperandType { kStoreDoubleword }; +// Used to test the values returned by ClassS/ClassD. +enum FPClassMaskType { + kSignalingNaN = 0x001, + kQuietNaN = 0x002, + kNegativeInfinity = 0x004, + kNegativeNormal = 0x008, + kNegativeSubnormal = 0x010, + kNegativeZero = 0x020, + kPositiveInfinity = 0x040, + kPositiveNormal = 0x080, + kPositiveSubnormal = 0x100, + kPositiveZero = 0x200, +}; + class Mips64Assembler FINAL : public Assembler { public: Mips64Assembler() {} diff --git a/runtime/check_jni.cc b/runtime/check_jni.cc index b6ad5473ff..beabce36fb 100644 --- a/runtime/check_jni.cc +++ b/runtime/check_jni.cc @@ -2463,6 +2463,9 @@ class CheckJNI { ScopedCheck sc(kFlag_Default, __FUNCTION__); JniValueType args[2] = {{.E = env}, {.L = obj}}; if (sc.Check(soa, true, "EL", args)) { + if (obj != nullptr) { + down_cast<JNIEnvExt*>(env)->RecordMonitorEnter(obj); + } JniValueType result; result.i = baseEnv(env)->MonitorEnter(env, obj); if (sc.Check(soa, false, "i", &result)) { @@ -2477,6 +2480,9 @@ class CheckJNI { ScopedCheck sc(kFlag_ExcepOkay, __FUNCTION__); JniValueType args[2] = {{.E = env}, {.L = obj}}; if (sc.Check(soa, true, "EL", args)) { + if (obj != nullptr) { + down_cast<JNIEnvExt*>(env)->CheckMonitorRelease(obj); + } JniValueType result; result.i = baseEnv(env)->MonitorExit(env, obj); if (sc.Check(soa, false, "i", &result)) { diff --git a/runtime/class_linker.h b/runtime/class_linker.h index 739403f6c9..7f3e93806e 100644 --- a/runtime/class_linker.h +++ b/runtime/class_linker.h @@ -883,6 +883,7 @@ class ClassLinker { friend class ImageWriter; // for GetClassRoots friend class ImageDumper; // for FindOpenedOatFileFromOatLocation friend class JniCompilerTest; // for GetRuntimeQuickGenericJniStub + friend class JniInternalTest; // for GetRuntimeQuickGenericJniStub ART_FRIEND_TEST(mirror::DexCacheTest, Open); // for AllocDexCache DISALLOW_COPY_AND_ASSIGN(ClassLinker); diff --git a/runtime/entrypoints/quick/quick_jni_entrypoints.cc b/runtime/entrypoints/quick/quick_jni_entrypoints.cc index fc5c52e75a..58f256a191 100644 --- a/runtime/entrypoints/quick/quick_jni_entrypoints.cc +++ b/runtime/entrypoints/quick/quick_jni_entrypoints.cc @@ -65,6 +65,9 @@ static void GoToRunnable(Thread* self) NO_THREAD_SAFETY_ANALYSIS { static void PopLocalReferences(uint32_t saved_local_ref_cookie, Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) { JNIEnvExt* env = self->GetJniEnv(); + if (UNLIKELY(env->check_jni)) { + env->CheckNoHeldMonitors(); + } env->locals.SetSegmentState(env->local_ref_cookie); env->local_ref_cookie = saved_local_ref_cookie; self->PopHandleScope(); diff --git a/runtime/jni_env_ext.cc b/runtime/jni_env_ext.cc index b18b430403..4104d7a0e8 100644 --- a/runtime/jni_env_ext.cc +++ b/runtime/jni_env_ext.cc @@ -16,10 +16,17 @@ #include "jni_env_ext.h" +#include <algorithm> +#include <vector> + #include "check_jni.h" #include "indirect_reference_table.h" #include "java_vm_ext.h" #include "jni_internal.h" +#include "lock_word.h" +#include "mirror/object-inl.h" +#include "nth_caller_visitor.h" +#include "thread-inl.h" namespace art { @@ -63,14 +70,14 @@ JNIEnvExt::JNIEnvExt(Thread* self_in, JavaVMExt* vm_in) JNIEnvExt::~JNIEnvExt() { } -jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) SHARED_REQUIRES(Locks::mutator_lock_) { +jobject JNIEnvExt::NewLocalRef(mirror::Object* obj) { if (obj == nullptr) { return nullptr; } return reinterpret_cast<jobject>(locals.Add(local_ref_cookie, obj)); } -void JNIEnvExt::DeleteLocalRef(jobject obj) SHARED_REQUIRES(Locks::mutator_lock_) { +void JNIEnvExt::DeleteLocalRef(jobject obj) { if (obj != nullptr) { locals.Remove(local_ref_cookie, reinterpret_cast<IndirectRef>(obj)); } @@ -86,14 +93,14 @@ void JNIEnvExt::DumpReferenceTables(std::ostream& os) { monitors.Dump(os); } -void JNIEnvExt::PushFrame(int capacity) SHARED_REQUIRES(Locks::mutator_lock_) { +void JNIEnvExt::PushFrame(int capacity) { UNUSED(capacity); // cpplint gets confused with (int) and thinks its a cast. // TODO: take 'capacity' into account. stacked_local_ref_cookies.push_back(local_ref_cookie); local_ref_cookie = locals.GetSegmentState(); } -void JNIEnvExt::PopFrame() SHARED_REQUIRES(Locks::mutator_lock_) { +void JNIEnvExt::PopFrame() { locals.SetSegmentState(local_ref_cookie); local_ref_cookie = stacked_local_ref_cookies.back(); stacked_local_ref_cookies.pop_back(); @@ -104,4 +111,118 @@ Offset JNIEnvExt::SegmentStateOffset() { IndirectReferenceTable::SegmentStateOffset().Int32Value()); } +// Use some defining part of the caller's frame as the identifying mark for the JNI segment. +static uintptr_t GetJavaCallFrame(Thread* self) SHARED_REQUIRES(Locks::mutator_lock_) { + NthCallerVisitor zeroth_caller(self, 0, false); + zeroth_caller.WalkStack(); + if (zeroth_caller.caller == nullptr) { + // No Java code, must be from pure native code. + return 0; + } else if (zeroth_caller.GetCurrentQuickFrame() == nullptr) { + // Shadow frame = interpreter. Use the actual shadow frame's address. + DCHECK(zeroth_caller.GetCurrentShadowFrame() != nullptr); + return reinterpret_cast<uintptr_t>(zeroth_caller.GetCurrentShadowFrame()); + } else { + // Quick frame = compiled code. Use the bottom of the frame. + return reinterpret_cast<uintptr_t>(zeroth_caller.GetCurrentQuickFrame()); + } +} + +void JNIEnvExt::RecordMonitorEnter(jobject obj) { + locked_objects_.push_back(std::make_pair(GetJavaCallFrame(self), obj)); +} + +static std::string ComputeMonitorDescription(Thread* self, + jobject obj) SHARED_REQUIRES(Locks::mutator_lock_) { + mirror::Object* o = self->DecodeJObject(obj); + if ((o->GetLockWord(false).GetState() == LockWord::kThinLocked) && + Locks::mutator_lock_->IsExclusiveHeld(self)) { + // Getting the identity hashcode here would result in lock inflation and suspension of the + // current thread, which isn't safe if this is the only runnable thread. + return StringPrintf("<@addr=0x%" PRIxPTR "> (a %s)", + reinterpret_cast<intptr_t>(o), + PrettyTypeOf(o).c_str()); + } else { + // IdentityHashCode can cause thread suspension, which would invalidate o if it moved. So + // we get the pretty type before we call IdentityHashCode. + const std::string pretty_type(PrettyTypeOf(o)); + return StringPrintf("<0x%08x> (a %s)", o->IdentityHashCode(), pretty_type.c_str()); + } +} + +static void RemoveMonitors(Thread* self, + uintptr_t frame, + ReferenceTable* monitors, + std::vector<std::pair<uintptr_t, jobject>>* locked_objects) + SHARED_REQUIRES(Locks::mutator_lock_) { + auto kept_end = std::remove_if( + locked_objects->begin(), + locked_objects->end(), + [self, frame, monitors](const std::pair<uintptr_t, jobject>& pair) + SHARED_REQUIRES(Locks::mutator_lock_) { + if (frame == pair.first) { + mirror::Object* o = self->DecodeJObject(pair.second); + monitors->Remove(o); + return true; + } + return false; + }); + locked_objects->erase(kept_end, locked_objects->end()); +} + +void JNIEnvExt::CheckMonitorRelease(jobject obj) { + uintptr_t current_frame = GetJavaCallFrame(self); + std::pair<uintptr_t, jobject> exact_pair = std::make_pair(current_frame, obj); + auto it = std::find(locked_objects_.begin(), locked_objects_.end(), exact_pair); + bool will_abort = false; + if (it != locked_objects_.end()) { + locked_objects_.erase(it); + } else { + // Check whether this monitor was locked in another JNI "session." + mirror::Object* mirror_obj = self->DecodeJObject(obj); + for (std::pair<uintptr_t, jobject>& pair : locked_objects_) { + if (self->DecodeJObject(pair.second) == mirror_obj) { + std::string monitor_descr = ComputeMonitorDescription(self, pair.second); + vm->JniAbortF("<JNI MonitorExit>", + "Unlocking monitor that wasn't locked here: %s", + monitor_descr.c_str()); + will_abort = true; + break; + } + } + } + + // When we abort, also make sure that any locks from the current "session" are removed from + // the monitors table, otherwise we may visit local objects in GC during abort (which won't be + // valid anymore). + if (will_abort) { + RemoveMonitors(self, current_frame, &monitors, &locked_objects_); + } +} + +void JNIEnvExt::CheckNoHeldMonitors() { + uintptr_t current_frame = GetJavaCallFrame(self); + // The locked_objects_ are grouped by their stack frame component, as this enforces structured + // locking, and the groups form a stack. So the current frame entries are at the end. Check + // whether the vector is empty, and when there are elements, whether the last element belongs + // to this call - this signals that there are unlocked monitors. + if (!locked_objects_.empty()) { + std::pair<uintptr_t, jobject>& pair = locked_objects_[locked_objects_.size() - 1]; + if (pair.first == current_frame) { + std::string monitor_descr = ComputeMonitorDescription(self, pair.second); + vm->JniAbortF("<JNI End>", + "Still holding a locked object on JNI end: %s", + monitor_descr.c_str()); + // When we abort, also make sure that any locks from the current "session" are removed from + // the monitors table, otherwise we may visit local objects in GC during abort. + RemoveMonitors(self, current_frame, &monitors, &locked_objects_); + } else if (kIsDebugBuild) { + // Make sure there are really no other entries and our checking worked as expected. + for (std::pair<uintptr_t, jobject>& check_pair : locked_objects_) { + CHECK_NE(check_pair.first, current_frame); + } + } + } +} + } // namespace art diff --git a/runtime/jni_env_ext.h b/runtime/jni_env_ext.h index 9b55536e98..3828ff045d 100644 --- a/runtime/jni_env_ext.h +++ b/runtime/jni_env_ext.h @@ -43,8 +43,8 @@ struct JNIEnvExt : public JNIEnv { void SetCheckJniEnabled(bool enabled); - void PushFrame(int capacity); - void PopFrame(); + void PushFrame(int capacity) SHARED_REQUIRES(Locks::mutator_lock_); + void PopFrame() SHARED_REQUIRES(Locks::mutator_lock_); template<typename T> T AddLocalReference(mirror::Object* obj) @@ -89,10 +89,27 @@ struct JNIEnvExt : public JNIEnv { // Used by -Xcheck:jni. const JNINativeInterface* unchecked_functions; + // Functions to keep track of monitor lock and unlock operations. Used to ensure proper locking + // rules in CheckJNI mode. + + // Record locking of a monitor. + void RecordMonitorEnter(jobject obj) SHARED_REQUIRES(Locks::mutator_lock_); + + // Check the release, that is, that the release is performed in the same JNI "segment." + void CheckMonitorRelease(jobject obj) SHARED_REQUIRES(Locks::mutator_lock_); + + // Check that no monitors are held that have been acquired in this JNI "segment." + void CheckNoHeldMonitors() SHARED_REQUIRES(Locks::mutator_lock_); + private: // The constructor should not be called directly. It may leave the object in an erronuous state, // and the result needs to be checked. JNIEnvExt(Thread* self, JavaVMExt* vm); + + // All locked objects, with the (Java caller) stack frame that locked them. Used in CheckJNI + // to ensure that only monitors locked in this native frame are being unlocked, and that at + // the end all are unlocked. + std::vector<std::pair<uintptr_t, jobject>> locked_objects_; }; // Used to save and restore the JNIEnvExt state when not going through code created by the JNI diff --git a/runtime/jni_internal_test.cc b/runtime/jni_internal_test.cc index 2a0cb28f0c..41b368ec32 100644 --- a/runtime/jni_internal_test.cc +++ b/runtime/jni_internal_test.cc @@ -607,11 +607,64 @@ class JniInternalTest : public CommonCompilerTest { EXPECT_EQ(check_jni, vm_->SetCheckJniEnabled(old_check_jni)); } + void SetUpForTest(bool direct, const char* method_name, const char* method_sig, + void* native_fnptr) { + // Initialize class loader and set generic JNI entrypoint. + // Note: this code is adapted from the jni_compiler_test, and taken with minimal modifications. + if (!runtime_->IsStarted()) { + { + ScopedObjectAccess soa(Thread::Current()); + class_loader_ = LoadDex("MyClassNatives"); + StackHandleScope<1> hs(soa.Self()); + Handle<mirror::ClassLoader> loader( + hs.NewHandle(soa.Decode<mirror::ClassLoader*>(class_loader_))); + mirror::Class* c = class_linker_->FindClass(soa.Self(), "LMyClassNatives;", loader); + const auto pointer_size = class_linker_->GetImagePointerSize(); + ArtMethod* method = direct ? c->FindDirectMethod(method_name, method_sig, pointer_size) : + c->FindVirtualMethod(method_name, method_sig, pointer_size); + ASSERT_TRUE(method != nullptr) << method_name << " " << method_sig; + method->SetEntryPointFromQuickCompiledCode(class_linker_->GetRuntimeQuickGenericJniStub()); + } + // Start runtime. + Thread::Current()->TransitionFromSuspendedToRunnable(); + bool started = runtime_->Start(); + CHECK(started); + } + // JNI operations after runtime start. + env_ = Thread::Current()->GetJniEnv(); + jklass_ = env_->FindClass("MyClassNatives"); + ASSERT_TRUE(jklass_ != nullptr) << method_name << " " << method_sig; + + if (direct) { + jmethod_ = env_->GetStaticMethodID(jklass_, method_name, method_sig); + } else { + jmethod_ = env_->GetMethodID(jklass_, method_name, method_sig); + } + ASSERT_TRUE(jmethod_ != nullptr) << method_name << " " << method_sig; + + if (native_fnptr != nullptr) { + JNINativeMethod methods[] = { { method_name, method_sig, native_fnptr } }; + ASSERT_EQ(JNI_OK, env_->RegisterNatives(jklass_, methods, 1)) + << method_name << " " << method_sig; + } else { + env_->UnregisterNatives(jklass_); + } + + jmethodID constructor = env_->GetMethodID(jklass_, "<init>", "()V"); + jobj_ = env_->NewObject(jklass_, constructor); + ASSERT_TRUE(jobj_ != nullptr) << method_name << " " << method_sig; + } + JavaVMExt* vm_; JNIEnv* env_; jclass aioobe_; jclass ase_; jclass sioobe_; + + jclass jklass_; + jobject jobj_; + jobject class_loader_; + jmethodID jmethod_; }; TEST_F(JniInternalTest, AllocObject) { @@ -2111,4 +2164,38 @@ TEST_F(JniInternalTest, MonitorEnterExit) { } } +void Java_MyClassNatives_foo_exit(JNIEnv* env, jobject thisObj) { + // Release the monitor on self. This should trigger an abort. + env->MonitorExit(thisObj); +} + +TEST_F(JniInternalTest, MonitorExitLockedInDifferentCall) { + SetUpForTest(false, "foo", "()V", reinterpret_cast<void*>(&Java_MyClassNatives_foo_exit)); + ASSERT_NE(jobj_, nullptr); + + env_->MonitorEnter(jobj_); + EXPECT_FALSE(env_->ExceptionCheck()); + + CheckJniAbortCatcher check_jni_abort_catcher; + env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_); + check_jni_abort_catcher.Check("Unlocking monitor that wasn't locked here"); +} + +void Java_MyClassNatives_foo_enter_no_exit(JNIEnv* env, jobject thisObj) { + // Acquire but don't release the monitor on self. This should trigger an abort on return. + env->MonitorEnter(thisObj); +} + +TEST_F(JniInternalTest, MonitorExitNotAllUnlocked) { + SetUpForTest(false, + "foo", + "()V", + reinterpret_cast<void*>(&Java_MyClassNatives_foo_enter_no_exit)); + ASSERT_NE(jobj_, nullptr); + + CheckJniAbortCatcher check_jni_abort_catcher; + env_->CallNonvirtualVoidMethod(jobj_, jklass_, jmethod_); + check_jni_abort_catcher.Check("Still holding a locked object on JNI end"); +} + } // namespace art diff --git a/runtime/read_barrier_c.h b/runtime/read_barrier_c.h index 710c21f03e..8e5b1872f2 100644 --- a/runtime/read_barrier_c.h +++ b/runtime/read_barrier_c.h @@ -26,10 +26,16 @@ // table-lookup read barriers. #ifdef ART_USE_READ_BARRIER +#if ART_READ_BARRIER_TYPE_IS_BAKER #define USE_BAKER_READ_BARRIER -// #define USE_BROOKS_READ_BARRIER -// #define USE_TABLE_LOOKUP_READ_BARRIER +#elif ART_READ_BARRIER_TYPE_IS_BROOKS +#define USE_BROOKS_READ_BARRIER +#elif ART_READ_BARRIER_TYPE_IS_TABLELOOKUP +#define USE_TABLE_LOOKUP_READ_BARRIER +#else +#error "ART read barrier type must be set" #endif +#endif // ART_USE_READ_BARRIER #ifdef ART_HEAP_POISONING #define USE_HEAP_POISONING diff --git a/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali b/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali index aa0c2d5a13..0dc492f2b3 100644 --- a/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali +++ b/test/088-monitor-verification/smali/NotStructuredOverUnlock.smali @@ -5,7 +5,7 @@ .method public static run(Ljava/lang/Object;)V .registers 3 - invoke-static {}, LMain;->assertCallerIsInterpreted()V + invoke-static {}, LMain;->assertIsInterpreted()V # Lock twice, but unlock thrice. diff --git a/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali b/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali index 2c31fdaa85..df6e168685 100644 --- a/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali +++ b/test/088-monitor-verification/smali/NotStructuredUnderUnlock.smali @@ -5,7 +5,7 @@ .method public static run(Ljava/lang/Object;)V .registers 3 - invoke-static {}, LMain;->assertCallerIsInterpreted()V + invoke-static {}, LMain;->assertIsInterpreted()V # Lock thrice, but only unlock twice. diff --git a/test/088-monitor-verification/smali/OK.smali b/test/088-monitor-verification/smali/OK.smali index 596798d80c..a43ecb0704 100644 --- a/test/088-monitor-verification/smali/OK.smali +++ b/test/088-monitor-verification/smali/OK.smali @@ -20,7 +20,7 @@ .method public static runNoMonitors(Ljava/lang/Object;Ljava/lang/Object;)V .registers 3 - invoke-static {}, LMain;->assertCallerIsManaged()V + invoke-static {}, LMain;->assertIsManaged()V return-void @@ -29,7 +29,7 @@ .method public static runStraightLine(Ljava/lang/Object;Ljava/lang/Object;)V .registers 3 - invoke-static {}, LMain;->assertCallerIsManaged()V + invoke-static {}, LMain;->assertIsManaged()V monitor-enter v1 # 1 monitor-enter v2 # 2 @@ -44,7 +44,7 @@ .method public static runBalancedJoin(Ljava/lang/Object;Ljava/lang/Object;)V .registers 3 - invoke-static {}, LMain;->assertCallerIsManaged()V + invoke-static {}, LMain;->assertIsManaged()V monitor-enter v1 # 1 diff --git a/test/088-monitor-verification/smali/TooDeep.smali b/test/088-monitor-verification/smali/TooDeep.smali index 1a8f2f06e8..a1e328148d 100644 --- a/test/088-monitor-verification/smali/TooDeep.smali +++ b/test/088-monitor-verification/smali/TooDeep.smali @@ -7,7 +7,7 @@ # Lock depth is 33, which is more than the verifier supports. This should have been punted to # the interpreter. - invoke-static {}, LMain;->assertCallerIsInterpreted()V + invoke-static {}, LMain;->assertIsInterpreted()V monitor-enter v2 # 1 monitor-enter v2 # 2 diff --git a/test/088-monitor-verification/smali/UnbalancedJoin.smali b/test/088-monitor-verification/smali/UnbalancedJoin.smali index da8f7732af..993f32c022 100644 --- a/test/088-monitor-verification/smali/UnbalancedJoin.smali +++ b/test/088-monitor-verification/smali/UnbalancedJoin.smali @@ -5,7 +5,7 @@ .method public static run(Ljava/lang/Object;Ljava/lang/Object;)V .registers 3 - invoke-static {}, LMain;->assertCallerIsInterpreted()V + invoke-static {}, LMain;->assertIsInterpreted()V if-eqz v2, :Lnull diff --git a/test/088-monitor-verification/smali/UnbalancedStraight.smali b/test/088-monitor-verification/smali/UnbalancedStraight.smali index 68edb6c783..cbb8bcc488 100644 --- a/test/088-monitor-verification/smali/UnbalancedStraight.smali +++ b/test/088-monitor-verification/smali/UnbalancedStraight.smali @@ -5,7 +5,7 @@ .method public static run(Ljava/lang/Object;Ljava/lang/Object;)V .registers 3 - invoke-static {}, LMain;->assertCallerIsInterpreted()V + invoke-static {}, LMain;->assertIsInterpreted()V monitor-enter v1 # 1 monitor-enter v2 # 2 diff --git a/test/088-monitor-verification/src/Main.java b/test/088-monitor-verification/src/Main.java index fc5755b06f..218805543e 100644 --- a/test/088-monitor-verification/src/Main.java +++ b/test/088-monitor-verification/src/Main.java @@ -220,7 +220,7 @@ public class Main { // Smali testing code. private static void runSmaliTests() { - if (!hasOatFile() || runtimeIsSoftFail() || isCallerInterpreted()) { + if (!hasOatFile() || runtimeIsSoftFail() || isInterpreted()) { // Skip test, this seems to be a non-compiled code test configuration. return; } @@ -277,9 +277,9 @@ public class Main { } // Helpers for the smali code. - public static native void assertCallerIsInterpreted(); - public static native void assertCallerIsManaged(); + public static native void assertIsInterpreted(); + public static native void assertIsManaged(); public static native boolean hasOatFile(); public static native boolean runtimeIsSoftFail(); - public static native boolean isCallerInterpreted(); + public static native boolean isInterpreted(); } diff --git a/test/449-checker-bce/expected.txt b/test/449-checker-bce/expected.txt index e114c50371..4665d7af8b 100644 --- a/test/449-checker-bce/expected.txt +++ b/test/449-checker-bce/expected.txt @@ -1 +1,2 @@ +JNI_OnLoad called java.lang.ArrayIndexOutOfBoundsException: length=5; index=82 diff --git a/test/449-checker-bce/src/Main.java b/test/449-checker-bce/src/Main.java index f06c250dc7..22829cddc8 100644 --- a/test/449-checker-bce/src/Main.java +++ b/test/449-checker-bce/src/Main.java @@ -265,6 +265,7 @@ public class Main { // A helper into which the actual throwing function should be inlined. static void constantIndexingForward6(int[] array) { + assertIsManaged(); constantIndexing6(array); } @@ -618,13 +619,17 @@ public class Main { static int foo() { try { + assertIsManaged(); // This will cause AIOOBE. constantIndexing2(new int[3]); } catch (ArrayIndexOutOfBoundsException e) { + assertIsManaged(); // This is to ensure that single-frame deoptimization works. + // Will need to be updated if constantIndexing2 is inlined. try { // This will cause AIOOBE. constantIndexingForward6(new int[3]); } catch (ArrayIndexOutOfBoundsException e2) { + assertIsManaged(); return 99; } } @@ -634,13 +639,13 @@ public class Main { int sum; - /// CHECK-START: void Main.foo1(int[], int, int) BCE (before) + /// CHECK-START: void Main.foo1(int[], int, int, boolean) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet - /// CHECK-START: void Main.foo1(int[], int, int) BCE (after) + /// CHECK-START: void Main.foo1(int[], int, int, boolean) BCE (after) /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet @@ -657,25 +662,30 @@ public class Main { /// CHECK: Phi /// CHECK: Goto - void foo1(int[] array, int start, int end) { + void foo1(int[] array, int start, int end, boolean expectInterpreter) { // Three HDeoptimize will be added. One for // start >= 0, one for end <= array.length, // and one for null check on array (to hoist null // check and array.length out of loop). for (int i = start ; i < end; i++) { + if (expectInterpreter) { + assertIsInterpreted(); + } else { + assertIsManaged(); + } array[i] = 1; sum += array[i]; } } - /// CHECK-START: void Main.foo2(int[], int, int) BCE (before) + /// CHECK-START: void Main.foo2(int[], int, int, boolean) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet - /// CHECK-START: void Main.foo2(int[], int, int) BCE (after) + /// CHECK-START: void Main.foo2(int[], int, int, boolean) BCE (after) /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet @@ -692,25 +702,30 @@ public class Main { /// CHECK: Phi /// CHECK: Goto - void foo2(int[] array, int start, int end) { + void foo2(int[] array, int start, int end, boolean expectInterpreter) { // Three HDeoptimize will be added. One for // start >= 0, one for end <= array.length, // and one for null check on array (to hoist null // check and array.length out of loop). for (int i = start ; i <= end; i++) { + if (expectInterpreter) { + assertIsInterpreted(); + } else { + assertIsManaged(); + } array[i] = 1; sum += array[i]; } } - /// CHECK-START: void Main.foo3(int[], int) BCE (before) + /// CHECK-START: void Main.foo3(int[], int, boolean) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet - /// CHECK-START: void Main.foo3(int[], int) BCE (after) + /// CHECK-START: void Main.foo3(int[], int, boolean) BCE (after) /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet @@ -726,24 +741,29 @@ public class Main { /// CHECK: Phi /// CHECK: Goto - void foo3(int[] array, int end) { + void foo3(int[] array, int end, boolean expectInterpreter) { // Two HDeoptimize will be added. One for end < array.length, // and one for null check on array (to hoist null check // and array.length out of loop). for (int i = 3 ; i <= end; i++) { + if (expectInterpreter) { + assertIsInterpreted(); + } else { + assertIsManaged(); + } array[i] = 1; sum += array[i]; } } - /// CHECK-START: void Main.foo4(int[], int) BCE (before) + /// CHECK-START: void Main.foo4(int[], int, boolean) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArraySet /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet - /// CHECK-START: void Main.foo4(int[], int) BCE (after) + /// CHECK-START: void Main.foo4(int[], int, boolean) BCE (after) /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet @@ -759,18 +779,23 @@ public class Main { /// CHECK: Phi /// CHECK: Goto - void foo4(int[] array, int end) { + void foo4(int[] array, int end, boolean expectInterpreter) { // Two HDeoptimize will be added. One for end <= array.length, // and one for null check on array (to hoist null check // and array.length out of loop). for (int i = end ; i > 0; i--) { + if (expectInterpreter) { + assertIsInterpreted(); + } else { + assertIsManaged(); + } array[i - 1] = 1; sum += array[i - 1]; } } - /// CHECK-START: void Main.foo5(int[], int) BCE (before) + /// CHECK-START: void Main.foo5(int[], int, boolean) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArraySet /// CHECK: BoundsCheck @@ -780,7 +805,7 @@ public class Main { /// CHECK: BoundsCheck /// CHECK: ArrayGet - /// CHECK-START: void Main.foo5(int[], int) BCE (after) + /// CHECK-START: void Main.foo5(int[], int, boolean) BCE (after) /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet /// CHECK: Phi @@ -800,7 +825,7 @@ public class Main { /// CHECK-NOT: Phi /// CHECK: Goto - void foo5(int[] array, int end) { + void foo5(int[] array, int end, boolean expectInterpreter) { // Bounds check in this loop can be eliminated without deoptimization. for (int i = array.length - 1 ; i >= 0; i--) { array[i] = 1; @@ -808,6 +833,11 @@ public class Main { // One HDeoptimize will be added. // It's for (end - 2 <= array.length - 2). for (int i = end - 2 ; i > 0; i--) { + if (expectInterpreter) { + assertIsInterpreted(); + } else { + assertIsManaged(); + } sum += array[i - 1]; sum += array[i]; sum += array[i + 1]; @@ -815,7 +845,7 @@ public class Main { } - /// CHECK-START: void Main.foo6(int[], int, int) BCE (before) + /// CHECK-START: void Main.foo6(int[], int, int, boolean) BCE (before) /// CHECK: BoundsCheck /// CHECK: ArrayGet /// CHECK: BoundsCheck @@ -829,7 +859,7 @@ public class Main { /// CHECK-NOT: BoundsCheck /// CHECK: ArraySet - /// CHECK-START: void Main.foo6(int[], int, int) BCE (after) + /// CHECK-START: void Main.foo6(int[], int, int, boolean) BCE (after) /// CHECK: Phi /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet @@ -855,12 +885,17 @@ public class Main { /// CHECK: Goto /// CHECK-NOT: Deoptimize - void foo6(int[] array, int start, int end) { + void foo6(int[] array, int start, int end, boolean expectInterpreter) { // Three HDeoptimize will be added. One for // start >= 2, one for end <= array.length - 3, // and one for null check on array (to hoist null // check and array.length out of loop). for (int i = end; i >= start; i--) { + if (expectInterpreter) { + assertIsInterpreted(); + } else { + assertIsManaged(); + } array[i] = (array[i-2] + array[i-1] + array[i] + array[i+1] + array[i+2]) / 5; } } @@ -948,12 +983,12 @@ public class Main { } - /// CHECK-START: void Main.foo9(int[]) BCE (before) + /// CHECK-START: void Main.foo9(int[], boolean) BCE (before) /// CHECK: NullCheck /// CHECK: BoundsCheck /// CHECK: ArrayGet - /// CHECK-START: void Main.foo9(int[]) BCE (after) + /// CHECK-START: void Main.foo9(int[], boolean) BCE (after) // The loop is guaranteed to be entered. No need to transform the // loop for loop body entry test. /// CHECK: Deoptimize @@ -964,10 +999,15 @@ public class Main { /// CHECK-NOT: BoundsCheck /// CHECK: ArrayGet - void foo9(int[] array) { + void foo9(int[] array, boolean expectInterpreter) { // Two HDeoptimize will be added. One for // 10 <= array.length, and one for null check on array. for (int i = 0 ; i < 10; i++) { + if (expectInterpreter) { + assertIsInterpreted(); + } else { + assertIsManaged(); + } sum += array[i]; } } @@ -999,7 +1039,7 @@ public class Main { static void testUnknownBounds() { boolean caught = false; Main main = new Main(); - main.foo1(new int[10], 0, 10); + main.foo1(new int[10], 0, 10, false); if (main.sum != 10) { System.out.println("foo1 failed!"); } @@ -1007,7 +1047,7 @@ public class Main { caught = false; main = new Main(); try { - main.foo1(new int[10], 0, 11); + main.foo1(new int[10], 0, 11, true); } catch (ArrayIndexOutOfBoundsException e) { caught = true; } @@ -1016,7 +1056,7 @@ public class Main { } main = new Main(); - main.foo2(new int[10], 0, 9); + main.foo2(new int[10], 0, 9, false); if (main.sum != 10) { System.out.println("foo2 failed!"); } @@ -1024,7 +1064,7 @@ public class Main { caught = false; main = new Main(); try { - main.foo2(new int[10], 0, 10); + main.foo2(new int[10], 0, 10, true); } catch (ArrayIndexOutOfBoundsException e) { caught = true; } @@ -1033,7 +1073,7 @@ public class Main { } main = new Main(); - main.foo3(new int[10], 9); + main.foo3(new int[10], 9, false); if (main.sum != 7) { System.out.println("foo3 failed!"); } @@ -1041,7 +1081,7 @@ public class Main { caught = false; main = new Main(); try { - main.foo3(new int[10], 10); + main.foo3(new int[10], 10, true); } catch (ArrayIndexOutOfBoundsException e) { caught = true; } @@ -1050,7 +1090,7 @@ public class Main { } main = new Main(); - main.foo4(new int[10], 10); + main.foo4(new int[10], 10, false); if (main.sum != 10) { System.out.println("foo4 failed!"); } @@ -1058,7 +1098,7 @@ public class Main { caught = false; main = new Main(); try { - main.foo4(new int[10], 11); + main.foo4(new int[10], 11, true); } catch (ArrayIndexOutOfBoundsException e) { caught = true; } @@ -1067,7 +1107,7 @@ public class Main { } main = new Main(); - main.foo5(new int[10], 10); + main.foo5(new int[10], 10, false); if (main.sum != 24) { System.out.println("foo5 failed!"); } @@ -1075,7 +1115,7 @@ public class Main { caught = false; main = new Main(); try { - main.foo5(new int[10], 11); + main.foo5(new int[10], 11, true); } catch (ArrayIndexOutOfBoundsException e) { caught = true; } @@ -1084,11 +1124,11 @@ public class Main { } main = new Main(); - main.foo6(new int[10], 2, 7); + main.foo6(new int[10], 2, 7, false); main = new Main(); int[] array9 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; - main.foo9(array9); + main.foo9(array9, false); if (main.sum != 45) { System.out.println("foo9 failed!"); } @@ -1104,7 +1144,7 @@ public class Main { caught = false; main = new Main(); try { - main.foo6(new int[10], 2, 8); + main.foo6(new int[10], 2, 8, true); } catch (ArrayIndexOutOfBoundsException e) { caught = true; } @@ -1115,7 +1155,7 @@ public class Main { caught = false; main = new Main(); try { - main.foo6(new int[10], 1, 7); + main.foo6(new int[10], 1, 7, true); } catch (ArrayIndexOutOfBoundsException e) { caught = true; } @@ -1152,6 +1192,15 @@ public class Main { /// CHECK: ParallelMove public static void main(String[] args) { + System.loadLibrary(args[0]); + + if (!compiledWithOptimizing() || + !hasOatFile() || + runtimeIsSoftFail() || + isInterpreted()) { + disableStackFrameAsserts(); + } + sieve(20); int[] array = {5, 2, 3, 7, 0, 1, 6, 4}; @@ -1190,4 +1239,11 @@ public class Main { new Main().testExceptionMessage(); } + public static native boolean compiledWithOptimizing(); + public static native void disableStackFrameAsserts(); + public static native void assertIsManaged(); + public static native void assertIsInterpreted(); + public static native boolean hasOatFile(); + public static native boolean runtimeIsSoftFail(); + public static native boolean isInterpreted(); } diff --git a/test/common/runtime_state.cc b/test/common/runtime_state.cc index 042b03bfb6..082c9b3c8d 100644 --- a/test/common/runtime_state.cc +++ b/test/common/runtime_state.cc @@ -66,4 +66,54 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isImageDex2OatEnabled(JNIEnv* en return Runtime::Current()->IsImageDex2OatEnabled(); } +// public static native boolean compiledWithOptimizing(); +// Did we use the optimizing compiler to compile this? + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_compiledWithOptimizing(JNIEnv* env, jclass cls) { + ScopedObjectAccess soa(env); + + mirror::Class* klass = soa.Decode<mirror::Class*>(cls); + const DexFile& dex_file = klass->GetDexFile(); + const OatFile::OatDexFile* oat_dex_file = dex_file.GetOatDexFile(); + if (oat_dex_file == nullptr) { + // Could be JIT, which also uses optimizing, but conservatively say no. + return JNI_FALSE; + } + const OatFile* oat_file = oat_dex_file->GetOatFile(); + CHECK(oat_file != nullptr); + + const char* cmd_line = oat_file->GetOatHeader().GetStoreValueByKey(OatHeader::kDex2OatCmdLineKey); + CHECK(cmd_line != nullptr); // Huh? This should not happen. + + // Check the backend. + constexpr const char* kCompilerBackend = "--compiler-backend="; + const char* backend = strstr(cmd_line, kCompilerBackend); + if (backend != nullptr) { + // If it's set, make sure it's optimizing. + backend += strlen(kCompilerBackend); + if (strncmp(backend, "Optimizing", strlen("Optimizing")) != 0) { + return JNI_FALSE; + } + } + + // Check the filter. + constexpr const char* kCompilerFilter = "--compiler-filter="; + const char* filter = strstr(cmd_line, kCompilerFilter); + if (filter != nullptr) { + // If it's set, make sure it's not interpret-only|verify-none|verify-at-runtime. + // Note: The space filter might have an impact on the test, but ignore that for now. + filter += strlen(kCompilerFilter); + constexpr const char* kInterpretOnly = "interpret-only"; + constexpr const char* kVerifyNone = "verify-none"; + constexpr const char* kVerifyAtRuntime = "verify-at-runtime"; + if (strncmp(filter, kInterpretOnly, strlen(kInterpretOnly)) == 0 || + strncmp(filter, kVerifyNone, strlen(kVerifyNone)) == 0 || + strncmp(filter, kVerifyAtRuntime, strlen(kVerifyAtRuntime)) == 0) { + return JNI_FALSE; + } + } + + return JNI_TRUE; +} + } // namespace art diff --git a/test/common/stack_inspect.cc b/test/common/stack_inspect.cc index d22cf52882..922eae61e2 100644 --- a/test/common/stack_inspect.cc +++ b/test/common/stack_inspect.cc @@ -27,9 +27,20 @@ namespace art { -// public static native boolean isCallerInterpreted(); +static bool asserts_enabled = true; -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerInterpreted(JNIEnv* env, jclass) { +// public static native void disableStackFrameAsserts(); +// Note: to globally disable asserts in unsupported configurations. + +extern "C" JNIEXPORT void JNICALL Java_Main_disableStackFrameAsserts(JNIEnv* env ATTRIBUTE_UNUSED, + jclass cls ATTRIBUTE_UNUSED) { + asserts_enabled = false; +} + + +// public static native boolean isInterpreted(); + +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isInterpreted(JNIEnv* env, jclass) { ScopedObjectAccess soa(env); NthCallerVisitor caller(soa.Self(), 1, false); caller.WalkStack(); @@ -37,16 +48,18 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerInterpreted(JNIEnv* env, return caller.GetCurrentShadowFrame() != nullptr ? JNI_TRUE : JNI_FALSE; } -// public static native void assertCallerIsInterpreted(); +// public static native void assertIsInterpreted(); -extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsInterpreted(JNIEnv* env, jclass klass) { - CHECK(Java_Main_isCallerInterpreted(env, klass)); +extern "C" JNIEXPORT void JNICALL Java_Main_assertIsInterpreted(JNIEnv* env, jclass klass) { + if (asserts_enabled) { + CHECK(Java_Main_isInterpreted(env, klass)); + } } -// public static native boolean isCallerManaged(); +// public static native boolean isManaged(); -extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerManaged(JNIEnv* env, jclass cls) { +extern "C" JNIEXPORT jboolean JNICALL Java_Main_isManaged(JNIEnv* env, jclass cls) { ScopedObjectAccess soa(env); mirror::Class* klass = soa.Decode<mirror::Class*>(cls); @@ -65,10 +78,12 @@ extern "C" JNIEXPORT jboolean JNICALL Java_Main_isCallerManaged(JNIEnv* env, jcl return caller.GetCurrentShadowFrame() != nullptr ? JNI_FALSE : JNI_TRUE; } -// public static native void assertCallerIsManaged(); +// public static native void assertIsManaged(); -extern "C" JNIEXPORT void JNICALL Java_Main_assertCallerIsManaged(JNIEnv* env, jclass cls) { - CHECK(Java_Main_isCallerManaged(env, cls)); +extern "C" JNIEXPORT void JNICALL Java_Main_assertIsManaged(JNIEnv* env, jclass cls) { + if (asserts_enabled) { + CHECK(Java_Main_isManaged(env, cls)); + } } } // namespace art |