Implement the direct ByteBuffer JNI functions, GetObjectRefType, and the string region functions.
Also run tests in a consistent (alphabetical) order.
Change-Id: I1bb4f3389e749ec031254d23da349be0397c260d
diff --git a/src/jni_internal.cc b/src/jni_internal.cc
index 0845c0f..d11f2a7 100644
--- a/src/jni_internal.cc
+++ b/src/jni_internal.cc
@@ -503,6 +503,10 @@
"%s offset=%d length=%d %s.length=%d",
type.c_str(), start, length, identifier, array->GetLength());
}
+void ThrowSIOOBE(ScopedJniThreadState& ts, jsize start, jsize length, jsize array_length) {
+ ts.Self()->ThrowNewException("Ljava/lang/StringIndexOutOfBoundsException;",
+ "offset=%d length=%d string.length()=%d", start, length, array_length);
+}
template <typename JavaArrayT, typename JavaT, typename ArrayT>
static void GetPrimitiveArrayRegion(ScopedJniThreadState& ts, JavaArrayT java_array, jsize start, jsize length, JavaT* buf) {
@@ -526,6 +530,17 @@
}
}
+static jclass InitDirectByteBufferClass(JNIEnv* env) {
+ ScopedLocalRef<jclass> buffer_class(env, env->FindClass("java/nio/ReadWriteDirectByteBuffer"));
+ CHECK(buffer_class.get() != NULL);
+ return reinterpret_cast<jclass>(env->NewGlobalRef(buffer_class.get()));
+}
+
+static jclass GetDirectByteBufferClass(JNIEnv* env) {
+ static jclass buffer_class = InitDirectByteBufferClass(env);
+ return buffer_class;
+}
+
} // namespace
class JNI {
@@ -1700,14 +1715,26 @@
return Decode<String*>(ts, java_string)->GetUtfLength();
}
- static void GetStringRegion(JNIEnv* env, jstring str, jsize start, jsize len, jchar* buf) {
+ static void GetStringRegion(JNIEnv* env, jstring java_string, jsize start, jsize length, jchar* buf) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
+ String* s = Decode<String*>(ts, java_string);
+ if (start < 0 || length < 0 || start + length > s->GetLength()) {
+ ThrowSIOOBE(ts, start, length, s->GetLength());
+ } else {
+ const jchar* chars = s->GetCharArray()->GetData() + s->GetOffset();
+ memcpy(buf, chars + start, length * sizeof(jchar));
+ }
}
- static void GetStringUTFRegion(JNIEnv* env, jstring str, jsize start, jsize len, char* buf) {
+ static void GetStringUTFRegion(JNIEnv* env, jstring java_string, jsize start, jsize length, char* buf) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
+ String* s = Decode<String*>(ts, java_string);
+ if (start < 0 || length < 0 || start + length > s->GetLength()) {
+ ThrowSIOOBE(ts, start, length, s->GetLength());
+ } else {
+ const jchar* chars = s->GetCharArray()->GetData() + s->GetOffset();
+ ConvertUtf16ToModifiedUtf8(buf, chars + start, length);
+ }
}
static const jchar* GetStringChars(JNIEnv* env, jstring str, jboolean* isCopy) {
@@ -1732,6 +1759,17 @@
UNIMPLEMENTED(FATAL);
}
+ static const jchar* GetStringCritical(JNIEnv* env, jstring s, jboolean* isCopy) {
+ ScopedJniThreadState ts(env);
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+ }
+
+ static void ReleaseStringCritical(JNIEnv* env, jstring s, const jchar* cstr) {
+ ScopedJniThreadState ts(env);
+ UNIMPLEMENTED(FATAL);
+ }
+
static jsize GetArrayLength(JNIEnv* env, jarray java_array) {
ScopedJniThreadState ts(env);
Object* obj = Decode<Object*>(ts, java_array);
@@ -1817,6 +1855,17 @@
return NewPrimitiveArray<jshortArray, ShortArray>(ts, length);
}
+ static void* GetPrimitiveArrayCritical(JNIEnv* env, jarray array, jboolean* isCopy) {
+ ScopedJniThreadState ts(env);
+ UNIMPLEMENTED(FATAL);
+ return NULL;
+ }
+
+ static void ReleasePrimitiveArrayCritical(JNIEnv* env, jarray array, void* carray, jint mode) {
+ ScopedJniThreadState ts(env);
+ UNIMPLEMENTED(FATAL);
+ }
+
static jboolean* GetBooleanArrayElements(JNIEnv* env,
jbooleanArray array, jboolean* isCopy) {
ScopedJniThreadState ts(env);
@@ -2091,52 +2140,73 @@
return (*vm != NULL) ? JNI_OK : JNI_ERR;
}
- static void* GetPrimitiveArrayCritical(JNIEnv* env,
- jarray array, jboolean* isCopy) {
- ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return NULL;
- }
-
- static void ReleasePrimitiveArrayCritical(JNIEnv* env,
- jarray array, void* carray, jint mode) {
- ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- }
-
- static const jchar* GetStringCritical(JNIEnv* env, jstring s, jboolean* isCopy) {
- ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return NULL;
- }
-
- static void ReleaseStringCritical(JNIEnv* env, jstring s, const jchar* cstr) {
- ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- }
-
static jobject NewDirectByteBuffer(JNIEnv* env, void* address, jlong capacity) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return NULL;
+
+ // The address may not be NULL, and the capacity must be > 0.
+ CHECK(address != NULL);
+ CHECK_GT(capacity, 0);
+
+ jclass buffer_class = GetDirectByteBufferClass(env);
+ jmethodID mid = env->GetMethodID(buffer_class, "<init>", "(II)V");
+ if (mid == NULL) {
+ return NULL;
+ }
+
+ // At the moment, the Java side is limited to 32 bits.
+ CHECK_LE(reinterpret_cast<uintptr_t>(address), 0xffffffff);
+ CHECK_LE(capacity, 0xffffffff);
+ jint address_arg = reinterpret_cast<jint>(address);
+ jint capacity_arg = static_cast<jint>(capacity);
+
+ jobject result = env->NewObject(buffer_class, mid, address_arg, capacity_arg);
+ return ts.Self()->IsExceptionPending() ? NULL : result;
}
- static void* GetDirectBufferAddress(JNIEnv* env, jobject buf) {
+ static void* GetDirectBufferAddress(JNIEnv* env, jobject java_buffer) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return NULL;
+ static jfieldID fid = env->GetFieldID(GetDirectByteBufferClass(env), "effectiveDirectAddress", "I");
+ return reinterpret_cast<void*>(env->GetIntField(java_buffer, fid));
}
- static jlong GetDirectBufferCapacity(JNIEnv* env, jobject buf) {
+ static jlong GetDirectBufferCapacity(JNIEnv* env, jobject java_buffer) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return 0;
+ static jfieldID fid = env->GetFieldID(GetDirectByteBufferClass(env), "capacity", "I");
+ return static_cast<jlong>(env->GetIntField(java_buffer, fid));
}
- static jobjectRefType GetObjectRefType(JNIEnv* env, jobject jobj) {
+ static jobjectRefType GetObjectRefType(JNIEnv* env, jobject java_object) {
ScopedJniThreadState ts(env);
- UNIMPLEMENTED(FATAL);
- return JNIInvalidRefType;
+
+ CHECK(java_object != NULL);
+
+ // Do we definitely know what kind of reference this is?
+ IndirectRef ref = reinterpret_cast<IndirectRef>(java_object);
+ IndirectRefKind kind = GetIndirectRefKind(ref);
+ switch (kind) {
+ case kLocal:
+ return JNILocalRefType;
+ case kGlobal:
+ return JNIGlobalRefType;
+ case kWeakGlobal:
+ return JNIWeakGlobalRefType;
+ case kSirtOrInvalid:
+ // Is it in a stack IRT?
+ if (ts.Self()->SirtContains(java_object)) {
+ return JNILocalRefType;
+ }
+
+ // If we're handing out direct pointers, check whether it's a direct pointer
+ // to a local reference.
+ // TODO: replace 'false' with the replacement for gDvmJni.workAroundAppJniBugs
+ if (false && Decode<Object*>(ts, java_object) == reinterpret_cast<Object*>(java_object)) {
+ if (ts.Env()->locals.Contains(java_object)) {
+ return JNILocalRefType;
+ }
+ }
+
+ return JNIInvalidRefType;
+ }
}
};
@@ -2699,3 +2769,20 @@
}
} // namespace art
+
+std::ostream& operator<<(std::ostream& os, const jobjectRefType& rhs) {
+ switch (rhs) {
+ case JNIInvalidRefType:
+ os << "JNIInvalidRefType";
+ return os;
+ case JNILocalRefType:
+ os << "JNILocalRefType";
+ return os;
+ case JNIGlobalRefType:
+ os << "JNIGlobalRefType";
+ return os;
+ case JNIWeakGlobalRefType:
+ os << "JNIWeakGlobalRefType";
+ return os;
+ }
+}
diff --git a/src/jni_internal.h b/src/jni_internal.h
index 98ce7c8..6d1ff96 100644
--- a/src/jni_internal.h
+++ b/src/jni_internal.h
@@ -81,4 +81,6 @@
} // namespace art
+std::ostream& operator<<(std::ostream& os, const jobjectRefType& rhs);
+
#endif // ART_SRC_JNI_INTERNAL_H_
diff --git a/src/jni_internal_test.cc b/src/jni_internal_test.cc
index 81fda42..0964b62 100644
--- a/src/jni_internal_test.cc
+++ b/src/jni_internal_test.cc
@@ -19,11 +19,17 @@
Runtime::Current()->GetJavaVM()->verbose_jni = true;
env_ = Thread::Current()->GetJniEnv();
+
aioobe_ = env_->FindClass("java/lang/ArrayIndexOutOfBoundsException");
CHECK(aioobe_ != NULL);
+
+ sioobe_ = env_->FindClass("java/lang/StringIndexOutOfBoundsException");
+ CHECK(sioobe_ != NULL);
}
+
JNIEnv* env_;
jclass aioobe_;
+ jclass sioobe_;
};
TEST_F(JniInternalTest, AllocObject) {
@@ -398,6 +404,23 @@
ASSERT_FALSE(env_->IsAssignableFrom(string_class, object_class));
}
+TEST_F(JniInternalTest, GetObjectRefType) {
+ jclass local = env_->FindClass("java/lang/Object");
+ ASSERT_TRUE(local != NULL);
+ EXPECT_EQ(JNILocalRefType, env_->GetObjectRefType(local));
+
+ jobject global = env_->NewGlobalRef(local);
+ EXPECT_EQ(JNIGlobalRefType, env_->GetObjectRefType(global));
+
+ jweak weak_global = env_->NewWeakGlobalRef(local);
+ EXPECT_EQ(JNIWeakGlobalRefType, env_->GetObjectRefType(weak_global));
+
+ jobject invalid = reinterpret_cast<jobject>(this);
+ EXPECT_EQ(JNIInvalidRefType, env_->GetObjectRefType(invalid));
+
+ // TODO: invoke a native method and test that its arguments are considered local references.
+}
+
TEST_F(JniInternalTest, NewStringUTF) {
EXPECT_TRUE(env_->NewStringUTF(NULL) == NULL);
jstring s;
@@ -431,6 +454,47 @@
// TODO: check some non-ASCII strings.
}
+TEST_F(JniInternalTest, GetStringLength_GetStringUTFLength) {
+ // Already tested in the NewString/NewStringUTF tests.
+}
+
+TEST_F(JniInternalTest, GetStringRegion_GetStringUTFRegion) {
+ jstring s = env_->NewStringUTF("hello");
+ ASSERT_TRUE(s != NULL);
+
+ env_->GetStringRegion(s, -1, 0, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+ env_->GetStringRegion(s, 0, -1, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+ env_->GetStringRegion(s, 0, 10, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+ env_->GetStringRegion(s, 10, 1, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+
+ jchar chars[4] = { 'x', 'x', 'x', 'x' };
+ env_->GetStringRegion(s, 1, 2, &chars[1]);
+ EXPECT_EQ('x', chars[0]);
+ EXPECT_EQ('e', chars[1]);
+ EXPECT_EQ('l', chars[2]);
+ EXPECT_EQ('x', chars[3]);
+
+ env_->GetStringUTFRegion(s, -1, 0, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+ env_->GetStringUTFRegion(s, 0, -1, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+ env_->GetStringUTFRegion(s, 0, 10, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+ env_->GetStringUTFRegion(s, 10, 1, NULL);
+ EXPECT_EXCEPTION(sioobe_);
+
+ char bytes[4] = { 'x', 'x', 'x', 'x' };
+ env_->GetStringUTFRegion(s, 1, 2, &bytes[1]);
+ EXPECT_EQ('x', bytes[0]);
+ EXPECT_EQ('e', bytes[1]);
+ EXPECT_EQ('l', bytes[2]);
+ EXPECT_EQ('x', bytes[3]);
+}
+
TEST_F(JniInternalTest, GetObjectArrayElement_SetObjectArrayElement) {
jclass c = env_->FindClass("[Ljava/lang/Object;");
ASSERT_TRUE(c != NULL);
@@ -1377,4 +1441,17 @@
env_->ExceptionClear();
}
+// TODO: this test is DISABLED until we can actually run java.nio.Buffer's <init>.
+TEST_F(JniInternalTest, DISABLED_NewDirectBuffer_GetDirectBufferAddress_GetDirectBufferCapacity) {
+ jclass buffer_class = env_->FindClass("java/nio/Buffer");
+ ASSERT_TRUE(buffer_class != NULL);
+
+ char bytes[1024];
+ jobject buffer = env_->NewDirectByteBuffer(bytes, sizeof(bytes));
+ ASSERT_TRUE(buffer != NULL);
+ ASSERT_TRUE(env_->IsInstanceOf(buffer, buffer_class));
+ ASSERT_TRUE(env_->GetDirectBufferAddress(buffer) == bytes);
+ ASSERT_TRUE(env_->GetDirectBufferCapacity(buffer) == sizeof(bytes));
+}
+
}
diff --git a/src/object.h b/src/object.h
index 93e7a7a..c8b8ec3 100644
--- a/src/object.h
+++ b/src/object.h
@@ -1619,23 +1619,10 @@
// Create a modified UTF-8 encoded std::string from a java/lang/String object.
std::string ToModifiedUtf8() const {
- std::string result;
- for (int32_t i = 0; i < GetLength(); i++) {
- uint16_t ch = CharAt(i);
- // The most common case is (ch > 0 && ch <= 0x7f).
- if (ch == 0 || ch > 0x7f) {
- if (ch > 0x07ff) {
- result.push_back((ch >> 12) | 0xe0);
- result.push_back(((ch >> 6) & 0x3f) | 0x80);
- result.push_back((ch & 0x3f) | 0x80);
- } else { // (ch > 0x7f || ch == 0)
- result.push_back((ch >> 6) | 0xc0);
- result.push_back((ch & 0x3f) | 0x80);
- }
- } else {
- result.push_back(ch);
- }
- }
+ uint16_t* chars = array_->GetData() + offset_;
+ size_t byte_count(CountUtf8Bytes(chars, count_));
+ std::string result(byte_count, char(0));
+ ConvertUtf16ToModifiedUtf8(&result[0], chars, count_);
return result;
}
diff --git a/src/utf.cc b/src/utf.cc
index 81c93ca..9356197 100644
--- a/src/utf.cc
+++ b/src/utf.cc
@@ -2,6 +2,8 @@
#include "utf.h"
+#include "logging.h"
+
namespace art {
size_t CountModifiedUtf8Chars(const char* utf8) {
@@ -31,6 +33,24 @@
}
}
+void ConvertUtf16ToModifiedUtf8(char* utf8_out, const uint16_t* utf16_in, size_t char_count) {
+ while (char_count--) {
+ uint16_t ch = *utf16_in++;
+ if (ch > 0 && ch <= 0x7f) {
+ *utf8_out++ = ch;
+ } else {
+ if (ch > 0x07ff) {
+ *utf8_out++ = (ch >> 12) | 0xe0;
+ *utf8_out++ = ((ch >> 6) & 0x3f) | 0x80;
+ *utf8_out++ = (ch & 0x3f) | 0x80;
+ } else /*(ch > 0x7f || ch == 0)*/ {
+ *utf8_out++ = (ch >> 6) | 0xc0;
+ *utf8_out++ = (ch & 0x3f) | 0x80;
+ }
+ }
+ }
+}
+
int32_t ComputeUtf16Hash(const uint16_t* chars, size_t char_count) {
int32_t hash = 0;
while (char_count--) {
diff --git a/src/utf.h b/src/utf.h
index a9d29a6..2c22f87 100644
--- a/src/utf.h
+++ b/src/utf.h
@@ -6,6 +6,12 @@
#include <stddef.h>
#include <stdint.h>
+/*
+ * All UTF-8 in art is actually modified UTF-8. Mostly, this distinction
+ * doesn't matter.
+ *
+ * See http://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8 for the details.
+ */
namespace art {
/*
@@ -20,10 +26,17 @@
size_t CountUtf8Bytes(const uint16_t* chars, size_t char_count);
/*
- * Convert Modified UTF-8 to UTF-16.
- * http://en.wikipedia.org/wiki/UTF-8#Modified_UTF-8
+ * Convert from Modified UTF-8 to UTF-16.
*/
-void ConvertModifiedUtf8ToUtf16(uint16_t* utf16_data_out, const char* utf8_data_in);
+void ConvertModifiedUtf8ToUtf16(uint16_t* utf16_out, const char* utf8_in);
+
+/*
+ * Convert from UTF-16 to Modified UTF-8. Note that the output is _not_
+ * NUL-terminated. You probably need to call CountUtf8Bytes before calling
+ * this anyway, so if you want a NUL-terminated string, you know where to
+ * put the NUL byte.
+ */
+void ConvertUtf16ToModifiedUtf8(char* utf8_out, const uint16_t* utf16_in, size_t char_count);
/*
* The java.lang.String hashCode() algorithm.
diff --git a/src/utils.h b/src/utils.h
index 4283f47..229d123 100644
--- a/src/utils.h
+++ b/src/utils.h
@@ -125,7 +125,7 @@
static inline std::string PrintableString(const StringT& s) {
std::string result;
result += '"';
- for (typename StringT::iterator it = s.begin(); it != s.end(); ++it) {
+ for (typename StringT::const_iterator it = s.begin(); it != s.end(); ++it) {
char ch = *it;
if (NeedsEscaping(ch)) {
StringAppendF(&result, "\\x%02x", ch & 0xff);