diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-01-10 19:00:09 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-01-10 19:00:09 +0000 |
commit | 11c2fa0ce8936d19e45cd6d1201dc2c8b2296a6a (patch) | |
tree | 899a2d71e3fd4029dedb2552f696933d8edcfad6 | |
parent | 8638d84afdfd8ac3e4d049a328b31dfb27bf2f8a (diff) | |
parent | 31a261d502eeac8ffe7b03829f2450c8fb794c9c (diff) | |
download | libnativehelper-aml_tz5_341510010.tar.gz |
Snap for 11296156 from 31a261d502eeac8ffe7b03829f2450c8fb794c9c to mainline-tzdata5-releaseaml_tz5_341510070aml_tz5_341510050aml_tz5_341510010aml_tz5_341510010
Change-Id: I9cb6983209f33163e756d34bdfb5744d1de18f01
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | README.md | 2 | ||||
-rw-r--r-- | header_only_include/nativehelper/nativehelper_utils.h | 2 | ||||
-rw-r--r-- | header_only_include/nativehelper/scoped_local_ref.h | 2 | ||||
-rw-r--r-- | header_only_include/nativehelper/scoped_utf_chars.h | 12 | ||||
-rw-r--r-- | header_only_include/nativehelper/utils.h | 158 | ||||
-rw-r--r-- | include/nativehelper/Utils.h | 19 | ||||
-rw-r--r-- | tests_mts/jni/libnativehelper_test.cpp | 162 |
8 files changed, 355 insertions, 3 deletions
@@ -36,6 +36,7 @@ cc_defaults { "-std=c11", ], shared_libs: ["liblog"], + export_shared_lib_headers: ["liblog"], } cc_library_headers { @@ -40,6 +40,7 @@ See: * [nativehelper/scoped_primitive_array.h](header_only_include/nativehelper/scoped_primitive_array.h) * [nativehelper/scoped_local_ref.h](header_only_include/nativehelper/scoped_local_ref.h) * [nativehelper/scoped_local_frame.h](header_only_include/nativehelper/scoped_local_frame.h) +* [nativehelper/utils.h](header_only_include/nativehelper/utils.h) ### jni_platform_headers @@ -76,6 +77,7 @@ See: * [nativehelper/ScopedPrimitiveArray.h](include/nativehelper/ScopedPrimitiveArray.h) * [nativehelper/ScopedStringChars.h](include/nativehelper/ScopedStringChars.h) * [nativehelper/toStringArray.h](include/nativehelper/toStringArray.h) +* [nativehelper/Utils.h](include/nativehelper/Utils.h) ### libnativehelper_compat_libc++ diff --git a/header_only_include/nativehelper/nativehelper_utils.h b/header_only_include/nativehelper/nativehelper_utils.h index 9cb8195..6c95c68 100644 --- a/header_only_include/nativehelper/nativehelper_utils.h +++ b/header_only_include/nativehelper/nativehelper_utils.h @@ -14,6 +14,8 @@ * limitations under the License. */ +/** JNI utils for nativehelper-internal use. */ + #pragma once #include <jni.h> diff --git a/header_only_include/nativehelper/scoped_local_ref.h b/header_only_include/nativehelper/scoped_local_ref.h index cd35a88..32ae885 100644 --- a/header_only_include/nativehelper/scoped_local_ref.h +++ b/header_only_include/nativehelper/scoped_local_ref.h @@ -23,6 +23,8 @@ #include "nativehelper_utils.h" // A smart pointer that deletes a JNI local reference when it goes out of scope. +// +// For creating a `ScopedLocalRef<jstring>`, consider using `CREATE_UTF_OR_RETURN`. template<typename T> class ScopedLocalRef { public: diff --git a/header_only_include/nativehelper/scoped_utf_chars.h b/header_only_include/nativehelper/scoped_utf_chars.h index 363ff42..25de0fc 100644 --- a/header_only_include/nativehelper/scoped_utf_chars.h +++ b/header_only_include/nativehelper/scoped_utf_chars.h @@ -23,6 +23,11 @@ #include "nativehelper_utils.h" +// Protect this with __has_include to cope with `stl: "none"` users. +#if __has_include(<string_view>) +#include <string_view> +#endif + // A smart pointer that provides read-only access to a Java string's UTF chars. // Unlike GetStringUTFChars, we throw NullPointerException rather than abort if // passed a null jstring, and c_str will return nullptr. @@ -32,6 +37,8 @@ // if (name.c_str() == nullptr) { // return nullptr; // } +// +// Also consider using `GET_UTF_OR_RETURN`, a shorthand for the 4 lines above. class ScopedUtfChars { public: ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) { @@ -84,6 +91,10 @@ class ScopedUtfChars { return utf_chars_[n]; } +#if __has_include(<string_view>) + operator std::string_view() const { return utf_chars_; } +#endif + private: JNIEnv* env_; jstring string_; @@ -91,4 +102,3 @@ class ScopedUtfChars { DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars); }; - diff --git a/header_only_include/nativehelper/utils.h b/header_only_include/nativehelper/utils.h new file mode 100644 index 0000000..12f591b --- /dev/null +++ b/header_only_include/nativehelper/utils.h @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * JNI utils for external use. + * + * This file may only be included by C++ code. + */ + +#pragma once + +#include <jni.h> + +#include <string> + +#include "nativehelper/scoped_local_ref.h" +#include "nativehelper/scoped_utf_chars.h" + +namespace android { +namespace jnihelp { + +// Implementation details. DO NOT use directly. +namespace internal { + +[[maybe_unused]] static const char* GetCStr(const char* str) { return str; } +[[maybe_unused]] static const char* GetCStr(const std::string& str) { return str.c_str(); } + +} // namespace internal + +// A class that implicitly casts to the default values of various JNI types. +// Used for returning from a JNI method when an exception occurs, where we don't care about the +// return value. +class JniDefaultValue { + public: + operator jboolean() const { return JNI_FALSE; } + operator jbyte() const { return 0; } + operator jchar() const { return 0; } + operator jshort() const { return 0; } + operator jint() const { return 0; } + operator jlong() const { return 0; } + operator jfloat() const { return 0; } + operator jdouble() const { return 0; } + operator jobject() const { return nullptr; } + operator jclass() const { return nullptr; } + operator jstring() const { return nullptr; } + operator jarray() const { return nullptr; } + operator jobjectArray() const { return nullptr; } + operator jbooleanArray() const { return nullptr; } + operator jbyteArray() const { return nullptr; } + operator jcharArray() const { return nullptr; } + operator jshortArray() const { return nullptr; } + operator jintArray() const { return nullptr; } + operator jlongArray() const { return nullptr; } + operator jfloatArray() const { return nullptr; } + operator jdoubleArray() const { return nullptr; } + operator jthrowable() const { return nullptr; } +}; + +// Gets `ScopedUtfChars` from a `jstring` expression. +// +// Throws `NullPointerException` and returns the default value if the given `jstring` is a null +// pointer. +// +// Examples: +// +// - If the function returns a value: +// +// jobject MyJniMethod(JNIEnv* env, jstring j_str) { +// ScopedUtfChars str = GET_UTF_OR_RETURN(env, j_str); +// // Safely use `str` here... +// } +// +// - If the function returns void: +// +// void MyJniMethod(JNIEnv* env, jstring j_str) { +// ScopedUtfChars str = GET_UTF_OR_RETURN_VOID(env, j_str); +// // Safely use `str` here... +// } +// +// The idiomatic way to construct an `std::string` using this macro (an additional string copy is +// performed): +// +// jobject MyJniMethod(JNIEnv* env, jstring j_str) { +// std::string str(GET_UTF_OR_RETURN(env, j_str)); +// // Safely use `str` here... +// } +#define GET_UTF_OR_RETURN(env, expr) \ + GET_UTF_OR_RETURN_IMPL_((env), (expr), android::jnihelp::JniDefaultValue()) +#define GET_UTF_OR_RETURN_VOID(env, expr) GET_UTF_OR_RETURN_IMPL_((env), (expr)) + +#define GET_UTF_OR_RETURN_IMPL_(env, expr, ...) \ + ({ \ + ScopedUtfChars __or_return_scoped_utf_chars(env, expr); \ + if (__or_return_scoped_utf_chars.c_str() == nullptr) { \ + /* Return with a pending exception from `ScopedUtfChars`. */ \ + return __VA_ARGS__; \ + } \ + std::move(__or_return_scoped_utf_chars); \ + }) + +// Creates `ScopedLocalRef<jstring>` from a `const char*` or `std::string` expression using +// NewStringUTF. +// +// Throws `OutOfMemoryError` and returns the default value if the system runs out of memory. +// +// Examples: +// +// - If the function returns a value: +// +// jobject MyJniMethod(JNIEnv* env) { +// std::string str = "foo"; +// ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN(env, str); +// // Safely use `j_str` here... +// } +// +// - If the function returns void: +// +// void MyJniMethod(JNIEnv* env) { +// std::string str = "foo"; +// ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN_VOID(env, str); +// // Safely use `j_str` here... +// } +#define CREATE_UTF_OR_RETURN(env, expr) \ + CREATE_UTF_OR_RETURN_IMPL_((env), (expr), android::jnihelp::JniDefaultValue()) +#define CREATE_UTF_OR_RETURN_VOID(env, expr) CREATE_UTF_OR_RETURN_IMPL_((env), (expr)) + +#define CREATE_UTF_OR_RETURN_IMPL_(env, expr, ...) \ + ({ \ + const char* __or_return_c_str; \ + ScopedLocalRef<jstring> __or_return_local_ref( \ + env, \ + env->NewStringUTF(__or_return_c_str = android::jnihelp::internal::GetCStr(expr))); \ + /* `*__or_return_c_str` may be freed here, but we only compare the pointer against \ + * nullptr. DO NOT DEREFERENCE `*__or_return_c_str` after this point. */ \ + /* `NewStringUTF` returns nullptr when OOM or the input is nullptr, but only throws an \ + * exception when OOM. */ \ + if (__or_return_local_ref == nullptr && __or_return_c_str != nullptr) { \ + /* Return with a pending exception from `NewStringUTF`. */ \ + return __VA_ARGS__; \ + } \ + std::move(__or_return_local_ref); \ + }) + +} // namespace jnihelp +} // namespace android diff --git a/include/nativehelper/Utils.h b/include/nativehelper/Utils.h new file mode 100644 index 0000000..c05b2f1 --- /dev/null +++ b/include/nativehelper/Utils.h @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include <nativehelper/utils.h> diff --git a/tests_mts/jni/libnativehelper_test.cpp b/tests_mts/jni/libnativehelper_test.cpp index 5371004..bd448f9 100644 --- a/tests_mts/jni/libnativehelper_test.cpp +++ b/tests_mts/jni/libnativehelper_test.cpp @@ -14,9 +14,15 @@ * limitations under the License. */ -#include <libnativehelper_test.h> +#include "libnativehelper_test.h" -#include <nativetesthelper_jni/utils.h> +#include <memory> + +#include "jni.h" +#include "nativehelper/scoped_local_ref.h" +#include "nativehelper/scoped_utf_chars.h" +#include "nativehelper/utils.h" +#include "nativetesthelper_jni/utils.h" void LibnativehelperTest::SetUp() { int result = GetJavaVM()->GetEnv(reinterpret_cast<void**>(&mEnv), JNI_VERSION_1_6); @@ -27,3 +33,155 @@ void LibnativehelperTest::SetUp() { void LibnativehelperTest::TearDown() { mEnv = nullptr; } + +TEST_F(LibnativehelperTest, GetUtfOrReturn) { + ScopedLocalRef<jstring> j_str(mEnv, mEnv->NewStringUTF("foo")); + std::unique_ptr<ScopedUtfChars> result; + + jint ret = [&](JNIEnv* env) -> jint { + ScopedUtfChars str = GET_UTF_OR_RETURN(env, j_str.get()); + result.reset(new ScopedUtfChars(std::move(str))); + return 1; + }(mEnv); + + EXPECT_EQ(result->c_str(), std::string_view("foo")); + EXPECT_FALSE(mEnv->ExceptionCheck()); + EXPECT_EQ(ret, 1); +} + +TEST_F(LibnativehelperTest, GetUtfOrReturnVoid) { + ScopedLocalRef<jstring> j_str(mEnv, mEnv->NewStringUTF("foo")); + std::unique_ptr<ScopedUtfChars> result; + + [&](JNIEnv* env) -> void { + ScopedUtfChars str = GET_UTF_OR_RETURN_VOID(env, j_str.get()); + result.reset(new ScopedUtfChars(std::move(str))); + }(mEnv); + + EXPECT_EQ(result->c_str(), std::string_view("foo")); + EXPECT_FALSE(mEnv->ExceptionCheck()); +} + +TEST_F(LibnativehelperTest, GetUtfOrReturnFailed) { + jint ret = [&](JNIEnv* env) -> jint { + ScopedUtfChars str = GET_UTF_OR_RETURN(env, nullptr); + return 1; + }(mEnv); + + EXPECT_TRUE(mEnv->ExceptionCheck()); + EXPECT_EQ(ret, 0); + + mEnv->ExceptionClear(); +} + +TEST_F(LibnativehelperTest, GetUtfOrReturnVoidFailed) { + bool execution_completed = false; + + [&](JNIEnv* env) -> void { + ScopedUtfChars str = GET_UTF_OR_RETURN_VOID(env, nullptr); + execution_completed = true; + }(mEnv); + + EXPECT_TRUE(mEnv->ExceptionCheck()); + EXPECT_FALSE(execution_completed); + + mEnv->ExceptionClear(); +} + +TEST_F(LibnativehelperTest, CreateUtfOrReturn) { + std::unique_ptr<ScopedLocalRef<jstring>> result; + + jint ret = [&](JNIEnv* env) -> jint { + ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN(env, "foo"); + result.reset(new ScopedLocalRef<jstring>(std::move(j_str))); + return 1; + }(mEnv); + + ScopedUtfChars str(mEnv, result->get()); + EXPECT_EQ(str.c_str(), std::string_view("foo")); + EXPECT_FALSE(mEnv->ExceptionCheck()); + EXPECT_EQ(ret, 1); +} + +class MyString : public std::string { + public: + explicit MyString(const char* c_str) : std::string(c_str) {} + + // Force clear the string to catch use-after-free issues. + ~MyString() { clear(); } +}; + +// `expr` creates a temporary object and evaluates to it. +TEST_F(LibnativehelperTest, CreateUtfOrReturnExprEvaluatesToTemporary) { + std::unique_ptr<ScopedLocalRef<jstring>> result; + + jint ret = [&](JNIEnv* env) -> jint { + ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN(env, MyString("foo")); + result.reset(new ScopedLocalRef<jstring>(std::move(j_str))); + return 1; + }(mEnv); + + ScopedUtfChars str(mEnv, result->get()); + EXPECT_EQ(str.c_str(), std::string_view("foo")); + EXPECT_FALSE(mEnv->ExceptionCheck()); + EXPECT_EQ(ret, 1); +} + +// `expr` creates a temporary object and evaluates to something else backed by it. +TEST_F(LibnativehelperTest, CreateUtfOrReturnExprEvaluatesToValueBackedByTemporary) { + std::unique_ptr<ScopedLocalRef<jstring>> result; + + jint ret = [&](JNIEnv* env) -> jint { + ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN(env, MyString("foo").c_str()); + result.reset(new ScopedLocalRef<jstring>(std::move(j_str))); + return 1; + }(mEnv); + + ScopedUtfChars str(mEnv, result->get()); + EXPECT_EQ(str.c_str(), std::string_view("foo")); + EXPECT_FALSE(mEnv->ExceptionCheck()); + EXPECT_EQ(ret, 1); +} + +TEST_F(LibnativehelperTest, CreateUtfOrReturnVoid) { + std::unique_ptr<ScopedLocalRef<jstring>> result; + + [&](JNIEnv* env) -> void { + ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN_VOID(env, "foo"); + result.reset(new ScopedLocalRef<jstring>(std::move(j_str))); + }(mEnv); + + ScopedUtfChars str(mEnv, result->get()); + EXPECT_EQ(str.c_str(), std::string_view("foo")); + EXPECT_FALSE(mEnv->ExceptionCheck()); +} + +TEST_F(LibnativehelperTest, CreateUtfOrReturnFailed) { + JNINativeInterface interface; + interface.NewStringUTF = [](JNIEnv*, const char*) -> jstring { return nullptr; }; + JNIEnv fake_env; + fake_env.functions = &interface; + + jint ret = [&](JNIEnv* env) -> jint { + ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN(env, "foo"); + return 1; + }(&fake_env); + + EXPECT_EQ(ret, 0); +} + +TEST_F(LibnativehelperTest, CreateUtfOrReturnVoidFailed) { + JNINativeInterface interface; + interface.NewStringUTF = [](JNIEnv*, const char*) -> jstring { return nullptr; }; + JNIEnv fake_env; + fake_env.functions = &interface; + + bool execution_completed = false; + + [&](JNIEnv* env) -> void { + ScopedLocalRef<jstring> j_str = CREATE_UTF_OR_RETURN_VOID(env, "foo"); + execution_completed = true; + }(&fake_env); + + EXPECT_FALSE(execution_completed); +} |