From 8ce9c30f5d7328579619eb7e26c5f041837984af Mon Sep 17 00:00:00 2001 From: Andreas Gampe Date: Fri, 15 Apr 2016 21:24:28 -0700 Subject: [PATCH] ART: Add cutout for Character.toLower/UpperCase Add support for ASCII codepoint toLowerCase/toUpperCase. Those inputs are easy, and generally sufficient. Allows to compile-time initialize: * android.text.Html$HtmlParser * java.util.UUID$Holder * sun.security.ec.ECKeyFactory Bug: 27265238 (cherry picked from commit 0bdce99fcecdadcbafc8e7a9bb92f491a4f37b2a) Change-Id: I0f164a7df4f26c0b266cef230e36f6ca3af20bde --- runtime/interpreter/unstarted_runtime.cc | 39 +++++++++ runtime/interpreter/unstarted_runtime_list.h | 2 + runtime/interpreter/unstarted_runtime_test.cc | 113 ++++++++++++++++++++++++++ 3 files changed, 154 insertions(+) diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc index 953155707..a2a190e58 100644 --- a/runtime/interpreter/unstarted_runtime.cc +++ b/runtime/interpreter/unstarted_runtime.cc @@ -16,11 +16,13 @@ #include "unstarted_runtime.h" +#include #include #include #include #include +#include #include #include "ScopedLocalRef.h" @@ -70,6 +72,43 @@ static void AbortTransactionOrFail(Thread* self, const char* fmt, ...) { } } +// Restricted support for character upper case / lower case. Only support ASCII, where +// it's easy. Abort the transaction otherwise. +static void CharacterLowerUpper(Thread* self, + ShadowFrame* shadow_frame, + JValue* result, + size_t arg_offset, + bool to_lower_case) SHARED_REQUIRES(Locks::mutator_lock_) { + uint32_t int_value = static_cast(shadow_frame->GetVReg(arg_offset)); + + // Only ASCII (7-bit). + if (!isascii(int_value)) { + AbortTransactionOrFail(self, + "Only support ASCII characters for toLowerCase/toUpperCase: %u", + int_value); + return; + } + + std::locale c_locale("C"); + char char_value = static_cast(int_value); + + if (to_lower_case) { + result->SetI(std::tolower(char_value, c_locale)); + } else { + result->SetI(std::toupper(char_value, c_locale)); + } +} + +void UnstartedRuntime::UnstartedCharacterToLowerCase( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + CharacterLowerUpper(self, shadow_frame, result, arg_offset, true); +} + +void UnstartedRuntime::UnstartedCharacterToUpperCase( + Thread* self, ShadowFrame* shadow_frame, JValue* result, size_t arg_offset) { + CharacterLowerUpper(self, shadow_frame, result, arg_offset, false); +} + // Helper function to deal with class loading in an unstarted runtime. static void UnstartedRuntimeFindClass(Thread* self, Handle className, Handle class_loader, JValue* result, diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h index de62ed823..be881cd2c 100644 --- a/runtime/interpreter/unstarted_runtime_list.h +++ b/runtime/interpreter/unstarted_runtime_list.h @@ -19,6 +19,8 @@ // Methods that intercept available libcore implementations. #define UNSTARTED_RUNTIME_DIRECT_LIST(V) \ + V(CharacterToLowerCase, "int java.lang.Character.toLowerCase(int)") \ + V(CharacterToUpperCase, "int java.lang.Character.toUpperCase(int)") \ V(ClassForName, "java.lang.Class java.lang.Class.forName(java.lang.String)") \ V(ClassForNameLong, "java.lang.Class java.lang.Class.forName(java.lang.String, boolean, java.lang.ClassLoader)") \ V(ClassClassForName, "java.lang.Class java.lang.Class.classForName(java.lang.String, boolean, java.lang.ClassLoader)") \ diff --git a/runtime/interpreter/unstarted_runtime_test.cc b/runtime/interpreter/unstarted_runtime_test.cc index 1b5b665ed..100a44626 100644 --- a/runtime/interpreter/unstarted_runtime_test.cc +++ b/runtime/interpreter/unstarted_runtime_test.cc @@ -17,6 +17,7 @@ #include "unstarted_runtime.h" #include +#include #include "base/casts.h" #include "class_linker.h" @@ -30,6 +31,7 @@ #include "runtime.h" #include "scoped_thread_state_change.h" #include "thread.h" +#include "transaction.h" namespace art { namespace interpreter { @@ -182,6 +184,16 @@ class UnstartedRuntimeTest : public CommonRuntimeTest { EXPECT_EQ(expect_int64t, result_int64t) << result.GetD() << " vs " << test_pairs[i][1]; } } + + // Prepare for aborts. Aborts assume that the exception class is already resolved, as the + // loading code doesn't work under transactions. + void PrepareForAborts() SHARED_REQUIRES(Locks::mutator_lock_) { + mirror::Object* result = Runtime::Current()->GetClassLinker()->FindClass( + Thread::Current(), + Transaction::kAbortExceptionSignature, + ScopedNullHandle()); + CHECK(result != nullptr); + } }; TEST_F(UnstartedRuntimeTest, MemoryPeekByte) { @@ -689,5 +701,106 @@ TEST_F(UnstartedRuntimeTest, Floor) { ShadowFrame::DeleteDeoptimizedFrame(tmp); } +TEST_F(UnstartedRuntimeTest, ToLowerUpper) { + Thread* self = Thread::Current(); + ScopedObjectAccess soa(self); + + ShadowFrame* tmp = ShadowFrame::CreateDeoptimizedFrame(10, nullptr, nullptr, 0); + + std::locale c_locale("C"); + + // Check ASCII. + for (uint32_t i = 0; i < 128; ++i) { + bool c_upper = std::isupper(static_cast(i), c_locale); + bool c_lower = std::islower(static_cast(i), c_locale); + EXPECT_FALSE(c_upper && c_lower) << i; + + // Check toLowerCase. + { + JValue result; + tmp->SetVReg(0, static_cast(i)); + UnstartedCharacterToLowerCase(self, tmp, &result, 0); + ASSERT_FALSE(self->IsExceptionPending()); + uint32_t lower_result = static_cast(result.GetI()); + if (c_lower) { + EXPECT_EQ(i, lower_result); + } else if (c_upper) { + EXPECT_EQ(static_cast(std::tolower(static_cast(i), c_locale)), + lower_result); + } else { + EXPECT_EQ(i, lower_result); + } + } + + // Check toUpperCase. + { + JValue result2; + tmp->SetVReg(0, static_cast(i)); + UnstartedCharacterToUpperCase(self, tmp, &result2, 0); + ASSERT_FALSE(self->IsExceptionPending()); + uint32_t upper_result = static_cast(result2.GetI()); + if (c_upper) { + EXPECT_EQ(i, upper_result); + } else if (c_lower) { + EXPECT_EQ(static_cast(std::toupper(static_cast(i), c_locale)), + upper_result); + } else { + EXPECT_EQ(i, upper_result); + } + } + } + + // Check abort for other things. Can't test all. + + PrepareForAborts(); + + for (uint32_t i = 128; i < 256; ++i) { + { + JValue result; + tmp->SetVReg(0, static_cast(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToLowerCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + { + JValue result; + tmp->SetVReg(0, static_cast(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToUpperCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + } + for (uint64_t i = 256; i <= std::numeric_limits::max(); i <<= 1) { + { + JValue result; + tmp->SetVReg(0, static_cast(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToLowerCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + { + JValue result; + tmp->SetVReg(0, static_cast(i)); + Transaction transaction; + Runtime::Current()->EnterTransactionMode(&transaction); + UnstartedCharacterToUpperCase(self, tmp, &result, 0); + Runtime::Current()->ExitTransactionMode(); + ASSERT_TRUE(self->IsExceptionPending()); + ASSERT_TRUE(transaction.IsAborted()); + } + } + + ShadowFrame::DeleteDeoptimizedFrame(tmp); +} + } // namespace interpreter } // namespace art -- 2.11.0