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
diff --git a/runtime/interpreter/unstarted_runtime.cc b/runtime/interpreter/unstarted_runtime.cc
index 9531557..a2a190e 100644
--- a/runtime/interpreter/unstarted_runtime.cc
+++ b/runtime/interpreter/unstarted_runtime.cc
@@ -16,11 +16,13 @@
#include "unstarted_runtime.h"
+#include <ctype.h>
#include <errno.h>
#include <stdlib.h>
#include <cmath>
#include <limits>
+#include <locale>
#include <unordered_map>
#include "ScopedLocalRef.h"
@@ -70,6 +72,43 @@
}
}
+// 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<uint32_t>(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<char>(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<mirror::String> className,
Handle<mirror::ClassLoader> class_loader, JValue* result,
diff --git a/runtime/interpreter/unstarted_runtime_list.h b/runtime/interpreter/unstarted_runtime_list.h
index de62ed8..be881cd 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 1b5b665..100a446 100644
--- a/runtime/interpreter/unstarted_runtime_test.cc
+++ b/runtime/interpreter/unstarted_runtime_test.cc
@@ -17,6 +17,7 @@
#include "unstarted_runtime.h"
#include <limits>
+#include <locale>
#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 @@
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<mirror::ClassLoader>());
+ CHECK(result != nullptr);
+ }
};
TEST_F(UnstartedRuntimeTest, MemoryPeekByte) {
@@ -689,5 +701,106 @@
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<char>(i), c_locale);
+ bool c_lower = std::islower(static_cast<char>(i), c_locale);
+ EXPECT_FALSE(c_upper && c_lower) << i;
+
+ // Check toLowerCase.
+ {
+ JValue result;
+ tmp->SetVReg(0, static_cast<int32_t>(i));
+ UnstartedCharacterToLowerCase(self, tmp, &result, 0);
+ ASSERT_FALSE(self->IsExceptionPending());
+ uint32_t lower_result = static_cast<uint32_t>(result.GetI());
+ if (c_lower) {
+ EXPECT_EQ(i, lower_result);
+ } else if (c_upper) {
+ EXPECT_EQ(static_cast<uint32_t>(std::tolower(static_cast<char>(i), c_locale)),
+ lower_result);
+ } else {
+ EXPECT_EQ(i, lower_result);
+ }
+ }
+
+ // Check toUpperCase.
+ {
+ JValue result2;
+ tmp->SetVReg(0, static_cast<int32_t>(i));
+ UnstartedCharacterToUpperCase(self, tmp, &result2, 0);
+ ASSERT_FALSE(self->IsExceptionPending());
+ uint32_t upper_result = static_cast<uint32_t>(result2.GetI());
+ if (c_upper) {
+ EXPECT_EQ(i, upper_result);
+ } else if (c_lower) {
+ EXPECT_EQ(static_cast<uint32_t>(std::toupper(static_cast<char>(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<int32_t>(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<int32_t>(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<uint32_t>::max(); i <<= 1) {
+ {
+ JValue result;
+ tmp->SetVReg(0, static_cast<int32_t>(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<int32_t>(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