From b23f8b6eebc2d24b49ac25cf6ef0f55d76421849 Mon Sep 17 00:00:00 2001 From: cpeng Date: Mon, 25 Aug 2025 08:35:43 +0800 Subject: [PATCH] android 13 from xiaosuan --- ALog-priv.h | 78 + Android.bp | 248 +++ DlHelp.c | 82 + DlHelp.h | 32 + ExpandableString.c | 48 + ExpandableString.h | 45 + JNIHelp.c | 389 +++++ JNIPlatformHelp.c | 65 + JniConstants.c | 195 +++ JniConstants.h | 59 + JniInvocation-priv.h | 28 + JniInvocation.c | 224 +++ NOTICE | 190 +++ OWNERS | 3 + PREUPLOAD.cfg | 2 + README.md | 97 ++ TEST_MAPPING | 19 + file_descriptor_jni.c | 47 + .../nativehelper/nativehelper_utils.h | 53 + .../nativehelper/scoped_local_frame.h | 38 + .../nativehelper/scoped_local_ref.h | 90 + .../nativehelper/scoped_primitive_array.h | 148 ++ .../nativehelper/scoped_string_chars.h | 74 + .../nativehelper/scoped_utf_chars.h | 94 ++ include/android/file_descriptor_jni.h | 88 + include/nativehelper/JNIHelp.h | 511 ++++++ include/nativehelper/ScopedLocalFrame.h | 20 + include/nativehelper/ScopedLocalRef.h | 21 + include/nativehelper/ScopedPrimitiveArray.h | 21 + include/nativehelper/ScopedStringChars.h | 21 + include/nativehelper/ScopedUtfChars.h | 21 + include/nativehelper/toStringArray.h | 64 + include_jni/jni.h | 1141 +++++++++++++ .../nativehelper/JNIPlatformHelp.h | 133 ++ include_platform/nativehelper/JniInvocation.h | 125 ++ .../nativehelper/detail/signature_checker.h | 1441 +++++++++++++++++ .../nativehelper/jni_macros.h | 283 ++++ libnativehelper.map.txt | 30 + libnativehelper_lazy.c | 275 ++++ libnativehelper_lazy.h | 25 + tests/Android.bp | 103 ++ tests/ExpandableString_test.cpp | 109 ++ tests/JniInvocation_test.cpp | 71 + tests/JniSafeRegisterNativeMethods_test.cpp | 1281 +++++++++++++++ tests/jni_gtest/Android.bp | 20 + tests/jni_gtest/base/nativehelper/jni_gtest.h | 124 ++ tests/libnativehelper_api_test.c | 26 + tests/libnativehelper_lazy_test.cpp | 65 + tests/scoped_local_frame_test.cpp | 23 + tests/scoped_local_ref_test.cpp | 25 + tests/scoped_primitive_array_test.cpp | 34 + tests/scoped_string_chars_test.cpp | 24 + tests/scoped_utf_chars_test.cpp | 24 + tests_mts/Android.bp | 72 + tests_mts/AndroidManifest.xml | 30 + tests_mts/OWNERS | 5 + tests_mts/README.md | 51 + tests_mts/jni/Android.bp | 56 + tests_mts/jni/jni_helper_jni.cpp | 141 ++ tests_mts/jni/jni_invocation_test.cpp | 42 + tests_mts/jni/libnativehelper_test.cpp | 29 + tests_mts/jni/libnativehelper_test.h | 29 + .../art/libnativehelper/JniHelpTest.java | 178 ++ .../LibnativehelperGTests.java | 26 + .../LibnativehelperLazyGTests.java | 26 + 65 files changed, 9182 insertions(+) create mode 100644 ALog-priv.h create mode 100644 Android.bp create mode 100644 DlHelp.c create mode 100644 DlHelp.h create mode 100644 ExpandableString.c create mode 100644 ExpandableString.h create mode 100644 JNIHelp.c create mode 100644 JNIPlatformHelp.c create mode 100644 JniConstants.c create mode 100644 JniConstants.h create mode 100644 JniInvocation-priv.h create mode 100644 JniInvocation.c create mode 100644 NOTICE create mode 100644 OWNERS create mode 100644 PREUPLOAD.cfg create mode 100644 README.md create mode 100644 TEST_MAPPING create mode 100644 file_descriptor_jni.c create mode 100644 header_only_include/nativehelper/nativehelper_utils.h create mode 100644 header_only_include/nativehelper/scoped_local_frame.h create mode 100644 header_only_include/nativehelper/scoped_local_ref.h create mode 100644 header_only_include/nativehelper/scoped_primitive_array.h create mode 100644 header_only_include/nativehelper/scoped_string_chars.h create mode 100644 header_only_include/nativehelper/scoped_utf_chars.h create mode 100644 include/android/file_descriptor_jni.h create mode 100644 include/nativehelper/JNIHelp.h create mode 100644 include/nativehelper/ScopedLocalFrame.h create mode 100644 include/nativehelper/ScopedLocalRef.h create mode 100644 include/nativehelper/ScopedPrimitiveArray.h create mode 100644 include/nativehelper/ScopedStringChars.h create mode 100644 include/nativehelper/ScopedUtfChars.h create mode 100644 include/nativehelper/toStringArray.h create mode 100644 include_jni/jni.h create mode 100644 include_platform/nativehelper/JNIPlatformHelp.h create mode 100644 include_platform/nativehelper/JniInvocation.h create mode 100644 include_platform_header_only/nativehelper/detail/signature_checker.h create mode 100644 include_platform_header_only/nativehelper/jni_macros.h create mode 100644 libnativehelper.map.txt create mode 100644 libnativehelper_lazy.c create mode 100644 libnativehelper_lazy.h create mode 100644 tests/Android.bp create mode 100644 tests/ExpandableString_test.cpp create mode 100644 tests/JniInvocation_test.cpp create mode 100644 tests/JniSafeRegisterNativeMethods_test.cpp create mode 100644 tests/jni_gtest/Android.bp create mode 100644 tests/jni_gtest/base/nativehelper/jni_gtest.h create mode 100644 tests/libnativehelper_api_test.c create mode 100644 tests/libnativehelper_lazy_test.cpp create mode 100644 tests/scoped_local_frame_test.cpp create mode 100644 tests/scoped_local_ref_test.cpp create mode 100644 tests/scoped_primitive_array_test.cpp create mode 100644 tests/scoped_string_chars_test.cpp create mode 100644 tests/scoped_utf_chars_test.cpp create mode 100644 tests_mts/Android.bp create mode 100644 tests_mts/AndroidManifest.xml create mode 100644 tests_mts/OWNERS create mode 100644 tests_mts/README.md create mode 100644 tests_mts/jni/Android.bp create mode 100644 tests_mts/jni/jni_helper_jni.cpp create mode 100644 tests_mts/jni/jni_invocation_test.cpp create mode 100644 tests_mts/jni/libnativehelper_test.cpp create mode 100644 tests_mts/jni/libnativehelper_test.h create mode 100644 tests_mts/src/com/android/art/libnativehelper/JniHelpTest.java create mode 100644 tests_mts/src/com/android/art/libnativehelper/LibnativehelperGTests.java create mode 100644 tests_mts/src/com/android/art/libnativehelper/LibnativehelperLazyGTests.java diff --git a/ALog-priv.h b/ALog-priv.h new file mode 100644 index 0000000..afca5db --- /dev/null +++ b/ALog-priv.h @@ -0,0 +1,78 @@ +/* + * Copyright 2013 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 + +#ifndef LOG_NDEBUG +#ifdef NDEBUG +#define LOG_NDEBUG 1 +#else +#define LOG_NDEBUG 0 +#endif +#endif + + +/* + * Basic log message macros intended to emulate the behavior of log/log.h + * in system core. This should be dependent only on ndk exposed logging + * functionality. + */ + +#ifndef ALOG +#define ALOG(priority, tag, fmt, ...) \ + __android_log_print(ANDROID_##priority, tag, fmt, __VA_ARGS__) +#endif + +#ifndef ALOGV +#if LOG_NDEBUG +#define ALOGV(...) ((void)0) +#else +#define ALOGV(...) ((void)ALOG(LOG_VERBOSE, LOG_TAG, __VA_ARGS__)) +#endif +#endif + +#ifndef ALOGD +#define ALOGD(...) ((void)ALOG(LOG_DEBUG, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGI +#define ALOGI(...) ((void)ALOG(LOG_INFO, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGW +#define ALOGW(...) ((void)ALOG(LOG_WARN, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGE +#define ALOGE(...) ((void)ALOG(LOG_ERROR, LOG_TAG, __VA_ARGS__)) +#endif + +#ifndef ALOGF +#define ALOGF(...) ((void)ALOG(LOG_FATAL, LOG_TAG, __VA_ARGS__)) +#endif + +/* + * Log a fatal error if cond is true. The condition test is inverted from + * assert(3) semantics. The test and message are not stripped from release + * builds + */ +#ifndef ALOG_ALWAYS_FATAL_IF +#define ALOG_ALWAYS_FATAL_IF(cond, ...) \ + if (cond) __android_log_assert(#cond, LOG_TAG, __VA_ARGS__) +#endif + diff --git a/Android.bp b/Android.bp new file mode 100644 index 0000000..91ac351 --- /dev/null +++ b/Android.bp @@ -0,0 +1,248 @@ +// Copyright (C) 2009 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. + +package { + default_applicable_licenses: ["libnativehelper_license"], +} + +// Added automatically by a large-scale-change +// http://go/android-license-faq +license { + name: "libnativehelper_license", + visibility: [":__subpackages__"], + license_kinds: [ + "SPDX-license-identifier-Apache-2.0", + ], + license_text: [ + "NOTICE", + ], +} + +cc_defaults { + name: "libnativehelper_defaults", + cflags: [ + "-fvisibility=protected", + "-std=c11", + ], + shared_libs: ["liblog"], +} + +cc_library_headers { + name: "jni_headers", + host_supported: true, + export_include_dirs: ["include_jni"], + native_bridge_supported: true, + vendor_available: true, + target: { + windows: { + enabled: true, + }, + }, + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], + ramdisk_available: true, + // recovery_available currently required for libchrome (https://r.android.com/799940). + recovery_available: true, + visibility: ["//visibility:public"], + stl: "none", + system_shared_libs: [], + // The minimum sdk version required by users of this module. + sdk_version: "minimum", + // As part of mainline modules(APEX), it should support at least 29(Q) + min_sdk_version: "29", +} + +cc_library_headers { + name: "libnativehelper_header_only", + host_supported: true, + export_include_dirs: [ + "header_only_include", + ], + header_libs: ["jni_headers"], + export_header_lib_headers: ["jni_headers"], + // As part of mainline modules(APEX), it should support at least 29(Q) + min_sdk_version: "29", + sdk_version: "minimum", + apex_available: [ + "//apex_available:platform", + "//apex_available:anyapex", + ], +} + +cc_library_headers { + name: "jni_platform_headers", + host_supported: true, + export_include_dirs: [ + "include_platform_header_only", + ], + header_libs: ["jni_headers"], + export_header_lib_headers: ["jni_headers"], + sdk_version: "minimum", + apex_available: [ + "//apex_available:platform", + "com.android.art", + "com.android.art.debug", + ], + min_sdk_version: "S", +} + +cc_library_shared { + name: "libnativehelper", + defaults: ["libnativehelper_defaults"], + bootstrap: false, + host_supported: true, + srcs: [ + "DlHelp.c", + "ExpandableString.c", + "JNIHelp.c", + "JNIPlatformHelp.c", + "JniConstants.c", + "JniInvocation.c", + "file_descriptor_jni.c", + ], + export_include_dirs: [ + "header_only_include", + "include", + "include_jni", + "include_platform", + "include_platform_header_only", + ], + stl: "none", + stubs: { + symbol_file: "libnativehelper.map.txt", + versions: ["S"], + }, + // Only distributed in the ART Module. + apex_available: [ + "com.android.art", + "com.android.art.debug", + ], + min_sdk_version: "S", +} + +// Lazy loading version of libnativehelper that can be used by code +// that is running before the ART APEX is mounted and +// libnativehelper.so is available. +cc_library_static { + name: "libnativehelper_lazy", + defaults: ["libnativehelper_defaults"], + bootstrap: true, + host_supported: true, + export_include_dirs: [ + "header_only_include", + "include", + "include_jni", + "include_platform", + "include_platform_header_only", + ], + apex_available: ["//apex_available:platform"], + srcs: ["libnativehelper_lazy.c"], + target: { + linux: { + version_script: "libnativehelper.map.txt", + }, + }, +} + +// +// NDK-only build for the target (device), using libc++. +// - Relies only on NDK exposed functionality. +// - This doesn't include JniInvocation. +// + +cc_library { + name: "libnativehelper_compat_libc++", + defaults: ["libnativehelper_defaults"], + header_libs: ["jni_headers"], + cflags: ["-Werror"], + export_header_lib_headers: ["jni_headers"], + export_include_dirs: [ + "header_only_include", + "include", + ], + host_supported: true, + local_include_dirs: [ + "header_only_include", + "include_platform_header_only", + ], + srcs: [ + "ExpandableString.c", + "JNIHelp.c", + ], + min_sdk_version: "29", + sdk_version: "19", + stl: "none", + apex_available: [ + "//apex_available:platform", + "com.android.art", + "com.android.art.debug", + "com.android.extservices", + "com.android.tethering", + ], + visibility: [ + "//art:__subpackages__", + "//cts:__subpackages__", + "//external/perfetto:__subpackages__", + "//frameworks/base/packages/Connectivity/tests/integration:__pkg__", + "//frameworks/base/packages/ConnectivityT:__subpackages__", // TODO: remove after code move + "//frameworks/base/packages/Tethering:__subpackages__", + "//frameworks/libs/net/common/native/bpfmapjni", + "//frameworks/libs/net/common/native/bpfutiljni", + "//libcore:__subpackages__", + "//packages/modules/Connectivity:__subpackages__", + "//packages/modules/ExtServices:__subpackages__", + "//packages/modules/NetworkStack:__subpackages__", + ":__subpackages__", + ], +} + +// The NDK module definitions reside in +// system/extras/module_ndk_libs/libnativehelper in platform, with copies of +// these headers and map.txt. Any changes here should be synced there and vice +// versa. +// +// TODO(b/170644498): Improve tooling to remove this duplication. +// +// ndk_headers { +// name: "ndk_jni.h", +// from: "include_jni", +// to: "", +// srcs: ["include_jni/jni.h"], +// license: "NOTICE", +// } +// +// ndk_headers { +// name: "libnativehelper_ndk_headers", +// from: "include", +// to: "", +// srcs: ["include/android/*.h"], +// license: "NOTICE", +// } +// +// ndk_library { +// name: "libnativehelper", +// symbol_file: "libnativehelper.map.txt", +// first_version: "S", +// } + +// +// Tests. +// + +subdirs = [ + "tests", + "tests_mts", +] diff --git a/DlHelp.c b/DlHelp.c new file mode 100644 index 0000000..3320606 --- /dev/null +++ b/DlHelp.c @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "DlHelp.h" + +#include + +#ifdef WIN32_LEAN_AND_MEAN +#include +#include +#else +#include +#endif + +DlLibrary DlOpenLibrary(const char* filename) { +#ifdef _WIN32 + return LoadLibrary(filename); +#else + // Load with RTLD_NODELETE in order to ensure that libart.so is not unmapped when it is closed. + // This is due to the fact that it is possible that some threads might have yet to finish + // exiting even after JNI_DeleteJavaVM returns, which can lead to segfaults if the library is + // unloaded. + return dlopen(filename, RTLD_NOW | RTLD_NODELETE); +#endif +} + +bool DlCloseLibrary(DlLibrary library) { +#ifdef _WIN32 + return (FreeLibrary(library) == TRUE); +#else + return (dlclose(library) == 0); +#endif +} + +DlSymbol DlGetSymbol(DlLibrary handle, const char* symbol) { +#ifdef _WIN32 + return (DlSymbol) GetProcAddress(handle, symbol); +#else + return dlsym(handle, symbol); +#endif +} + +const char* DlGetError() { +#ifdef _WIN32 + static char buffer[256]; + + DWORD cause = GetLastError(); + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS; + DWORD length = FormatMessageA(flags, NULL, cause, 0, buffer, sizeof(buffer), NULL); + if (length == 0) { + snprintf(buffer, sizeof(buffer), + "Error %lu while retrieving message for error %lu", + GetLastError(), cause); + length = strlen(buffer); + } + + // Trim trailing whitespace. + for (DWORD i = length - 1; i > 0; --i) { + if (!isspace(buffer[i])) { + break; + } + buffer[i] = '\0'; + } + + return buffer; +#else + return dlerror(); +#endif +} diff --git a/DlHelp.h b/DlHelp.h new file mode 100644 index 0000000..6087e62 --- /dev/null +++ b/DlHelp.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 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 +#include + +__BEGIN_DECLS + +typedef void* DlLibrary; +typedef void* DlSymbol; + +DlLibrary DlOpenLibrary(const char* filename); +bool DlCloseLibrary(DlLibrary library); +DlSymbol DlGetSymbol(DlLibrary library, const char* symbol); +const char* DlGetError(); + +__END_DECLS diff --git a/ExpandableString.c b/ExpandableString.c new file mode 100644 index 0000000..d52fdd4 --- /dev/null +++ b/ExpandableString.c @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "ExpandableString.h" + +#include +#include +#include + +void ExpandableStringInitialize(struct ExpandableString *s) { + memset(s, 0, sizeof(*s)); +} + +void ExpandableStringRelease(struct ExpandableString* s) { + free(s->data); + memset(s, 0, sizeof(*s)); +} + +bool ExpandableStringAppend(struct ExpandableString* s, const char* text) { + size_t textSize = strlen(text); + size_t requiredSize = s->dataSize + textSize + 1; + char* data = (char*) realloc(s->data, requiredSize); + if (data == NULL) { + return false; + } + s->data = data; + memcpy(s->data + s->dataSize, text, textSize + 1); + s->dataSize += textSize; + return true; +} + +bool ExpandableStringAssign(struct ExpandableString* s, const char* text) { + ExpandableStringRelease(s); + return ExpandableStringAppend(s, text); +} \ No newline at end of file diff --git a/ExpandableString.h b/ExpandableString.h new file mode 100644 index 0000000..be04f20 --- /dev/null +++ b/ExpandableString.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2020 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 + +#include +#include + +__BEGIN_DECLS + +struct ExpandableString { + size_t dataSize; // The length of the C string data (not including the null-terminator). + char* data; // The C string data. +}; + +// Initialize ExpandableString. +void ExpandableStringInitialize(struct ExpandableString* s); + +// Release memory associated with ExpandableString. +void ExpandableStringRelease(struct ExpandableString* s); + +// Append null-terminated string |text| to ExpandableString, expanding the storage if required. +// Returns true on success, false othewise. +bool ExpandableStringAppend(struct ExpandableString* s, const char* text); + +// Assign null-terminate string |text| to ExpandableString, expanding the storage if required. +// Returns true on success, false othewise. +bool ExpandableStringAssign(struct ExpandableString*s, const char* text); + +__END_DECLS diff --git a/JNIHelp.c b/JNIHelp.c new file mode 100644 index 0000000..fba7f8d --- /dev/null +++ b/JNIHelp.c @@ -0,0 +1,389 @@ +/* + * Copyright (C) 2006 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. + */ + +#include "include/nativehelper/JNIHelp.h" + +#include +#include +#include +#include +#include + +#include + +#define LOG_TAG "JNIHelp" +#include "ALog-priv.h" + +#include "ExpandableString.h" + +// +// Helper methods +// + +static const char* platformStrError(int errnum, char* buf, size_t buflen) { +#ifdef _WIN32 + strerror_s(buf, buflen, errnum); + return buf; +#elif defined(__USE_GNU) && __ANDROID_API__ >= 23 + // char *strerror_r(int errnum, char *buf, size_t buflen); /* GNU-specific */ + return strerror_r(errnum, buf, buflen); +#else + // int strerror_r(int errnum, char *buf, size_t buflen); /* XSI-compliant */ + int rc = strerror_r(errnum, buf, buflen); + if (rc != 0) { + snprintf(buf, buflen, "errno %d", errnum); + } + return buf; +#endif +} + +static jmethodID FindMethod(JNIEnv* env, + const char* className, + const char* methodName, + const char* descriptor) { + // This method is only valid for classes in the core library which are + // not unloaded during the lifetime of managed code execution. + jclass clazz = (*env)->FindClass(env, className); + jmethodID methodId = (*env)->GetMethodID(env, clazz, methodName, descriptor); + (*env)->DeleteLocalRef(env, clazz); + return methodId; +} + +static bool AppendJString(JNIEnv* env, jstring text, struct ExpandableString* dst) { + const char* utfText = (*env)->GetStringUTFChars(env, text, NULL); + if (utfText == NULL) { + return false; + } + bool success = ExpandableStringAppend(dst, utfText); + (*env)->ReleaseStringUTFChars(env, text, utfText); + return success; +} + +/* + * Returns a human-readable summary of an exception object. The buffer will + * be populated with the "binary" class name and, if present, the + * exception message. + */ +static bool GetExceptionSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { + // Summary is ": " + jclass exceptionClass = (*env)->GetObjectClass(env, thrown); // Always succeeds + jmethodID getName = FindMethod(env, "java/lang/Class", "getName", "()Ljava/lang/String;"); + jstring className = (jstring) (*env)->CallObjectMethod(env, exceptionClass, getName); + if (className == NULL) { + ExpandableStringAssign(dst, ""); + (*env)->ExceptionClear(env); + (*env)->DeleteLocalRef(env, exceptionClass); + return false; + } + (*env)->DeleteLocalRef(env, exceptionClass); + exceptionClass = NULL; + + if (!AppendJString(env, className, dst)) { + ExpandableStringAssign(dst, ""); + (*env)->ExceptionClear(env); + (*env)->DeleteLocalRef(env, className); + return false; + } + (*env)->DeleteLocalRef(env, className); + className = NULL; + + jmethodID getMessage = + FindMethod(env, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;"); + jstring message = (jstring) (*env)->CallObjectMethod(env, thrown, getMessage); + if (message == NULL) { + return true; + } + + bool success = (ExpandableStringAppend(dst, ": ") && AppendJString(env, message, dst)); + if (!success) { + // Two potential reasons for reaching here: + // + // 1. managed heap allocation failure (OOME). + // 2. native heap allocation failure for the storage in |dst|. + // + // Attempt to append failure notification, okay to fail, |dst| contains the class name + // of |thrown|. + ExpandableStringAppend(dst, ""); + // Clear OOME if present. + (*env)->ExceptionClear(env); + } + (*env)->DeleteLocalRef(env, message); + message = NULL; + return success; +} + +static jobject NewStringWriter(JNIEnv* env) { + jclass clazz = (*env)->FindClass(env, "java/io/StringWriter"); + jmethodID init = (*env)->GetMethodID(env, clazz, "", "()V"); + jobject instance = (*env)->NewObject(env, clazz, init); + (*env)->DeleteLocalRef(env, clazz); + return instance; +} + +static jstring StringWriterToString(JNIEnv* env, jobject stringWriter) { + jmethodID toString = + FindMethod(env, "java/io/StringWriter", "toString", "()Ljava/lang/String;"); + return (jstring) (*env)->CallObjectMethod(env, stringWriter, toString); +} + +static jobject NewPrintWriter(JNIEnv* env, jobject writer) { + jclass clazz = (*env)->FindClass(env, "java/io/PrintWriter"); + jmethodID init = (*env)->GetMethodID(env, clazz, "", "(Ljava/io/Writer;)V"); + jobject instance = (*env)->NewObject(env, clazz, init, writer); + (*env)->DeleteLocalRef(env, clazz); + return instance; +} + +static bool GetStackTrace(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { + // This function is equivalent to the following Java snippet: + // StringWriter sw = new StringWriter(); + // PrintWriter pw = new PrintWriter(sw); + // thrown.printStackTrace(pw); + // String trace = sw.toString(); + // return trace; + jobject sw = NewStringWriter(env); + if (sw == NULL) { + return false; + } + + jobject pw = NewPrintWriter(env, sw); + if (pw == NULL) { + (*env)->DeleteLocalRef(env, sw); + return false; + } + + jmethodID printStackTrace = + FindMethod(env, "java/lang/Throwable", "printStackTrace", "(Ljava/io/PrintWriter;)V"); + (*env)->CallVoidMethod(env, thrown, printStackTrace, pw); + + jstring trace = StringWriterToString(env, sw); + + (*env)->DeleteLocalRef(env, pw); + pw = NULL; + (*env)->DeleteLocalRef(env, sw); + sw = NULL; + + if (trace == NULL) { + return false; + } + + bool success = AppendJString(env, trace, dst); + (*env)->DeleteLocalRef(env, trace); + return success; +} + +static void GetStackTraceOrSummary(JNIEnv* env, jthrowable thrown, struct ExpandableString* dst) { + // This method attempts to get a stack trace or summary info for an exception. + // The exception may be provided in the |thrown| argument to this function. + // If |thrown| is NULL, then any pending exception is used if it exists. + + // Save pending exception, callees may raise other exceptions. Any pending exception is + // rethrown when this function exits. + jthrowable pendingException = (*env)->ExceptionOccurred(env); + if (pendingException != NULL) { + (*env)->ExceptionClear(env); + } + + if (thrown == NULL) { + if (pendingException == NULL) { + ExpandableStringAssign(dst, ""); + return; + } + thrown = pendingException; + } + + if (!GetStackTrace(env, thrown, dst)) { + // GetStackTrace may have raised an exception, clear it since it's not for the caller. + (*env)->ExceptionClear(env); + GetExceptionSummary(env, thrown, dst); + } + + if (pendingException != NULL) { + // Re-throw the pending exception present when this method was called. + (*env)->Throw(env, pendingException); + (*env)->DeleteLocalRef(env, pendingException); + } +} + +static void DiscardPendingException(JNIEnv* env, const char* className) { + jthrowable exception = (*env)->ExceptionOccurred(env); + (*env)->ExceptionClear(env); + if (exception == NULL) { + return; + } + + struct ExpandableString summary; + ExpandableStringInitialize(&summary); + GetExceptionSummary(env, exception, &summary); + const char* details = (summary.data != NULL) ? summary.data : "Unknown"; + ALOGW("Discarding pending exception (%s) to throw %s", details, className); + ExpandableStringRelease(&summary); + (*env)->DeleteLocalRef(env, exception); +} + +static int ThrowException(JNIEnv* env, const char* className, const char* ctorSig, ...) { + int status = -1; + jclass exceptionClass = NULL; + + va_list args; + va_start(args, ctorSig); + + DiscardPendingException(env, className); + + { + /* We want to clean up local references before returning from this function, so, + * regardless of return status, the end block must run. Have the work done in a + * nested block to avoid using any uninitialized variables in the end block. */ + exceptionClass = (*env)->FindClass(env, className); + if (exceptionClass == NULL) { + ALOGE("Unable to find exception class %s", className); + /* an exception, most likely ClassNotFoundException, will now be pending */ + goto end; + } + + jmethodID init = (*env)->GetMethodID(env, exceptionClass, "", ctorSig); + if(init == NULL) { + ALOGE("Failed to find constructor for '%s' '%s'", className, ctorSig); + goto end; + } + + jobject instance = (*env)->NewObjectV(env, exceptionClass, init, args); + if (instance == NULL) { + ALOGE("Failed to construct '%s'", className); + goto end; + } + + if ((*env)->Throw(env, (jthrowable)instance) != JNI_OK) { + ALOGE("Failed to throw '%s'", className); + /* an exception, most likely OOM, will now be pending */ + goto end; + } + + /* everything worked fine, just update status to success and clean up */ + status = 0; + } + +end: + va_end(args); + if (exceptionClass != NULL) { + (*env)->DeleteLocalRef(env, exceptionClass); + } + return status; +} + +static jstring CreateExceptionMsg(JNIEnv* env, const char* msg) { + jstring detailMessage = (*env)->NewStringUTF(env, msg); + if (detailMessage == NULL) { + /* Not really much we can do here. We're probably dead in the water, + but let's try to stumble on... */ + (*env)->ExceptionClear(env); + } + return detailMessage; +} + +/* Helper macro to deal with conversion of the exception message from a C string + * to jstring. + * + * This is useful because most exceptions have a message as the first parameter + * and delegating the conversion to all the callers of ThrowException results in + * code duplication. However, since we try to allow variable number of arguments + * for the exception constructor we'd either need to do the conversion inside + * the macro, or manipulate the va_list to replace the C string to a jstring. + * This seems like the cleaner solution. + */ +#define THROW_EXCEPTION_WITH_MESSAGE(env, className, ctorSig, msg, ...) ({ \ + jstring _detailMessage = CreateExceptionMsg(env, msg); \ + int _status = ThrowException(env, className, ctorSig, _detailMessage, ## __VA_ARGS__); \ + if (_detailMessage != NULL) { \ + (*env)->DeleteLocalRef(env, _detailMessage); \ + } \ + _status; }) + +// +// JNIHelp external API +// + +int jniRegisterNativeMethods(JNIEnv* env, const char* className, + const JNINativeMethod* methods, int numMethods) +{ + ALOGV("Registering %s's %d native methods...", className, numMethods); + jclass clazz = (*env)->FindClass(env, className); + ALOG_ALWAYS_FATAL_IF(clazz == NULL, + "Native registration unable to find class '%s'; aborting...", + className); + int result = (*env)->RegisterNatives(env, clazz, methods, numMethods); + (*env)->DeleteLocalRef(env, clazz); + if (result == 0) { + return 0; + } + + // Failure to register natives is fatal. Try to report the corresponding exception, + // otherwise abort with generic failure message. + jthrowable thrown = (*env)->ExceptionOccurred(env); + if (thrown != NULL) { + struct ExpandableString summary; + ExpandableStringInitialize(&summary); + if (GetExceptionSummary(env, thrown, &summary)) { + ALOGF("%s", summary.data); + } + ExpandableStringRelease(&summary); + (*env)->DeleteLocalRef(env, thrown); + } + ALOGF("RegisterNatives failed for '%s'; aborting...", className); + return result; +} + +void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown) { + struct ExpandableString summary; + ExpandableStringInitialize(&summary); + GetStackTraceOrSummary(env, thrown, &summary); + const char* details = (summary.data != NULL) ? summary.data : "No memory to report exception"; + __android_log_write(priority, tag, details); + ExpandableStringRelease(&summary); +} + +int jniThrowException(JNIEnv* env, const char* className, const char* message) { + return THROW_EXCEPTION_WITH_MESSAGE(env, className, "(Ljava/lang/String;)V", message); +} + +int jniThrowExceptionFmt(JNIEnv* env, const char* className, const char* fmt, va_list args) { + char msgBuf[512]; + vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); + return jniThrowException(env, className, msgBuf); +} + +int jniThrowNullPointerException(JNIEnv* env, const char* msg) { + return jniThrowException(env, "java/lang/NullPointerException", msg); +} + +int jniThrowRuntimeException(JNIEnv* env, const char* msg) { + return jniThrowException(env, "java/lang/RuntimeException", msg); +} + +int jniThrowIOException(JNIEnv* env, int errno_value) { + char buffer[80]; + const char* message = platformStrError(errno_value, buffer, sizeof(buffer)); + return jniThrowException(env, "java/io/IOException", message); +} + +int jniThrowErrnoException(JNIEnv* env, const char* functionName, int errno_value) { + return THROW_EXCEPTION_WITH_MESSAGE(env, "android/system/ErrnoException", + "(Ljava/lang/String;I)V", functionName, errno_value); +} + +jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, jsize len) { + return (*env)->NewString(env, unicodeChars, len); +} diff --git a/JNIPlatformHelp.c b/JNIPlatformHelp.c new file mode 100644 index 0000000..464e964 --- /dev/null +++ b/JNIPlatformHelp.c @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2006 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. + */ + +#include "include_platform/nativehelper/JNIPlatformHelp.h" + +#include + +#include "JniConstants.h" + +static int GetBufferPosition(JNIEnv* env, jobject nioBuffer) { + return(*env)->GetIntField(env, nioBuffer, JniConstants_NioBuffer_position(env)); +} + +static int GetBufferLimit(JNIEnv* env, jobject nioBuffer) { + return(*env)->GetIntField(env, nioBuffer, JniConstants_NioBuffer_limit(env)); +} + +static int GetBufferElementSizeShift(JNIEnv* env, jobject nioBuffer) { + return(*env)->GetIntField(env, nioBuffer, JniConstants_NioBuffer__elementSizeShift(env)); +} + +jarray jniGetNioBufferBaseArray(JNIEnv* env, jobject nioBuffer) { + jclass nioAccessClass = JniConstants_NIOAccessClass(env); + jmethodID getBaseArrayMethod = JniConstants_NIOAccess_getBaseArray(env); + jobject object = (*env)->CallStaticObjectMethod(env, + nioAccessClass, getBaseArrayMethod, nioBuffer); + return (jarray) object; +} + +int jniGetNioBufferBaseArrayOffset(JNIEnv* env, jobject nioBuffer) { + jclass nioAccessClass = JniConstants_NIOAccessClass(env); + jmethodID getBaseArrayOffsetMethod = JniConstants_NIOAccess_getBaseArrayOffset(env); + return (*env)->CallStaticIntMethod(env, nioAccessClass, getBaseArrayOffsetMethod, nioBuffer); +} + +jlong jniGetNioBufferPointer(JNIEnv* env, jobject nioBuffer) { + jlong baseAddress = (*env)->GetLongField(env, nioBuffer, JniConstants_NioBuffer_address(env)); + if (baseAddress != 0) { + const int position = GetBufferPosition(env, nioBuffer); + const int shift = GetBufferElementSizeShift(env, nioBuffer); + baseAddress += position << shift; + } + return baseAddress; +} + +jlong jniGetNioBufferFields(JNIEnv* env, jobject nioBuffer, + jint* position, jint* limit, jint* elementSizeShift) { + *position = GetBufferPosition(env, nioBuffer); + *limit = GetBufferLimit(env, nioBuffer); + *elementSizeShift = GetBufferElementSizeShift(env, nioBuffer); + return (*env)->GetLongField(env, nioBuffer, JniConstants_NioBuffer_address(env)); +} diff --git a/JniConstants.c b/JniConstants.c new file mode 100644 index 0000000..177298e --- /dev/null +++ b/JniConstants.c @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2010 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. + */ + +#include "JniConstants.h" + +#include +#include +#include +#include + +#define LOG_TAG "JniConstants" +#include "ALog-priv.h" + +// jclass constants list: +// + +#define JCLASS_CONSTANTS_LIST(V) \ + V(FileDescriptor, "java/io/FileDescriptor", false) \ + V(NIOAccess, "java/nio/NIOAccess", true) \ + V(NioBuffer, "java/nio/Buffer", false) + +// jmethodID's of public methods constants list: +// +#define JMETHODID_CONSTANTS_LIST(V) \ + V(FileDescriptor, init, "", "()V", false) \ + V(NIOAccess, getBaseArray, "getBaseArray", "(Ljava/nio/Buffer;)Ljava/lang/Object;", true) \ + V(NIOAccess, getBaseArrayOffset, "getBaseArrayOffset", "(Ljava/nio/Buffer;)I", true) \ + V(NioBuffer, array, "array", "()Ljava/lang/Object;", false) \ + V(NioBuffer, arrayOffset, "arrayOffset", "()I", false) + +// jfieldID constants list: +// +#define JFIELDID_CONSTANTS_LIST(V) \ + V(FileDescriptor, descriptor, "I", false) \ + V(NioBuffer, _elementSizeShift, "I", false) \ + V(NioBuffer, address, "J", false) \ + V(NioBuffer, limit, "I", false) \ + V(NioBuffer, position, "I", false) + +#define CLASS_NAME(cls) g_ ## cls +#define METHOD_NAME(cls, method) g_ ## cls ## _ ## method +#define FIELD_NAME(cls, field) g_ ## cls ## _ ## field + +// +// Declare storage for cached classes, methods and fields. +// + +#define JCLASS_DECLARE_STORAGE(cls, ...) \ + static jclass CLASS_NAME(cls) = NULL; +JCLASS_CONSTANTS_LIST(JCLASS_DECLARE_STORAGE) +#undef JCLASS_DECLARE_STORAGE + +#define JMETHODID_DECLARE_STORAGE(cls, method, ...) \ + static jmethodID METHOD_NAME(cls, method) = NULL; +JMETHODID_CONSTANTS_LIST(JMETHODID_DECLARE_STORAGE) +#undef JMETHODID_DECLARE_STORAGE + +#define JFIELDID_DECLARE_STORAGE(cls, field, ...) \ + static jfieldID FIELD_NAME(cls, field) = NULL; +JFIELDID_CONSTANTS_LIST(JFIELDID_DECLARE_STORAGE) +#undef JFIELDID_DECLARE_STORAGE + +// +// Helper methods +// + +static jclass FindClass(JNIEnv* env, const char* signature, bool androidOnly) { + jclass cls = (*env)->FindClass(env, signature); + if (cls == NULL) { + ALOG_ALWAYS_FATAL_IF(!androidOnly, "Class not found: %s", signature); + return NULL; + } + return (*env)->NewGlobalRef(env, cls); +} + +static jmethodID FindMethod(JNIEnv* env, jclass cls, + const char* name, const char* signature, bool isStatic) { + jmethodID method; + if (isStatic) { + method = (*env)->GetStaticMethodID(env, cls, name, signature); + } else { + method = (*env)->GetMethodID(env, cls, name, signature); + } + ALOG_ALWAYS_FATAL_IF(method == NULL, "Method not found: %s:%s", name, signature); + return method; +} + +static jfieldID FindField(JNIEnv* env, jclass cls, + const char* name, const char* signature, bool isStatic) { + jfieldID field; + if (isStatic) { + field = (*env)->GetStaticFieldID(env, cls, name, signature); + } else { + field = (*env)->GetFieldID(env, cls, name, signature); + } + ALOG_ALWAYS_FATAL_IF(field == NULL, "Field not found: %s:%s", name, signature); + return field; +} + +static pthread_once_t g_initialized = PTHREAD_ONCE_INIT; +static JNIEnv* g_init_env; + +static void InitializeConstants() { + // Initialize cached classes. +#define JCLASS_INITIALIZE(cls, signature, androidOnly) \ + CLASS_NAME(cls) = FindClass(g_init_env, signature, androidOnly); + JCLASS_CONSTANTS_LIST(JCLASS_INITIALIZE) +#undef JCLASS_INITIALIZE + + // Initialize cached methods. +#define JMETHODID_INITIALIZE(cls, method, name, signature, isStatic) \ + METHOD_NAME(cls, method) = \ + FindMethod(g_init_env, CLASS_NAME(cls), name, signature, isStatic); + JMETHODID_CONSTANTS_LIST(JMETHODID_INITIALIZE) +#undef JMETHODID_INITIALIZE + + // Initialize cached fields. +#define JFIELDID_INITIALIZE(cls, field, signature, isStatic) \ + FIELD_NAME(cls, field) = \ + FindField(g_init_env, CLASS_NAME(cls), #field, signature, isStatic); + JFIELDID_CONSTANTS_LIST(JFIELDID_INITIALIZE) +#undef JFIELDID_INITIALIZE +} + +void EnsureInitialized(JNIEnv* env) { + // This method has to be called in every cache accesses because library can be built + // 2 different ways and existing usage for compat version doesn't have a good hook for + // initialization and is widely used. + g_init_env = env; + pthread_once(&g_initialized, InitializeConstants); +} + +// API exported by libnativehelper_api.h. + +void jniUninitializeConstants() { + // Uninitialize cached classes, methods and fields. + // + // NB we assume the runtime is stopped at this point and do not delete global + // references. +#define JCLASS_INVALIDATE(cls, ...) CLASS_NAME(cls) = NULL; + JCLASS_CONSTANTS_LIST(JCLASS_INVALIDATE); +#undef JCLASS_INVALIDATE + +#define JMETHODID_INVALIDATE(cls, method, ...) METHOD_NAME(cls, method) = NULL; + JMETHODID_CONSTANTS_LIST(JMETHODID_INVALIDATE); +#undef JMETHODID_INVALIDATE + +#define JFIELDID_INVALIDATE(cls, field, ...) FIELD_NAME(cls, field) = NULL; + JFIELDID_CONSTANTS_LIST(JFIELDID_INVALIDATE); +#undef JFIELDID_INVALIDATE + + // If jniConstantsUninitialize is called, runtime has shutdown. Reset + // state as some tests re-start the runtime. + pthread_once_t o = PTHREAD_ONCE_INIT; + memcpy(&g_initialized, &o, sizeof(o)); +} + +// +// Accessors +// + +#define JCLASS_ACCESSOR_IMPL(cls, ...) \ +jclass JniConstants_ ## cls ## Class(JNIEnv* env) { \ + EnsureInitialized(env); \ + return CLASS_NAME(cls); \ +} +JCLASS_CONSTANTS_LIST(JCLASS_ACCESSOR_IMPL) +#undef JCLASS_ACCESSOR_IMPL + +#define JMETHODID_ACCESSOR_IMPL(cls, method, ...) \ +jmethodID JniConstants_ ## cls ## _ ## method(JNIEnv* env) { \ + EnsureInitialized(env); \ + return METHOD_NAME(cls, method); \ +} +JMETHODID_CONSTANTS_LIST(JMETHODID_ACCESSOR_IMPL) + +#define JFIELDID_ACCESSOR_IMPL(cls, field, ...) \ +jfieldID JniConstants_ ## cls ## _ ## field(JNIEnv* env) { \ + EnsureInitialized(env); \ + return FIELD_NAME(cls, field); \ +} +JFIELDID_CONSTANTS_LIST(JFIELDID_ACCESSOR_IMPL) diff --git a/JniConstants.h b/JniConstants.h new file mode 100644 index 0000000..cb8992d --- /dev/null +++ b/JniConstants.h @@ -0,0 +1,59 @@ +/* + * Copyright 2018 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 + +#include + +__BEGIN_DECLS + +// +// Classes in constants cache. +// +// NB The implementations of these methods are generated by the JCLASS_ACCESSOR_IMPL macro in +// JniConstants.c. +// +jclass JniConstants_FileDescriptorClass(JNIEnv* env); +jclass JniConstants_NIOAccessClass(JNIEnv* env); +jclass JniConstants_NioBufferClass(JNIEnv* env); + +// +// Methods in the constants cache. +// +// NB The implementations of these methods are generated by the JMETHODID_ACCESSOR_IMPL macro in +// JniConstants.c. +// +jmethodID JniConstants_FileDescriptor_init(JNIEnv* env); +jmethodID JniConstants_NIOAccess_getBaseArray(JNIEnv* env); +jmethodID JniConstants_NIOAccess_getBaseArrayOffset(JNIEnv* env); +jmethodID JniConstants_NioBuffer_array(JNIEnv* env); +jmethodID JniConstants_NioBuffer_arrayOffset(JNIEnv* env); + +// +// Fields in the constants cache. +// +// NB The implementations of these methods are generated by the JFIELDID_ACCESSOR_IMPL macro in +// JniConstants.c. +// +jfieldID JniConstants_FileDescriptor_descriptor(JNIEnv* env); +jfieldID JniConstants_NioBuffer_address(JNIEnv* env); +jfieldID JniConstants_NioBuffer__elementSizeShift(JNIEnv* env); +jfieldID JniConstants_NioBuffer_limit(JNIEnv* env); +jfieldID JniConstants_NioBuffer_position(JNIEnv* env); + +__END_DECLS diff --git a/JniInvocation-priv.h b/JniInvocation-priv.h new file mode 100644 index 0000000..0a9f2b9 --- /dev/null +++ b/JniInvocation-priv.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2020 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 + +__BEGIN_DECLS + +const char* JniInvocationGetLibraryWith(const char* library, + bool is_debuggable, + const char* system_preferred_library); + +__END_DECLS + diff --git a/JniInvocation.c b/JniInvocation.c new file mode 100644 index 0000000..7effddb --- /dev/null +++ b/JniInvocation.c @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2013 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. + */ + +#include "include_platform/nativehelper/JniInvocation.h" + +#define LOG_TAG "JniInvocation" +#include "ALog-priv.h" + +#if defined(__ANDROID__) +#include +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include "DlHelp.h" + +// Name the default library providing the JNI Invocation API. +static const char* kDefaultJniInvocationLibrary = "libart.so"; +static const char* kDebugJniInvocationLibrary = "libartd.so"; +#if defined(__LP64__) +#define LIB_DIR "lib64" +#else +#define LIB_DIR "lib" +#endif +static const char* kDebugJniInvocationLibraryPath = "/apex/com.android.art/" LIB_DIR "/libartd.so"; + +struct JniInvocationImpl { + // Name of library providing JNI_ method implementations. + const char* jni_provider_library_name; + + // Opaque pointer to shared library from dlopen / LoadLibrary. + void* jni_provider_library; + + // Function pointers to methods in JNI provider. + jint (*JNI_GetDefaultJavaVMInitArgs)(void*); + jint (*JNI_CreateJavaVM)(JavaVM**, JNIEnv**, void*); + jint (*JNI_GetCreatedJavaVMs)(JavaVM**, jsize, jsize*); +}; + +static struct JniInvocationImpl g_impl; + +// +// Internal helpers. +// + +#define UNUSED(x) (x) = (x) + +static bool IsDebuggable() { +#ifdef __ANDROID__ + char debuggable[PROP_VALUE_MAX] = {0}; + __system_property_get("ro.debuggable", debuggable); + return strcmp(debuggable, "1") == 0; +#else + // Host is always treated as debuggable, which allows choice of library to be overridden. + return true; +#endif +} + +static int GetLibrarySystemProperty(char* buffer) { +#ifdef __ANDROID__ + return __system_property_get("persist.sys.dalvik.vm.lib.2", buffer); +#else + // Host does not use properties. + UNUSED(buffer); + return 0; +#endif +} + +static DlSymbol FindSymbol(DlLibrary library, const char* symbol) { + DlSymbol s = DlGetSymbol(library, symbol); + if (s == NULL) { + ALOGE("Failed to find symbol: %s", symbol); + } + return s; +} + +// +// Exported functions for JNI based VM management from JNI spec. +// + +jint JNI_GetDefaultJavaVMInitArgs(void* vmargs) { + ALOG_ALWAYS_FATAL_IF(NULL == g_impl.JNI_GetDefaultJavaVMInitArgs, "Runtime library not loaded."); + return g_impl.JNI_GetDefaultJavaVMInitArgs(vmargs); +} + +jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { + ALOG_ALWAYS_FATAL_IF(NULL == g_impl.JNI_CreateJavaVM, "Runtime library not loaded."); + return g_impl.JNI_CreateJavaVM(p_vm, p_env, vm_args); +} + +jint JNI_GetCreatedJavaVMs(JavaVM** vms, jsize size, jsize* vm_count) { + if (NULL == g_impl.JNI_GetCreatedJavaVMs) { + *vm_count = 0; + return JNI_OK; + } + return g_impl.JNI_GetCreatedJavaVMs(vms, size, vm_count); +} + +// +// JniInvocation functions for setting up JNI functions. +// + +const char* JniInvocationGetLibraryWith(const char* library, + bool is_debuggable, + const char* system_preferred_library) { + if (is_debuggable) { + // Debuggable property is set. Allow library providing JNI Invocation API to be overridden. + + // Choose the library parameter (if provided). + if (library != NULL) { + return library; + } + + // If the debug library is installed, use it. + // TODO(b/216099383): Do this in the test harness instead. + struct stat st; + if (stat(kDebugJniInvocationLibraryPath, &st) == 0) { + return kDebugJniInvocationLibrary; + } else if (errno != ENOENT) { + ALOGW("Failed to stat %s: %s", kDebugJniInvocationLibraryPath, strerror(errno)); + } + + // Choose the system_preferred_library (if provided). + if (system_preferred_library != NULL) { + return system_preferred_library; + } + + } + return kDefaultJniInvocationLibrary; +} + +const char* JniInvocationGetLibrary(const char* library, char* buffer) { + bool debuggable = IsDebuggable(); + const char* system_preferred_library = NULL; + if (buffer != NULL && (GetLibrarySystemProperty(buffer) > 0)) { + system_preferred_library = buffer; + } + return JniInvocationGetLibraryWith(library, debuggable, system_preferred_library); +} + +struct JniInvocationImpl* JniInvocationCreate() { + // Android only supports a single JniInvocation instance and only a single JavaVM. + if (g_impl.jni_provider_library != NULL) { + return NULL; + } + return &g_impl; +} + +bool JniInvocationInit(struct JniInvocationImpl* instance, const char* library_name) { +#ifdef __ANDROID__ + char buffer[PROP_VALUE_MAX]; +#else + char* buffer = NULL; +#endif + library_name = JniInvocationGetLibrary(library_name, buffer); + DlLibrary library = DlOpenLibrary(library_name); + if (library == NULL) { + if (strcmp(library_name, kDefaultJniInvocationLibrary) == 0) { + // Nothing else to try. + ALOGE("Failed to dlopen %s: %s", library_name, DlGetError()); + return false; + } + // Note that this is enough to get something like the zygote + // running, we can't property_set here to fix this for the future + // because we are root and not the system user. See + // RuntimeInit.commonInit for where we fix up the property to + // avoid future fallbacks. http://b/11463182 + ALOGW("Falling back from %s to %s after dlopen error: %s", + library_name, kDefaultJniInvocationLibrary, DlGetError()); + library_name = kDefaultJniInvocationLibrary; + library = DlOpenLibrary(library_name); + if (library == NULL) { + ALOGE("Failed to dlopen %s: %s", library_name, DlGetError()); + return false; + } + } + + DlSymbol JNI_GetDefaultJavaVMInitArgs_ = FindSymbol(library, "JNI_GetDefaultJavaVMInitArgs"); + if (JNI_GetDefaultJavaVMInitArgs_ == NULL) { + return false; + } + + DlSymbol JNI_CreateJavaVM_ = FindSymbol(library, "JNI_CreateJavaVM"); + if (JNI_CreateJavaVM_ == NULL) { + return false; + } + + DlSymbol JNI_GetCreatedJavaVMs_ = FindSymbol(library, "JNI_GetCreatedJavaVMs"); + if (JNI_GetCreatedJavaVMs_ == NULL) { + return false; + } + + instance->jni_provider_library_name = library_name; + instance->jni_provider_library = library; + instance->JNI_GetDefaultJavaVMInitArgs = (jint (*)(void *)) JNI_GetDefaultJavaVMInitArgs_; + instance->JNI_CreateJavaVM = (jint (*)(JavaVM**, JNIEnv**, void*)) JNI_CreateJavaVM_; + instance->JNI_GetCreatedJavaVMs = (jint (*)(JavaVM**, jsize, jsize*)) JNI_GetCreatedJavaVMs_; + + return true; +} + +void JniInvocationDestroy(struct JniInvocationImpl* instance) { + DlCloseLibrary(instance->jni_provider_library); + memset(instance, 0, sizeof(*instance)); +} diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..c5b1efa --- /dev/null +++ b/NOTICE @@ -0,0 +1,190 @@ + + Copyright (c) 2005-2008, 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. + + 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. + + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..3b96c3a --- /dev/null +++ b/OWNERS @@ -0,0 +1,3 @@ +mast@google.com +ngeoffray@google.com +oth@google.com diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg new file mode 100644 index 0000000..321bab6 --- /dev/null +++ b/PREUPLOAD.cfg @@ -0,0 +1,2 @@ +[Builtin Hooks] +bpfmt = true diff --git a/README.md b/README.md new file mode 100644 index 0000000..ea96f65 --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ +# libnativehelper + +libnativehelper is a collection of JNI related utilities used in Android. + +There are several header and binary libraries here and not all of the +functionality fits together well. The header libraries are mostly C++ +based. The binary libraries are entirely written in C with no C++ +dependencies. This is by design as the code here can be distributed in +multiple ways, including mainline modules, so keeping the size down +benefits everyone with smaller downloads and a stable ABI. + +## Header Libraries + +### jni_headers + +This is a header library that contains provides the API represented in +the JNI Specification 1.6. Any project in Android that depends on +`jni.h` should depend on this. + +See: + +* [jni.h](include_jni/jni.h) + +### libnativehelper_header_only + +These headers provide utilities that defined entirely within the +headers. There are scoped resource classes that make common JNI +patterns of acquiring and releasing resources safer to use than the +JNI specification equivalents. Examples being `ScopedLocalRef` to +manage the lifetime of local references and `ScopedUtfChars` to manage +the lifetime of Java strings in native code and provide access to utf8 +characters. + +See: + +* [nativehelper/nativehelper_utils.h](header_only_include/nativehelper/nativehelper_utils.h) +* [nativehelper/scoped_utf_chars.h](header_only_include/nativehelper/scoped_utf_chars.h) +* [nativehelper/scoped_bytes.h](header_only_include/nativehelper/scoped_bytes.h) +* [nativehelper/scoped_string_chars.h](header_only_include/nativehelper/scoped_string_chars.h) +* [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) + +### jni_platform_headers + +The `jni_macros.h` header provide compile time checking of JNI methods +implemented in C++. They ensure the C++ method declaration match the +Java signature they are associated with. + +See: + +* [nativehelper/jni_macros.h](include_platform_header_only/nativehelper/jni_macros.h) + +## Libraries + +### libnativehelper + +A shared library distributed in the ART module that provides helper +routines built on Java APIs. This library depends on details that are +private to libcore and use should be restricted to platform code and +within the ART module. + +This library also contains the JNI invocation API from the JNI +Specification and the glue that connects the ART runtime to the API +implementation. The glue logic is platform only as it is used with the +Zygote and the standalone dalvikvm. + +See: +* [nativehelper/JNIHelp.h](include/nativehelper/JNIHelp.h) +* [nativehelper/JniInvocation.h](include_platform/nativehelper/JniInvocation.h) +* [nativehelper/JNIPlatformHelp.h](include_platform/nativehelper/JNIPlatformHelp.h) +* [nativehelper/ScopedBytes.h](include/nativehelper/ScopedBytes.h) +* [nativehelper/ScopedUtfChars.h](include/nativehelper/ScopedUtfChars.h) +* [nativehelper/ScopedLocalFrame.h](include/nativehelper/ScopedLocalFrame.h) +* [nativehelper/ScopedLocalRef.h](include/nativehelper/ScopedLocalRef.h) +* [nativehelper/ScopedPrimitiveArray.h](include/nativehelper/ScopedPrimitiveArray.h) +* [nativehelper/ScopedStringChars.h](include/nativehelper/ScopedStringChars.h) +* [nativehelper/toStringArray.h](include/nativehelper/toStringArray.h) + +### libnativehelper_compat_libc++ + +This shared and static library contains a subset of the helper +routines in libnativehelper based on public Java API. This code can be +statically linked as the Java APIs it depends on are considered +stable. The name of this library is a misnomer since it contains no +C++ code. + +See: + +* [nativehelper/JNIHelp.h](include/nativehelper/JNIHelp.h) +* [nativehelper/ScopedBytes.h](include/nativehelper/ScopedBytes.h) +* [nativehelper/ScopedUtfChars.h](include/nativehelper/ScopedUtfChars.h) +* [nativehelper/ScopedLocalFrame.h](include/nativehelper/ScopedLocalFrame.h) +* [nativehelper/ScopedLocalRef.h](include/nativehelper/ScopedLocalRef.h) +* [nativehelper/ScopedPrimitiveArray.h](include/nativehelper/ScopedPrimitiveArray.h) +* [nativehelper/ScopedStringChars.h](include/nativehelper/ScopedStringChars.h) +* [nativehelper/toStringArray.h](include/nativehelper/toStringArray.h) diff --git a/TEST_MAPPING b/TEST_MAPPING new file mode 100644 index 0000000..251ee58 --- /dev/null +++ b/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "libnativehelper_tests" + }, + { + "name": "libnativehelper_lazy_tests" + }, + { + "name": "MtsLibnativehelperTestCases" + }, + { + "name": "MtsLibnativehelperLazyTestCases" + }, + { + "name": "CtsLibnativehelperTestCases" + } + ] +} diff --git a/file_descriptor_jni.c b/file_descriptor_jni.c new file mode 100644 index 0000000..bfae4af --- /dev/null +++ b/file_descriptor_jni.c @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2020 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. + */ + +#include + +#include + +#define LOG_TAG "file_descriptor_jni" +#include "ALog-priv.h" + +#include "JniConstants.h" + +static void EnsureArgumentIsFileDescriptor(JNIEnv* env, jobject instance) { + ALOG_ALWAYS_FATAL_IF(instance == NULL, "FileDescriptor is NULL"); + jclass jifd = JniConstants_FileDescriptorClass(env); + ALOG_ALWAYS_FATAL_IF(!(*env)->IsInstanceOf(env, instance, jifd), + "Argument is not a FileDescriptor"); +} + +JNIEXPORT _Nullable jobject AFileDescriptor_create(JNIEnv* env) { + return (*env)->NewObject(env, + JniConstants_FileDescriptorClass(env), + JniConstants_FileDescriptor_init(env)); +} + +JNIEXPORT int AFileDescriptor_getFd(JNIEnv* env, jobject fileDescriptor) { + EnsureArgumentIsFileDescriptor(env, fileDescriptor); + return (*env)->GetIntField(env, fileDescriptor, JniConstants_FileDescriptor_descriptor(env)); +} + +JNIEXPORT void AFileDescriptor_setFd(JNIEnv* env, jobject fileDescriptor, int fd) { + EnsureArgumentIsFileDescriptor(env, fileDescriptor); + (*env)->SetIntField(env, fileDescriptor, JniConstants_FileDescriptor_descriptor(env), fd); +} diff --git a/header_only_include/nativehelper/nativehelper_utils.h b/header_only_include/nativehelper/nativehelper_utils.h new file mode 100644 index 0000000..9cb8195 --- /dev/null +++ b/header_only_include/nativehelper/nativehelper_utils.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007 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 + +#if defined(__cplusplus) + +#if !defined(DISALLOW_COPY_AND_ASSIGN) +// DISALLOW_COPY_AND_ASSIGN disallows the copy and operator= functions. It goes in the private: +// declarations in a class. +#define DISALLOW_COPY_AND_ASSIGN(TypeName) \ + TypeName(const TypeName&) = delete; \ + void operator=(const TypeName&) = delete +#endif // !defined(DISALLOW_COPY_AND_ASSIGN) + +// This seems a header-only include. Provide NPE throwing. +static inline int jniThrowNullPointerException(JNIEnv* env) { + if (env->ExceptionCheck()) { + // Drop any pending exception. + env->ExceptionClear(); + } + + jclass e_class = env->FindClass("java/lang/NullPointerException"); + if (e_class == nullptr) { + return -1; + } + + if (env->ThrowNew(e_class, nullptr) != JNI_OK) { + env->DeleteLocalRef(e_class); + return -1; + } + + env->DeleteLocalRef(e_class); + return 0; +} + +#endif // defined(__cplusplus) + diff --git a/header_only_include/nativehelper/scoped_local_frame.h b/header_only_include/nativehelper/scoped_local_frame.h new file mode 100644 index 0000000..da06655 --- /dev/null +++ b/header_only_include/nativehelper/scoped_local_frame.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2010 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 + +#include "nativehelper_utils.h" + +class ScopedLocalFrame { +public: + explicit ScopedLocalFrame(JNIEnv* env) : mEnv(env) { + mEnv->PushLocalFrame(128); + } + + ~ScopedLocalFrame() { + mEnv->PopLocalFrame(nullptr); + } + +private: + JNIEnv* const mEnv; + + DISALLOW_COPY_AND_ASSIGN(ScopedLocalFrame); +}; + diff --git a/header_only_include/nativehelper/scoped_local_ref.h b/header_only_include/nativehelper/scoped_local_ref.h new file mode 100644 index 0000000..cd35a88 --- /dev/null +++ b/header_only_include/nativehelper/scoped_local_ref.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010 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 + +#include + +#include "nativehelper_utils.h" + +// A smart pointer that deletes a JNI local reference when it goes out of scope. +template +class ScopedLocalRef { +public: + ScopedLocalRef(JNIEnv* env, T localRef) : mEnv(env), mLocalRef(localRef) { + } + + ScopedLocalRef(ScopedLocalRef&& s) noexcept : mEnv(s.mEnv), mLocalRef(s.release()) { + } + + explicit ScopedLocalRef(JNIEnv* env) : mEnv(env), mLocalRef(nullptr) { + } + + ~ScopedLocalRef() { + reset(); + } + + void reset(T ptr = nullptr) { + if (ptr != mLocalRef) { + if (mLocalRef != nullptr) { + mEnv->DeleteLocalRef(mLocalRef); + } + mLocalRef = ptr; + } + } + + T release() __attribute__((warn_unused_result)) { + T localRef = mLocalRef; + mLocalRef = nullptr; + return localRef; + } + + T get() const { + return mLocalRef; + } + + + // We do not expose an empty constructor as it can easily lead to errors + // using common idioms, e.g.: + // ScopedLocalRef<...> ref; + // ref.reset(...); + + // Move assignment operator. + ScopedLocalRef& operator=(ScopedLocalRef&& s) noexcept { + reset(s.release()); + mEnv = s.mEnv; + return *this; + } + + // Allows "if (scoped_ref == nullptr)" + bool operator==(std::nullptr_t) const { + return mLocalRef == nullptr; + } + + // Allows "if (scoped_ref != nullptr)" + bool operator!=(std::nullptr_t) const { + return mLocalRef != nullptr; + } + +private: + JNIEnv* mEnv; + T mLocalRef; + + DISALLOW_COPY_AND_ASSIGN(ScopedLocalRef); +}; + diff --git a/header_only_include/nativehelper/scoped_primitive_array.h b/header_only_include/nativehelper/scoped_primitive_array.h new file mode 100644 index 0000000..16acb70 --- /dev/null +++ b/header_only_include/nativehelper/scoped_primitive_array.h @@ -0,0 +1,148 @@ +/* + * Copyright (C) 2010 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 + +#include + +#include "nativehelper_utils.h" + +#ifdef POINTER_TYPE +#error POINTER_TYPE is defined. +#else +#define POINTER_TYPE(T) T* /* NOLINT */ +#endif + +#ifdef REFERENCE_TYPE +#error REFERENCE_TYPE is defined. +#else +#define REFERENCE_TYPE(T) T& /* NOLINT */ +#endif + +// ScopedBooleanArrayRO, ScopedByteArrayRO, ScopedCharArrayRO, ScopedDoubleArrayRO, +// ScopedFloatArrayRO, ScopedIntArrayRO, ScopedLongArrayRO, and ScopedShortArrayRO provide +// convenient read-only access to Java arrays from JNI code. This is cheaper than read-write +// access and should be used by default. +#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(PRIMITIVE_TYPE, NAME) \ + class Scoped ## NAME ## ArrayRO { \ + public: \ + explicit Scoped ## NAME ## ArrayRO(JNIEnv* env) \ + : mEnv(env), mJavaArray(nullptr), mRawArray(nullptr), mSize(0) {} \ + Scoped ## NAME ## ArrayRO(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \ + : mEnv(env) { \ + if (javaArray == nullptr) { \ + mJavaArray = nullptr; \ + mSize = 0; \ + mRawArray = nullptr; \ + jniThrowNullPointerException(mEnv); \ + } else { \ + reset(javaArray); \ + } \ + } \ + ~Scoped ## NAME ## ArrayRO() { \ + if (mRawArray != nullptr && mRawArray != mBuffer) { \ + mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, JNI_ABORT); \ + } \ + } \ + void reset(PRIMITIVE_TYPE ## Array javaArray) { \ + mJavaArray = javaArray; \ + mSize = mEnv->GetArrayLength(mJavaArray); \ + if (mSize <= buffer_size) { \ + mEnv->Get ## NAME ## ArrayRegion(mJavaArray, 0, mSize, mBuffer); \ + mRawArray = mBuffer; \ + } else { \ + mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \ + } \ + } \ + const PRIMITIVE_TYPE* get() const { return mRawArray; } \ + PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \ + const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \ + size_t size() const { return mSize; } \ + private: \ + static const jsize buffer_size = 1024; \ + JNIEnv* const mEnv; \ + PRIMITIVE_TYPE ## Array mJavaArray; \ + POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \ + jsize mSize; \ + PRIMITIVE_TYPE mBuffer[buffer_size]; \ + DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRO); \ + } + +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jboolean, Boolean); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jbyte, Byte); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jchar, Char); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jdouble, Double); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jfloat, Float); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jint, Int); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jlong, Long); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO(jshort, Short); + +#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RO + +// ScopedBooleanArrayRW, ScopedByteArrayRW, ScopedCharArrayRW, ScopedDoubleArrayRW, +// ScopedFloatArrayRW, ScopedIntArrayRW, ScopedLongArrayRW, and ScopedShortArrayRW provide +// convenient read-write access to Java arrays from JNI code. These are more expensive, +// since they entail a copy back onto the Java heap, and should only be used when necessary. +#define INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(PRIMITIVE_TYPE, NAME) \ + class Scoped ## NAME ## ArrayRW { \ + public: \ + explicit Scoped ## NAME ## ArrayRW(JNIEnv* env) \ + : mEnv(env), mJavaArray(nullptr), mRawArray(nullptr) {} \ + Scoped ## NAME ## ArrayRW(JNIEnv* env, PRIMITIVE_TYPE ## Array javaArray) \ + : mEnv(env), mJavaArray(javaArray), mRawArray(nullptr) { \ + if (mJavaArray == nullptr) { \ + jniThrowNullPointerException(mEnv); \ + } else { \ + mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \ + } \ + } \ + ~Scoped ## NAME ## ArrayRW() { \ + if (mRawArray) { \ + mEnv->Release ## NAME ## ArrayElements(mJavaArray, mRawArray, 0); \ + } \ + } \ + void reset(PRIMITIVE_TYPE ## Array javaArray) { \ + mJavaArray = javaArray; \ + mRawArray = mEnv->Get ## NAME ## ArrayElements(mJavaArray, nullptr); \ + } \ + const PRIMITIVE_TYPE* get() const { return mRawArray; } \ + PRIMITIVE_TYPE ## Array getJavaArray() const { return mJavaArray; } \ + const PRIMITIVE_TYPE& operator[](size_t n) const { return mRawArray[n]; } \ + POINTER_TYPE(PRIMITIVE_TYPE) get() { return mRawArray; } \ + REFERENCE_TYPE(PRIMITIVE_TYPE) operator[](size_t n) { return mRawArray[n]; } \ + size_t size() const { return mEnv->GetArrayLength(mJavaArray); } \ + private: \ + JNIEnv* const mEnv; \ + PRIMITIVE_TYPE ## Array mJavaArray; \ + POINTER_TYPE(PRIMITIVE_TYPE) mRawArray; \ + DISALLOW_COPY_AND_ASSIGN(Scoped ## NAME ## ArrayRW); \ + } + +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jboolean, Boolean); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jbyte, Byte); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jchar, Char); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jdouble, Double); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jfloat, Float); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jint, Int); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jlong, Long); +INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW(jshort, Short); + +#undef INSTANTIATE_SCOPED_PRIMITIVE_ARRAY_RW +#undef POINTER_TYPE +#undef REFERENCE_TYPE + diff --git a/header_only_include/nativehelper/scoped_string_chars.h b/header_only_include/nativehelper/scoped_string_chars.h new file mode 100644 index 0000000..e9d60bc --- /dev/null +++ b/header_only_include/nativehelper/scoped_string_chars.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2011 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 + +#include + +#include "nativehelper_utils.h" + +// A smart pointer that provides access to a jchar* given a JNI jstring. +// Unlike GetStringChars, we throw NullPointerException rather than abort if +// passed a null jstring, and get will return nullptr. +// This makes the correct idiom very simple: +// +// ScopedStringChars name(env, java_name); +// if (name.get() == nullptr) { +// return nullptr; +// } +class ScopedStringChars { + public: + ScopedStringChars(JNIEnv* env, jstring s) : env_(env), string_(s), size_(0) { + if (s == nullptr) { + chars_ = nullptr; + jniThrowNullPointerException(env); + } else { + chars_ = env->GetStringChars(string_, nullptr); + if (chars_ != nullptr) { + size_ = env->GetStringLength(string_); + } + } + } + + ~ScopedStringChars() { + if (chars_ != nullptr) { + env_->ReleaseStringChars(string_, chars_); + } + } + + const jchar* get() const { + return chars_; + } + + size_t size() const { + return size_; + } + + const jchar& operator[](size_t n) const { + return chars_[n]; + } + + private: + JNIEnv* const env_; + const jstring string_; + const jchar* chars_; + size_t size_; + + DISALLOW_COPY_AND_ASSIGN(ScopedStringChars); +}; + diff --git a/header_only_include/nativehelper/scoped_utf_chars.h b/header_only_include/nativehelper/scoped_utf_chars.h new file mode 100644 index 0000000..363ff42 --- /dev/null +++ b/header_only_include/nativehelper/scoped_utf_chars.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2010 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 +#include + +#include + +#include "nativehelper_utils.h" + +// 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. +// This makes the correct idiom very simple: +// +// ScopedUtfChars name(env, java_name); +// if (name.c_str() == nullptr) { +// return nullptr; +// } +class ScopedUtfChars { + public: + ScopedUtfChars(JNIEnv* env, jstring s) : env_(env), string_(s) { + if (s == nullptr) { + utf_chars_ = nullptr; + jniThrowNullPointerException(env); + } else { + utf_chars_ = env->GetStringUTFChars(s, nullptr); + } + } + + ScopedUtfChars(ScopedUtfChars&& rhs) noexcept : + env_(rhs.env_), string_(rhs.string_), utf_chars_(rhs.utf_chars_) { + rhs.env_ = nullptr; + rhs.string_ = nullptr; + rhs.utf_chars_ = nullptr; + } + + ~ScopedUtfChars() { + if (utf_chars_) { + env_->ReleaseStringUTFChars(string_, utf_chars_); + } + } + + ScopedUtfChars& operator=(ScopedUtfChars&& rhs) noexcept { + if (this != &rhs) { + // Delete the currently owned UTF chars. + this->~ScopedUtfChars(); + + // Move the rhs ScopedUtfChars and zero it out. + env_ = rhs.env_; + string_ = rhs.string_; + utf_chars_ = rhs.utf_chars_; + rhs.env_ = nullptr; + rhs.string_ = nullptr; + rhs.utf_chars_ = nullptr; + } + return *this; + } + + const char* c_str() const { + return utf_chars_; + } + + size_t size() const { + return strlen(utf_chars_); + } + + const char& operator[](size_t n) const { + return utf_chars_[n]; + } + + private: + JNIEnv* env_; + jstring string_; + const char* utf_chars_; + + DISALLOW_COPY_AND_ASSIGN(ScopedUtfChars); +}; + diff --git a/include/android/file_descriptor_jni.h b/include/android/file_descriptor_jni.h new file mode 100644 index 0000000..26529b9 --- /dev/null +++ b/include/android/file_descriptor_jni.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2020 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. + */ + +/** + * @addtogroup FileDescriptor File Descriptor + * @{ + */ + +/** + * @file file_descriptor_jni.h + */ + +#pragma once + +#include + +#include + +#if !defined(__BIONIC__) && !defined(__INTRODUCED_IN) +#define __INTRODUCED_IN(x) +#endif + +__BEGIN_DECLS + +/** + * Returns a new java.io.FileDescriptor. + * + * The FileDescriptor created represents an invalid Unix file descriptor (represented by + * a file descriptor value of -1). + * + * Callers of this method should be aware that it can fail, returning NULL with a pending Java + * exception. + * + * Available since API level 31. + * + * \param env a pointer to the JNI Native Interface of the current thread. + * \return a java.io.FileDescriptor on success, nullptr if insufficient heap memory is available. + */ +jobject AFileDescriptor_create(JNIEnv* env) __INTRODUCED_IN(31); + +/** + * Returns the Unix file descriptor represented by the given java.io.FileDescriptor. + * + * A return value of -1 indicates that \a fileDescriptor represents an invalid file descriptor. + * + * Aborts the program if \a fileDescriptor is not a java.io.FileDescriptor instance. + * + * Available since API level 31. + * + * \param env a pointer to the JNI Native Interface of the current thread. + * \param fileDescriptor a java.io.FileDescriptor instance. + * \return the Unix file descriptor wrapped by \a fileDescriptor. + */ +int AFileDescriptor_getFd(JNIEnv* env, jobject fileDescriptor) __INTRODUCED_IN(31); + +/** + * Sets the Unix file descriptor represented by the given java.io.FileDescriptor. + * + * This function performs no validation of the Unix file descriptor argument, \a fd. Android uses + * the value -1 to represent an invalid file descriptor, all other values are considered valid. + * The validity of a file descriptor can be checked with FileDescriptor#valid(). + * + * Aborts the program if \a fileDescriptor is not a java.io.FileDescriptor instance. + * + * Available since API level 31. + * + * \param env a pointer to the JNI Native Interface of the current thread. + * \param fileDescriptor a java.io.FileDescriptor instance. + * \param fd a Unix file descriptor that \a fileDescriptor will subsequently represent. + */ +void AFileDescriptor_setFd(JNIEnv* env, jobject fileDescriptor, int fd) __INTRODUCED_IN(31); + +__END_DECLS + +/** @} */ diff --git a/include/nativehelper/JNIHelp.h b/include/nativehelper/JNIHelp.h new file mode 100644 index 0000000..6538ddf --- /dev/null +++ b/include/nativehelper/JNIHelp.h @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2007 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 helper functions. + * + * This file may be included by C or C++ code, which is trouble because jni.h + * uses different typedefs for JNIEnv in each language. + */ +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include + +// Avoid formatting this as it must match webview's usage (webview/graphics_utils.cpp). +// clang-format off +#ifndef NELEM +#define NELEM(x) ((int) (sizeof(x) / sizeof((x)[0]))) +#endif +// clang-format on + +/* + * For C++ code, we provide inlines that map to the C functions. g++ always + * inlines these, even on non-optimized builds. + */ +#if defined(__cplusplus) + +namespace android::jnihelp { +struct [[maybe_unused]] ExpandableString { + size_t dataSize; // The length of the C string data (not including the null-terminator). + char* data; // The C string data. +}; + +[[maybe_unused]] static void ExpandableStringInitialize(struct ExpandableString* s) { + memset(s, 0, sizeof(*s)); +} + +[[maybe_unused]] static void ExpandableStringRelease(struct ExpandableString* s) { + free(s->data); + memset(s, 0, sizeof(*s)); +} + +[[maybe_unused]] static bool ExpandableStringAppend(struct ExpandableString* s, const char* text) { + size_t textSize = strlen(text); + size_t requiredSize = s->dataSize + textSize + 1; + char* data = (char*)realloc(s->data, requiredSize); + if (data == NULL) { + return false; + } + s->data = data; + memcpy(s->data + s->dataSize, text, textSize + 1); + s->dataSize += textSize; + return true; +} + +[[maybe_unused]] static bool ExpandableStringAssign(struct ExpandableString* s, const char* text) { + ExpandableStringRelease(s); + return ExpandableStringAppend(s, text); +} + +[[maybe_unused]] inline char* safe_strerror(char* (*strerror_r_method)(int, char*, size_t), + int errnum, char* buf, size_t buflen) { + return strerror_r_method(errnum, buf, buflen); +} + +[[maybe_unused]] inline char* safe_strerror(int (*strerror_r_method)(int, char*, size_t), + int errnum, char* buf, size_t buflen) { + int rc = strerror_r_method(errnum, buf, buflen); + if (rc != 0) { + snprintf(buf, buflen, "errno %d", errnum); + } + return buf; +} + +[[maybe_unused]] static const char* platformStrError(int errnum, char* buf, size_t buflen) { + return safe_strerror(strerror_r, errnum, buf, buflen); +} + +[[maybe_unused]] static jmethodID FindMethod(JNIEnv* env, const char* className, + const char* methodName, const char* descriptor) { + // This method is only valid for classes in the core library which are + // not unloaded during the lifetime of managed code execution. + jclass clazz = env->FindClass(className); + jmethodID methodId = env->GetMethodID(clazz, methodName, descriptor); + env->DeleteLocalRef(clazz); + return methodId; +} + +[[maybe_unused]] static bool AppendJString(JNIEnv* env, jstring text, + struct ExpandableString* dst) { + const char* utfText = env->GetStringUTFChars(text, NULL); + if (utfText == NULL) { + return false; + } + bool success = ExpandableStringAppend(dst, utfText); + env->ReleaseStringUTFChars(text, utfText); + return success; +} + +/* + * Returns a human-readable summary of an exception object. The buffer will + * be populated with the "binary" class name and, if present, the + * exception message. + */ +[[maybe_unused]] static bool GetExceptionSummary(JNIEnv* env, jthrowable thrown, + struct ExpandableString* dst) { + // Summary is ": " + jclass exceptionClass = env->GetObjectClass(thrown); // Always succeeds + jmethodID getName = FindMethod(env, "java/lang/Class", "getName", "()Ljava/lang/String;"); + jstring className = (jstring)env->CallObjectMethod(exceptionClass, getName); + if (className == NULL) { + ExpandableStringAssign(dst, ""); + env->ExceptionClear(); + env->DeleteLocalRef(exceptionClass); + return false; + } + env->DeleteLocalRef(exceptionClass); + exceptionClass = NULL; + + if (!AppendJString(env, className, dst)) { + ExpandableStringAssign(dst, ""); + env->ExceptionClear(); + env->DeleteLocalRef(className); + return false; + } + env->DeleteLocalRef(className); + className = NULL; + + jmethodID getMessage = + FindMethod(env, "java/lang/Throwable", "getMessage", "()Ljava/lang/String;"); + jstring message = (jstring)env->CallObjectMethod(thrown, getMessage); + if (message == NULL) { + return true; + } + + bool success = (ExpandableStringAppend(dst, ": ") && AppendJString(env, message, dst)); + if (!success) { + // Two potential reasons for reaching here: + // + // 1. managed heap allocation failure (OOME). + // 2. native heap allocation failure for the storage in |dst|. + // + // Attempt to append failure notification, okay to fail, |dst| contains the class name + // of |thrown|. + ExpandableStringAppend(dst, ""); + // Clear OOME if present. + env->ExceptionClear(); + } + env->DeleteLocalRef(message); + message = NULL; + return success; +} + +[[maybe_unused]] static jobject NewStringWriter(JNIEnv* env) { + jclass clazz = env->FindClass("java/io/StringWriter"); + jmethodID init = env->GetMethodID(clazz, "", "()V"); + jobject instance = env->NewObject(clazz, init); + env->DeleteLocalRef(clazz); + return instance; +} + +[[maybe_unused]] static jstring StringWriterToString(JNIEnv* env, jobject stringWriter) { + jmethodID toString = + FindMethod(env, "java/io/StringWriter", "toString", "()Ljava/lang/String;"); + return (jstring)env->CallObjectMethod(stringWriter, toString); +} + +[[maybe_unused]] static jobject NewPrintWriter(JNIEnv* env, jobject writer) { + jclass clazz = env->FindClass("java/io/PrintWriter"); + jmethodID init = env->GetMethodID(clazz, "", "(Ljava/io/Writer;)V"); + jobject instance = env->NewObject(clazz, init, writer); + env->DeleteLocalRef(clazz); + return instance; +} + +[[maybe_unused]] static bool GetStackTrace(JNIEnv* env, jthrowable thrown, + struct ExpandableString* dst) { + // This function is equivalent to the following Java snippet: + // StringWriter sw = new StringWriter(); + // PrintWriter pw = new PrintWriter(sw); + // thrown.printStackTrace(pw); + // String trace = sw.toString(); + // return trace; + jobject sw = NewStringWriter(env); + if (sw == NULL) { + return false; + } + + jobject pw = NewPrintWriter(env, sw); + if (pw == NULL) { + env->DeleteLocalRef(sw); + return false; + } + + jmethodID printStackTrace = + FindMethod(env, "java/lang/Throwable", "printStackTrace", "(Ljava/io/PrintWriter;)V"); + env->CallVoidMethod(thrown, printStackTrace, pw); + + jstring trace = StringWriterToString(env, sw); + + env->DeleteLocalRef(pw); + pw = NULL; + env->DeleteLocalRef(sw); + sw = NULL; + + if (trace == NULL) { + return false; + } + + bool success = AppendJString(env, trace, dst); + env->DeleteLocalRef(trace); + return success; +} + +[[maybe_unused]] static void GetStackTraceOrSummary(JNIEnv* env, jthrowable thrown, + struct ExpandableString* dst) { + // This method attempts to get a stack trace or summary info for an exception. + // The exception may be provided in the |thrown| argument to this function. + // If |thrown| is NULL, then any pending exception is used if it exists. + + // Save pending exception, callees may raise other exceptions. Any pending exception is + // rethrown when this function exits. + jthrowable pendingException = env->ExceptionOccurred(); + if (pendingException != NULL) { + env->ExceptionClear(); + } + + if (thrown == NULL) { + if (pendingException == NULL) { + ExpandableStringAssign(dst, ""); + return; + } + thrown = pendingException; + } + + if (!GetStackTrace(env, thrown, dst)) { + // GetStackTrace may have raised an exception, clear it since it's not for the caller. + env->ExceptionClear(); + GetExceptionSummary(env, thrown, dst); + } + + if (pendingException != NULL) { + // Re-throw the pending exception present when this method was called. + env->Throw(pendingException); + env->DeleteLocalRef(pendingException); + } +} + +[[maybe_unused]] static void DiscardPendingException(JNIEnv* env, const char* className) { + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); + if (exception == NULL) { + return; + } + + struct ExpandableString summary; + ExpandableStringInitialize(&summary); + GetExceptionSummary(env, exception, &summary); + const char* details = (summary.data != NULL) ? summary.data : "Unknown"; + __android_log_print(ANDROID_LOG_WARN, "JNIHelp", + "Discarding pending exception (%s) to throw %s", details, className); + ExpandableStringRelease(&summary); + env->DeleteLocalRef(exception); +} + +[[maybe_unused]] static int ThrowException(JNIEnv* env, const char* className, const char* ctorSig, + ...) { + int status = -1; + jclass exceptionClass = NULL; + + va_list args; + va_start(args, ctorSig); + + DiscardPendingException(env, className); + + { + /* We want to clean up local references before returning from this function, so, + * regardless of return status, the end block must run. Have the work done in a + * nested block to avoid using any uninitialized variables in the end block. */ + exceptionClass = env->FindClass(className); + if (exceptionClass == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", "Unable to find exception class %s", + className); + /* an exception, most likely ClassNotFoundException, will now be pending */ + goto end; + } + + jmethodID init = env->GetMethodID(exceptionClass, "", ctorSig); + if (init == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", + "Failed to find constructor for '%s' '%s'", className, ctorSig); + goto end; + } + + jobject instance = env->NewObjectV(exceptionClass, init, args); + if (instance == NULL) { + __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", "Failed to construct '%s'", + className); + goto end; + } + + if (env->Throw((jthrowable)instance) != JNI_OK) { + __android_log_print(ANDROID_LOG_ERROR, "JNIHelp", "Failed to throw '%s'", className); + /* an exception, most likely OOM, will now be pending */ + goto end; + } + + /* everything worked fine, just update status to success and clean up */ + status = 0; + } + +end: + va_end(args); + if (exceptionClass != NULL) { + env->DeleteLocalRef(exceptionClass); + } + return status; +} + +[[maybe_unused]] static jstring CreateExceptionMsg(JNIEnv* env, const char* msg) { + jstring detailMessage = env->NewStringUTF(msg); + if (detailMessage == NULL) { + /* Not really much we can do here. We're probably dead in the water, + but let's try to stumble on... */ + env->ExceptionClear(); + } + return detailMessage; +} +} // namespace android::jnihelp + +/* + * Register one or more native methods with a particular class. "className" looks like + * "java/lang/String". Aborts on failure, returns 0 on success. + */ +[[maybe_unused]] static int jniRegisterNativeMethods(JNIEnv* env, const char* className, + const JNINativeMethod* methods, + int numMethods) { + using namespace android::jnihelp; + jclass clazz = env->FindClass(className); + if (clazz == NULL) { + __android_log_assert("clazz == NULL", "JNIHelp", + "Native registration unable to find class '%s'; aborting...", + className); + } + int result = env->RegisterNatives(clazz, methods, numMethods); + env->DeleteLocalRef(clazz); + if (result == 0) { + return 0; + } + + // Failure to register natives is fatal. Try to report the corresponding exception, + // otherwise abort with generic failure message. + jthrowable thrown = env->ExceptionOccurred(); + if (thrown != NULL) { + struct ExpandableString summary; + ExpandableStringInitialize(&summary); + if (GetExceptionSummary(env, thrown, &summary)) { + __android_log_print(ANDROID_LOG_FATAL, "JNIHelp", "%s", summary.data); + } + ExpandableStringRelease(&summary); + env->DeleteLocalRef(thrown); + } + __android_log_print(ANDROID_LOG_FATAL, "JNIHelp", + "RegisterNatives failed for '%s'; aborting...", className); + return result; +} + +/* + * Throw an exception with the specified class and an optional message. + * + * The "className" argument will be passed directly to FindClass, which + * takes strings with slashes (e.g. "java/lang/Object"). + * + * If an exception is currently pending, we log a warning message and + * clear it. + * + * Returns 0 on success, nonzero if something failed (e.g. the exception + * class couldn't be found, so *an* exception will still be pending). + * + * Currently aborts the VM if it can't throw the exception. + */ +[[maybe_unused]] static int jniThrowException(JNIEnv* env, const char* className, const char* msg) { + using namespace android::jnihelp; + jstring _detailMessage = CreateExceptionMsg(env, msg); + int _status = ThrowException(env, className, "(Ljava/lang/String;)V", _detailMessage); + if (_detailMessage != NULL) { + env->DeleteLocalRef(_detailMessage); + } + return _status; +} + +/* + * Throw an android.system.ErrnoException, with the given function name and errno value. + */ +[[maybe_unused]] static int jniThrowErrnoException(JNIEnv* env, const char* functionName, + int errnum) { + using namespace android::jnihelp; + jstring _detailMessage = CreateExceptionMsg(env, functionName); + int _status = ThrowException(env, "android/system/ErrnoException", "(Ljava/lang/String;I)V", + _detailMessage, errnum); + if (_detailMessage != NULL) { + env->DeleteLocalRef(_detailMessage); + } + return _status; +} + +/* + * Throw an exception with the specified class and formatted error message. + * + * The "className" argument will be passed directly to FindClass, which + * takes strings with slashes (e.g. "java/lang/Object"). + * + * If an exception is currently pending, we log a warning message and + * clear it. + * + * Returns 0 on success, nonzero if something failed (e.g. the exception + * class couldn't be found, so *an* exception will still be pending). + * + * Currently aborts the VM if it can't throw the exception. + */ +[[maybe_unused]] static int jniThrowExceptionFmt(JNIEnv* env, const char* className, + const char* fmt, ...) { + va_list args; + va_start(args, fmt); + char msgBuf[512]; + vsnprintf(msgBuf, sizeof(msgBuf), fmt, args); + va_end(args); + return jniThrowException(env, className, msgBuf); +} + +[[maybe_unused]] static int jniThrowNullPointerException(JNIEnv* env, const char* msg) { + return jniThrowException(env, "java/lang/NullPointerException", msg); +} + +[[maybe_unused]] static int jniThrowRuntimeException(JNIEnv* env, const char* msg) { + return jniThrowException(env, "java/lang/RuntimeException", msg); +} + +[[maybe_unused]] static int jniThrowIOException(JNIEnv* env, int errno_value) { + using namespace android::jnihelp; + char buffer[80]; + const char* message = platformStrError(errno_value, buffer, sizeof(buffer)); + return jniThrowException(env, "java/io/IOException", message); +} + +/* + * Returns a Java String object created from UTF-16 data either from jchar or, + * if called from C++11, char16_t (a bitwise identical distinct type). + */ +[[maybe_unused]] static inline jstring jniCreateString(JNIEnv* env, const jchar* unicodeChars, + jsize len) { + return env->NewString(unicodeChars, len); +} + +[[maybe_unused]] static inline jstring jniCreateString(JNIEnv* env, const char16_t* unicodeChars, + jsize len) { + return jniCreateString(env, reinterpret_cast(unicodeChars), len); +} + +/* + * Log a message and an exception. + * If exception is NULL, logs the current exception in the JNI environment. + */ +[[maybe_unused]] static void jniLogException(JNIEnv* env, int priority, const char* tag, + jthrowable exception = NULL) { + using namespace android::jnihelp; + struct ExpandableString summary; + ExpandableStringInitialize(&summary); + GetStackTraceOrSummary(env, exception, &summary); + const char* details = (summary.data != NULL) ? summary.data : "No memory to report exception"; + __android_log_write(priority, tag, details); + ExpandableStringRelease(&summary); +} + +#else // defined(__cplusplus) + +// ART-internal only methods (not exported), exposed for legacy C users + +int jniRegisterNativeMethods(JNIEnv* env, const char* className, const JNINativeMethod* gMethods, + int numMethods); + +void jniLogException(JNIEnv* env, int priority, const char* tag, jthrowable thrown); + +int jniThrowException(JNIEnv* env, const char* className, const char* msg); + +int jniThrowNullPointerException(JNIEnv* env, const char* msg); + +#endif // defined(__cplusplus) diff --git a/include/nativehelper/ScopedLocalFrame.h b/include/nativehelper/ScopedLocalFrame.h new file mode 100644 index 0000000..387836c --- /dev/null +++ b/include/nativehelper/ScopedLocalFrame.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2010 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 + diff --git a/include/nativehelper/ScopedLocalRef.h b/include/nativehelper/ScopedLocalRef.h new file mode 100644 index 0000000..7631155 --- /dev/null +++ b/include/nativehelper/ScopedLocalRef.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2010 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 "JNIHelp.h" +#include + diff --git a/include/nativehelper/ScopedPrimitiveArray.h b/include/nativehelper/ScopedPrimitiveArray.h new file mode 100644 index 0000000..ee34556 --- /dev/null +++ b/include/nativehelper/ScopedPrimitiveArray.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2010 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 "JNIHelp.h" +#include + diff --git a/include/nativehelper/ScopedStringChars.h b/include/nativehelper/ScopedStringChars.h new file mode 100644 index 0000000..d82b2fa --- /dev/null +++ b/include/nativehelper/ScopedStringChars.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2011 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 "JNIHelp.h" +#include + diff --git a/include/nativehelper/ScopedUtfChars.h b/include/nativehelper/ScopedUtfChars.h new file mode 100644 index 0000000..dc41679 --- /dev/null +++ b/include/nativehelper/ScopedUtfChars.h @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2010 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 "JNIHelp.h" +#include + diff --git a/include/nativehelper/toStringArray.h b/include/nativehelper/toStringArray.h new file mode 100644 index 0000000..e4eeaca --- /dev/null +++ b/include/nativehelper/toStringArray.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011 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 + +#ifdef __cplusplus + +#include +#include + +#include "JNIHelp.h" +#include "ScopedLocalRef.h" + +template +jobjectArray toStringArray(JNIEnv* env, size_t count, StringVisitor&& visitor) { + jclass stringClass = env->FindClass("java/lang/String"); + ScopedLocalRef result(env, env->NewObjectArray(count, stringClass, NULL)); + env->DeleteLocalRef(stringClass); + if (result == nullptr) { + return nullptr; + } + for (size_t i = 0; i < count; ++i) { + ScopedLocalRef s(env, env->NewStringUTF(visitor(i))); + if (env->ExceptionCheck()) { + return nullptr; + } + env->SetObjectArrayElement(result.get(), i, s.get()); + if (env->ExceptionCheck()) { + return nullptr; + } + } + return result.release(); +} + +inline jobjectArray toStringArray(JNIEnv* env, const std::vector& strings) { + return toStringArray(env, strings.size(), [&strings](size_t i) { return strings[i].c_str(); }); +} + +inline jobjectArray toStringArray(JNIEnv* env, const char* const* strings) { + size_t count = 0; + for (; strings[count] != nullptr; ++count) {} + return toStringArray(env, count, [&strings](size_t i) { return strings[i]; }); +} + +template +jobjectArray toStringArray(JNIEnv* env, Counter* counter, Getter* getter) { + return toStringArray(env, counter(), [getter](size_t i) { return getter(i); }); +} + +#endif // __cplusplus + diff --git a/include_jni/jni.h b/include_jni/jni.h new file mode 100644 index 0000000..67417a5 --- /dev/null +++ b/include_jni/jni.h @@ -0,0 +1,1141 @@ +/* + * Copyright (C) 2006 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 specification, as defined by Sun: + * http://java.sun.com/javase/6/docs/technotes/guides/jni/spec/jniTOC.html + * + * Everything here is expected to be VM-neutral. + */ + +#pragma once + +#include +#include + +/* Primitive types that match up with Java equivalents. */ +typedef uint8_t jboolean; /* unsigned 8 bits */ +typedef int8_t jbyte; /* signed 8 bits */ +typedef uint16_t jchar; /* unsigned 16 bits */ +typedef int16_t jshort; /* signed 16 bits */ +typedef int32_t jint; /* signed 32 bits */ +typedef int64_t jlong; /* signed 64 bits */ +typedef float jfloat; /* 32-bit IEEE 754 */ +typedef double jdouble; /* 64-bit IEEE 754 */ + +/* "cardinal indices and sizes" */ +typedef jint jsize; + +#ifdef __cplusplus +/* + * Reference types, in C++ + */ +class _jobject {}; +class _jclass : public _jobject {}; +class _jstring : public _jobject {}; +class _jarray : public _jobject {}; +class _jobjectArray : public _jarray {}; +class _jbooleanArray : public _jarray {}; +class _jbyteArray : public _jarray {}; +class _jcharArray : public _jarray {}; +class _jshortArray : public _jarray {}; +class _jintArray : public _jarray {}; +class _jlongArray : public _jarray {}; +class _jfloatArray : public _jarray {}; +class _jdoubleArray : public _jarray {}; +class _jthrowable : public _jobject {}; + +typedef _jobject* jobject; +typedef _jclass* jclass; +typedef _jstring* jstring; +typedef _jarray* jarray; +typedef _jobjectArray* jobjectArray; +typedef _jbooleanArray* jbooleanArray; +typedef _jbyteArray* jbyteArray; +typedef _jcharArray* jcharArray; +typedef _jshortArray* jshortArray; +typedef _jintArray* jintArray; +typedef _jlongArray* jlongArray; +typedef _jfloatArray* jfloatArray; +typedef _jdoubleArray* jdoubleArray; +typedef _jthrowable* jthrowable; +typedef _jobject* jweak; + + +#else /* not __cplusplus */ + +/* + * Reference types, in C. + */ +typedef void* jobject; +typedef jobject jclass; +typedef jobject jstring; +typedef jobject jarray; +typedef jarray jobjectArray; +typedef jarray jbooleanArray; +typedef jarray jbyteArray; +typedef jarray jcharArray; +typedef jarray jshortArray; +typedef jarray jintArray; +typedef jarray jlongArray; +typedef jarray jfloatArray; +typedef jarray jdoubleArray; +typedef jobject jthrowable; +typedef jobject jweak; + +#endif /* not __cplusplus */ + +struct _jfieldID; /* opaque structure */ +typedef struct _jfieldID* jfieldID; /* field IDs */ + +struct _jmethodID; /* opaque structure */ +typedef struct _jmethodID* jmethodID; /* method IDs */ + +struct JNIInvokeInterface; + +typedef union jvalue { + jboolean z; + jbyte b; + jchar c; + jshort s; + jint i; + jlong j; + jfloat f; + jdouble d; + jobject l; +} jvalue; + +typedef enum jobjectRefType { + JNIInvalidRefType = 0, + JNILocalRefType = 1, + JNIGlobalRefType = 2, + JNIWeakGlobalRefType = 3 +} jobjectRefType; + +typedef struct { + const char* name; + const char* signature; + void* fnPtr; +} JNINativeMethod; + +struct _JNIEnv; +struct _JavaVM; +typedef const struct JNINativeInterface* C_JNIEnv; + +#if defined(__cplusplus) +typedef _JNIEnv JNIEnv; +typedef _JavaVM JavaVM; +#else +typedef const struct JNINativeInterface* JNIEnv; +typedef const struct JNIInvokeInterface* JavaVM; +#endif + +/* + * Table of interface function pointers. + */ +struct JNINativeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + void* reserved3; + + jint (*GetVersion)(JNIEnv *); + + jclass (*DefineClass)(JNIEnv*, const char*, jobject, const jbyte*, + jsize); + jclass (*FindClass)(JNIEnv*, const char*); + + jmethodID (*FromReflectedMethod)(JNIEnv*, jobject); + jfieldID (*FromReflectedField)(JNIEnv*, jobject); + /* spec doesn't show jboolean parameter */ + jobject (*ToReflectedMethod)(JNIEnv*, jclass, jmethodID, jboolean); + + jclass (*GetSuperclass)(JNIEnv*, jclass); + jboolean (*IsAssignableFrom)(JNIEnv*, jclass, jclass); + + /* spec doesn't show jboolean parameter */ + jobject (*ToReflectedField)(JNIEnv*, jclass, jfieldID, jboolean); + + jint (*Throw)(JNIEnv*, jthrowable); + jint (*ThrowNew)(JNIEnv *, jclass, const char *); + jthrowable (*ExceptionOccurred)(JNIEnv*); + void (*ExceptionDescribe)(JNIEnv*); + void (*ExceptionClear)(JNIEnv*); + void (*FatalError)(JNIEnv*, const char*); + + jint (*PushLocalFrame)(JNIEnv*, jint); + jobject (*PopLocalFrame)(JNIEnv*, jobject); + + jobject (*NewGlobalRef)(JNIEnv*, jobject); + void (*DeleteGlobalRef)(JNIEnv*, jobject); + void (*DeleteLocalRef)(JNIEnv*, jobject); + jboolean (*IsSameObject)(JNIEnv*, jobject, jobject); + + jobject (*NewLocalRef)(JNIEnv*, jobject); + jint (*EnsureLocalCapacity)(JNIEnv*, jint); + + jobject (*AllocObject)(JNIEnv*, jclass); + jobject (*NewObject)(JNIEnv*, jclass, jmethodID, ...); + jobject (*NewObjectV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*NewObjectA)(JNIEnv*, jclass, jmethodID, const jvalue*); + + jclass (*GetObjectClass)(JNIEnv*, jobject); + jboolean (*IsInstanceOf)(JNIEnv*, jobject, jclass); + jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*CallObjectMethod)(JNIEnv*, jobject, jmethodID, ...); + jobject (*CallObjectMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jobject (*CallObjectMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jboolean (*CallBooleanMethod)(JNIEnv*, jobject, jmethodID, ...); + jboolean (*CallBooleanMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jboolean (*CallBooleanMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jbyte (*CallByteMethod)(JNIEnv*, jobject, jmethodID, ...); + jbyte (*CallByteMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jbyte (*CallByteMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jchar (*CallCharMethod)(JNIEnv*, jobject, jmethodID, ...); + jchar (*CallCharMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jchar (*CallCharMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jshort (*CallShortMethod)(JNIEnv*, jobject, jmethodID, ...); + jshort (*CallShortMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jshort (*CallShortMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jint (*CallIntMethod)(JNIEnv*, jobject, jmethodID, ...); + jint (*CallIntMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jint (*CallIntMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jlong (*CallLongMethod)(JNIEnv*, jobject, jmethodID, ...); + jlong (*CallLongMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jlong (*CallLongMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jfloat (*CallFloatMethod)(JNIEnv*, jobject, jmethodID, ...); + jfloat (*CallFloatMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jfloat (*CallFloatMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + jdouble (*CallDoubleMethod)(JNIEnv*, jobject, jmethodID, ...); + jdouble (*CallDoubleMethodV)(JNIEnv*, jobject, jmethodID, va_list); + jdouble (*CallDoubleMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + void (*CallVoidMethod)(JNIEnv*, jobject, jmethodID, ...); + void (*CallVoidMethodV)(JNIEnv*, jobject, jmethodID, va_list); + void (*CallVoidMethodA)(JNIEnv*, jobject, jmethodID, const jvalue*); + + jobject (*CallNonvirtualObjectMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jobject (*CallNonvirtualObjectMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jobject (*CallNonvirtualObjectMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jboolean (*CallNonvirtualBooleanMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jboolean (*CallNonvirtualBooleanMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jboolean (*CallNonvirtualBooleanMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jbyte (*CallNonvirtualByteMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jbyte (*CallNonvirtualByteMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jbyte (*CallNonvirtualByteMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jchar (*CallNonvirtualCharMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jchar (*CallNonvirtualCharMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jchar (*CallNonvirtualCharMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jshort (*CallNonvirtualShortMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jshort (*CallNonvirtualShortMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jshort (*CallNonvirtualShortMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jint (*CallNonvirtualIntMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jint (*CallNonvirtualIntMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jint (*CallNonvirtualIntMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jlong (*CallNonvirtualLongMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jlong (*CallNonvirtualLongMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jlong (*CallNonvirtualLongMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jfloat (*CallNonvirtualFloatMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jfloat (*CallNonvirtualFloatMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jfloat (*CallNonvirtualFloatMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + jdouble (*CallNonvirtualDoubleMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + jdouble (*CallNonvirtualDoubleMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + jdouble (*CallNonvirtualDoubleMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + void (*CallNonvirtualVoidMethod)(JNIEnv*, jobject, jclass, + jmethodID, ...); + void (*CallNonvirtualVoidMethodV)(JNIEnv*, jobject, jclass, + jmethodID, va_list); + void (*CallNonvirtualVoidMethodA)(JNIEnv*, jobject, jclass, + jmethodID, const jvalue*); + + jfieldID (*GetFieldID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*GetObjectField)(JNIEnv*, jobject, jfieldID); + jboolean (*GetBooleanField)(JNIEnv*, jobject, jfieldID); + jbyte (*GetByteField)(JNIEnv*, jobject, jfieldID); + jchar (*GetCharField)(JNIEnv*, jobject, jfieldID); + jshort (*GetShortField)(JNIEnv*, jobject, jfieldID); + jint (*GetIntField)(JNIEnv*, jobject, jfieldID); + jlong (*GetLongField)(JNIEnv*, jobject, jfieldID); + jfloat (*GetFloatField)(JNIEnv*, jobject, jfieldID); + jdouble (*GetDoubleField)(JNIEnv*, jobject, jfieldID); + + void (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject); + void (*SetBooleanField)(JNIEnv*, jobject, jfieldID, jboolean); + void (*SetByteField)(JNIEnv*, jobject, jfieldID, jbyte); + void (*SetCharField)(JNIEnv*, jobject, jfieldID, jchar); + void (*SetShortField)(JNIEnv*, jobject, jfieldID, jshort); + void (*SetIntField)(JNIEnv*, jobject, jfieldID, jint); + void (*SetLongField)(JNIEnv*, jobject, jfieldID, jlong); + void (*SetFloatField)(JNIEnv*, jobject, jfieldID, jfloat); + void (*SetDoubleField)(JNIEnv*, jobject, jfieldID, jdouble); + + jmethodID (*GetStaticMethodID)(JNIEnv*, jclass, const char*, const char*); + + jobject (*CallStaticObjectMethod)(JNIEnv*, jclass, jmethodID, ...); + jobject (*CallStaticObjectMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jobject (*CallStaticObjectMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jboolean (*CallStaticBooleanMethod)(JNIEnv*, jclass, jmethodID, ...); + jboolean (*CallStaticBooleanMethodV)(JNIEnv*, jclass, jmethodID, + va_list); + jboolean (*CallStaticBooleanMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jbyte (*CallStaticByteMethod)(JNIEnv*, jclass, jmethodID, ...); + jbyte (*CallStaticByteMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jbyte (*CallStaticByteMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jchar (*CallStaticCharMethod)(JNIEnv*, jclass, jmethodID, ...); + jchar (*CallStaticCharMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jchar (*CallStaticCharMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jshort (*CallStaticShortMethod)(JNIEnv*, jclass, jmethodID, ...); + jshort (*CallStaticShortMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jshort (*CallStaticShortMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jint (*CallStaticIntMethod)(JNIEnv*, jclass, jmethodID, ...); + jint (*CallStaticIntMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jint (*CallStaticIntMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jlong (*CallStaticLongMethod)(JNIEnv*, jclass, jmethodID, ...); + jlong (*CallStaticLongMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jlong (*CallStaticLongMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jfloat (*CallStaticFloatMethod)(JNIEnv*, jclass, jmethodID, ...); + jfloat (*CallStaticFloatMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jfloat (*CallStaticFloatMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + jdouble (*CallStaticDoubleMethod)(JNIEnv*, jclass, jmethodID, ...); + jdouble (*CallStaticDoubleMethodV)(JNIEnv*, jclass, jmethodID, va_list); + jdouble (*CallStaticDoubleMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + void (*CallStaticVoidMethod)(JNIEnv*, jclass, jmethodID, ...); + void (*CallStaticVoidMethodV)(JNIEnv*, jclass, jmethodID, va_list); + void (*CallStaticVoidMethodA)(JNIEnv*, jclass, jmethodID, const jvalue*); + + jfieldID (*GetStaticFieldID)(JNIEnv*, jclass, const char*, + const char*); + + jobject (*GetStaticObjectField)(JNIEnv*, jclass, jfieldID); + jboolean (*GetStaticBooleanField)(JNIEnv*, jclass, jfieldID); + jbyte (*GetStaticByteField)(JNIEnv*, jclass, jfieldID); + jchar (*GetStaticCharField)(JNIEnv*, jclass, jfieldID); + jshort (*GetStaticShortField)(JNIEnv*, jclass, jfieldID); + jint (*GetStaticIntField)(JNIEnv*, jclass, jfieldID); + jlong (*GetStaticLongField)(JNIEnv*, jclass, jfieldID); + jfloat (*GetStaticFloatField)(JNIEnv*, jclass, jfieldID); + jdouble (*GetStaticDoubleField)(JNIEnv*, jclass, jfieldID); + + void (*SetStaticObjectField)(JNIEnv*, jclass, jfieldID, jobject); + void (*SetStaticBooleanField)(JNIEnv*, jclass, jfieldID, jboolean); + void (*SetStaticByteField)(JNIEnv*, jclass, jfieldID, jbyte); + void (*SetStaticCharField)(JNIEnv*, jclass, jfieldID, jchar); + void (*SetStaticShortField)(JNIEnv*, jclass, jfieldID, jshort); + void (*SetStaticIntField)(JNIEnv*, jclass, jfieldID, jint); + void (*SetStaticLongField)(JNIEnv*, jclass, jfieldID, jlong); + void (*SetStaticFloatField)(JNIEnv*, jclass, jfieldID, jfloat); + void (*SetStaticDoubleField)(JNIEnv*, jclass, jfieldID, jdouble); + + jstring (*NewString)(JNIEnv*, const jchar*, jsize); + jsize (*GetStringLength)(JNIEnv*, jstring); + const jchar* (*GetStringChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringChars)(JNIEnv*, jstring, const jchar*); + jstring (*NewStringUTF)(JNIEnv*, const char*); + jsize (*GetStringUTFLength)(JNIEnv*, jstring); + /* JNI spec says this returns const jbyte*, but that's inconsistent */ + const char* (*GetStringUTFChars)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringUTFChars)(JNIEnv*, jstring, const char*); + jsize (*GetArrayLength)(JNIEnv*, jarray); + jobjectArray (*NewObjectArray)(JNIEnv*, jsize, jclass, jobject); + jobject (*GetObjectArrayElement)(JNIEnv*, jobjectArray, jsize); + void (*SetObjectArrayElement)(JNIEnv*, jobjectArray, jsize, jobject); + + jbooleanArray (*NewBooleanArray)(JNIEnv*, jsize); + jbyteArray (*NewByteArray)(JNIEnv*, jsize); + jcharArray (*NewCharArray)(JNIEnv*, jsize); + jshortArray (*NewShortArray)(JNIEnv*, jsize); + jintArray (*NewIntArray)(JNIEnv*, jsize); + jlongArray (*NewLongArray)(JNIEnv*, jsize); + jfloatArray (*NewFloatArray)(JNIEnv*, jsize); + jdoubleArray (*NewDoubleArray)(JNIEnv*, jsize); + + jboolean* (*GetBooleanArrayElements)(JNIEnv*, jbooleanArray, jboolean*); + jbyte* (*GetByteArrayElements)(JNIEnv*, jbyteArray, jboolean*); + jchar* (*GetCharArrayElements)(JNIEnv*, jcharArray, jboolean*); + jshort* (*GetShortArrayElements)(JNIEnv*, jshortArray, jboolean*); + jint* (*GetIntArrayElements)(JNIEnv*, jintArray, jboolean*); + jlong* (*GetLongArrayElements)(JNIEnv*, jlongArray, jboolean*); + jfloat* (*GetFloatArrayElements)(JNIEnv*, jfloatArray, jboolean*); + jdouble* (*GetDoubleArrayElements)(JNIEnv*, jdoubleArray, jboolean*); + + void (*ReleaseBooleanArrayElements)(JNIEnv*, jbooleanArray, + jboolean*, jint); + void (*ReleaseByteArrayElements)(JNIEnv*, jbyteArray, + jbyte*, jint); + void (*ReleaseCharArrayElements)(JNIEnv*, jcharArray, + jchar*, jint); + void (*ReleaseShortArrayElements)(JNIEnv*, jshortArray, + jshort*, jint); + void (*ReleaseIntArrayElements)(JNIEnv*, jintArray, + jint*, jint); + void (*ReleaseLongArrayElements)(JNIEnv*, jlongArray, + jlong*, jint); + void (*ReleaseFloatArrayElements)(JNIEnv*, jfloatArray, + jfloat*, jint); + void (*ReleaseDoubleArrayElements)(JNIEnv*, jdoubleArray, + jdouble*, jint); + + void (*GetBooleanArrayRegion)(JNIEnv*, jbooleanArray, + jsize, jsize, jboolean*); + void (*GetByteArrayRegion)(JNIEnv*, jbyteArray, + jsize, jsize, jbyte*); + void (*GetCharArrayRegion)(JNIEnv*, jcharArray, + jsize, jsize, jchar*); + void (*GetShortArrayRegion)(JNIEnv*, jshortArray, + jsize, jsize, jshort*); + void (*GetIntArrayRegion)(JNIEnv*, jintArray, + jsize, jsize, jint*); + void (*GetLongArrayRegion)(JNIEnv*, jlongArray, + jsize, jsize, jlong*); + void (*GetFloatArrayRegion)(JNIEnv*, jfloatArray, + jsize, jsize, jfloat*); + void (*GetDoubleArrayRegion)(JNIEnv*, jdoubleArray, + jsize, jsize, jdouble*); + + /* spec shows these without const; some jni.h do, some don't */ + void (*SetBooleanArrayRegion)(JNIEnv*, jbooleanArray, + jsize, jsize, const jboolean*); + void (*SetByteArrayRegion)(JNIEnv*, jbyteArray, + jsize, jsize, const jbyte*); + void (*SetCharArrayRegion)(JNIEnv*, jcharArray, + jsize, jsize, const jchar*); + void (*SetShortArrayRegion)(JNIEnv*, jshortArray, + jsize, jsize, const jshort*); + void (*SetIntArrayRegion)(JNIEnv*, jintArray, + jsize, jsize, const jint*); + void (*SetLongArrayRegion)(JNIEnv*, jlongArray, + jsize, jsize, const jlong*); + void (*SetFloatArrayRegion)(JNIEnv*, jfloatArray, + jsize, jsize, const jfloat*); + void (*SetDoubleArrayRegion)(JNIEnv*, jdoubleArray, + jsize, jsize, const jdouble*); + + jint (*RegisterNatives)(JNIEnv*, jclass, const JNINativeMethod*, + jint); + jint (*UnregisterNatives)(JNIEnv*, jclass); + jint (*MonitorEnter)(JNIEnv*, jobject); + jint (*MonitorExit)(JNIEnv*, jobject); + jint (*GetJavaVM)(JNIEnv*, JavaVM**); + + void (*GetStringRegion)(JNIEnv*, jstring, jsize, jsize, jchar*); + void (*GetStringUTFRegion)(JNIEnv*, jstring, jsize, jsize, char*); + + void* (*GetPrimitiveArrayCritical)(JNIEnv*, jarray, jboolean*); + void (*ReleasePrimitiveArrayCritical)(JNIEnv*, jarray, void*, jint); + + const jchar* (*GetStringCritical)(JNIEnv*, jstring, jboolean*); + void (*ReleaseStringCritical)(JNIEnv*, jstring, const jchar*); + + jweak (*NewWeakGlobalRef)(JNIEnv*, jobject); + void (*DeleteWeakGlobalRef)(JNIEnv*, jweak); + + jboolean (*ExceptionCheck)(JNIEnv*); + + jobject (*NewDirectByteBuffer)(JNIEnv*, void*, jlong); + void* (*GetDirectBufferAddress)(JNIEnv*, jobject); + jlong (*GetDirectBufferCapacity)(JNIEnv*, jobject); + + /* added in JNI 1.6 */ + jobjectRefType (*GetObjectRefType)(JNIEnv*, jobject); +}; + +/* + * C++ object wrapper. + * + * This is usually overlaid on a C struct whose first element is a + * JNINativeInterface*. We rely somewhat on compiler behavior. + */ +struct _JNIEnv { + /* do not rename this; it does not seem to be entirely opaque */ + const struct JNINativeInterface* functions; + +#if defined(__cplusplus) + + jint GetVersion() + { return functions->GetVersion(this); } + + jclass DefineClass(const char *name, jobject loader, const jbyte* buf, + jsize bufLen) + { return functions->DefineClass(this, name, loader, buf, bufLen); } + + jclass FindClass(const char* name) + { return functions->FindClass(this, name); } + + jmethodID FromReflectedMethod(jobject method) + { return functions->FromReflectedMethod(this, method); } + + jfieldID FromReflectedField(jobject field) + { return functions->FromReflectedField(this, field); } + + jobject ToReflectedMethod(jclass cls, jmethodID methodID, jboolean isStatic) + { return functions->ToReflectedMethod(this, cls, methodID, isStatic); } + + jclass GetSuperclass(jclass clazz) + { return functions->GetSuperclass(this, clazz); } + + jboolean IsAssignableFrom(jclass clazz1, jclass clazz2) + { return functions->IsAssignableFrom(this, clazz1, clazz2); } + + jobject ToReflectedField(jclass cls, jfieldID fieldID, jboolean isStatic) + { return functions->ToReflectedField(this, cls, fieldID, isStatic); } + + jint Throw(jthrowable obj) + { return functions->Throw(this, obj); } + + jint ThrowNew(jclass clazz, const char* message) + { return functions->ThrowNew(this, clazz, message); } + + jthrowable ExceptionOccurred() + { return functions->ExceptionOccurred(this); } + + void ExceptionDescribe() + { functions->ExceptionDescribe(this); } + + void ExceptionClear() + { functions->ExceptionClear(this); } + + void FatalError(const char* msg) + { functions->FatalError(this, msg); } + + jint PushLocalFrame(jint capacity) + { return functions->PushLocalFrame(this, capacity); } + + jobject PopLocalFrame(jobject result) + { return functions->PopLocalFrame(this, result); } + + jobject NewGlobalRef(jobject obj) + { return functions->NewGlobalRef(this, obj); } + + void DeleteGlobalRef(jobject globalRef) + { functions->DeleteGlobalRef(this, globalRef); } + + void DeleteLocalRef(jobject localRef) + { functions->DeleteLocalRef(this, localRef); } + + jboolean IsSameObject(jobject ref1, jobject ref2) + { return functions->IsSameObject(this, ref1, ref2); } + + jobject NewLocalRef(jobject ref) + { return functions->NewLocalRef(this, ref); } + + jint EnsureLocalCapacity(jint capacity) + { return functions->EnsureLocalCapacity(this, capacity); } + + jobject AllocObject(jclass clazz) + { return functions->AllocObject(this, clazz); } + + jobject NewObject(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + jobject result = functions->NewObjectV(this, clazz, methodID, args); + va_end(args); + return result; + } + + jobject NewObjectV(jclass clazz, jmethodID methodID, va_list args) + { return functions->NewObjectV(this, clazz, methodID, args); } + + jobject NewObjectA(jclass clazz, jmethodID methodID, const jvalue* args) + { return functions->NewObjectA(this, clazz, methodID, args); } + + jclass GetObjectClass(jobject obj) + { return functions->GetObjectClass(this, obj); } + + jboolean IsInstanceOf(jobject obj, jclass clazz) + { return functions->IsInstanceOf(this, obj, clazz); } + + jmethodID GetMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetMethodID(this, clazz, name, sig); } + +#define CALL_TYPE_METHOD(_jtype, _jname) \ + _jtype Call##_jname##Method(jobject obj, jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->Call##_jname##MethodV(this, obj, methodID, \ + args); \ + va_end(args); \ + return result; \ + } +#define CALL_TYPE_METHODV(_jtype, _jname) \ + _jtype Call##_jname##MethodV(jobject obj, jmethodID methodID, \ + va_list args) \ + { return functions->Call##_jname##MethodV(this, obj, methodID, args); } +#define CALL_TYPE_METHODA(_jtype, _jname) \ + _jtype Call##_jname##MethodA(jobject obj, jmethodID methodID, \ + const jvalue* args) \ + { return functions->Call##_jname##MethodA(this, obj, methodID, args); } + +#define CALL_TYPE(_jtype, _jname) \ + CALL_TYPE_METHOD(_jtype, _jname) \ + CALL_TYPE_METHODV(_jtype, _jname) \ + CALL_TYPE_METHODA(_jtype, _jname) + + CALL_TYPE(jobject, Object) + CALL_TYPE(jboolean, Boolean) + CALL_TYPE(jbyte, Byte) + CALL_TYPE(jchar, Char) + CALL_TYPE(jshort, Short) + CALL_TYPE(jint, Int) + CALL_TYPE(jlong, Long) + CALL_TYPE(jfloat, Float) + CALL_TYPE(jdouble, Double) + + void CallVoidMethod(jobject obj, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallVoidMethodV(this, obj, methodID, args); + va_end(args); + } + void CallVoidMethodV(jobject obj, jmethodID methodID, va_list args) + { functions->CallVoidMethodV(this, obj, methodID, args); } + void CallVoidMethodA(jobject obj, jmethodID methodID, const jvalue* args) + { functions->CallVoidMethodA(this, obj, methodID, args); } + +#define CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##Method(jobject obj, jclass clazz, \ + jmethodID methodID, ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallNonvirtual##_jname##MethodV(this, obj, \ + clazz, methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodV(jobject obj, jclass clazz, \ + jmethodID methodID, va_list args) \ + { return functions->CallNonvirtual##_jname##MethodV(this, obj, clazz, \ + methodID, args); } +#define CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) \ + _jtype CallNonvirtual##_jname##MethodA(jobject obj, jclass clazz, \ + jmethodID methodID, const jvalue* args) \ + { return functions->CallNonvirtual##_jname##MethodA(this, obj, clazz, \ + methodID, args); } + +#define CALL_NONVIRT_TYPE(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHOD(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODV(_jtype, _jname) \ + CALL_NONVIRT_TYPE_METHODA(_jtype, _jname) + + CALL_NONVIRT_TYPE(jobject, Object) + CALL_NONVIRT_TYPE(jboolean, Boolean) + CALL_NONVIRT_TYPE(jbyte, Byte) + CALL_NONVIRT_TYPE(jchar, Char) + CALL_NONVIRT_TYPE(jshort, Short) + CALL_NONVIRT_TYPE(jint, Int) + CALL_NONVIRT_TYPE(jlong, Long) + CALL_NONVIRT_TYPE(jfloat, Float) + CALL_NONVIRT_TYPE(jdouble, Double) + + void CallNonvirtualVoidMethod(jobject obj, jclass clazz, + jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); + va_end(args); + } + void CallNonvirtualVoidMethodV(jobject obj, jclass clazz, + jmethodID methodID, va_list args) + { functions->CallNonvirtualVoidMethodV(this, obj, clazz, methodID, args); } + void CallNonvirtualVoidMethodA(jobject obj, jclass clazz, + jmethodID methodID, const jvalue* args) + { functions->CallNonvirtualVoidMethodA(this, obj, clazz, methodID, args); } + + jfieldID GetFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetFieldID(this, clazz, name, sig); } + + jobject GetObjectField(jobject obj, jfieldID fieldID) + { return functions->GetObjectField(this, obj, fieldID); } + jboolean GetBooleanField(jobject obj, jfieldID fieldID) + { return functions->GetBooleanField(this, obj, fieldID); } + jbyte GetByteField(jobject obj, jfieldID fieldID) + { return functions->GetByteField(this, obj, fieldID); } + jchar GetCharField(jobject obj, jfieldID fieldID) + { return functions->GetCharField(this, obj, fieldID); } + jshort GetShortField(jobject obj, jfieldID fieldID) + { return functions->GetShortField(this, obj, fieldID); } + jint GetIntField(jobject obj, jfieldID fieldID) + { return functions->GetIntField(this, obj, fieldID); } + jlong GetLongField(jobject obj, jfieldID fieldID) + { return functions->GetLongField(this, obj, fieldID); } + jfloat GetFloatField(jobject obj, jfieldID fieldID) + { return functions->GetFloatField(this, obj, fieldID); } + jdouble GetDoubleField(jobject obj, jfieldID fieldID) + { return functions->GetDoubleField(this, obj, fieldID); } + + void SetObjectField(jobject obj, jfieldID fieldID, jobject value) + { functions->SetObjectField(this, obj, fieldID, value); } + void SetBooleanField(jobject obj, jfieldID fieldID, jboolean value) + { functions->SetBooleanField(this, obj, fieldID, value); } + void SetByteField(jobject obj, jfieldID fieldID, jbyte value) + { functions->SetByteField(this, obj, fieldID, value); } + void SetCharField(jobject obj, jfieldID fieldID, jchar value) + { functions->SetCharField(this, obj, fieldID, value); } + void SetShortField(jobject obj, jfieldID fieldID, jshort value) + { functions->SetShortField(this, obj, fieldID, value); } + void SetIntField(jobject obj, jfieldID fieldID, jint value) + { functions->SetIntField(this, obj, fieldID, value); } + void SetLongField(jobject obj, jfieldID fieldID, jlong value) + { functions->SetLongField(this, obj, fieldID, value); } + void SetFloatField(jobject obj, jfieldID fieldID, jfloat value) + { functions->SetFloatField(this, obj, fieldID, value); } + void SetDoubleField(jobject obj, jfieldID fieldID, jdouble value) + { functions->SetDoubleField(this, obj, fieldID, value); } + + jmethodID GetStaticMethodID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticMethodID(this, clazz, name, sig); } + +#define CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + _jtype CallStatic##_jname##Method(jclass clazz, jmethodID methodID, \ + ...) \ + { \ + _jtype result; \ + va_list args; \ + va_start(args, methodID); \ + result = functions->CallStatic##_jname##MethodV(this, clazz, \ + methodID, args); \ + va_end(args); \ + return result; \ + } +#define CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodV(jclass clazz, jmethodID methodID, \ + va_list args) \ + { return functions->CallStatic##_jname##MethodV(this, clazz, methodID, \ + args); } +#define CALL_STATIC_TYPE_METHODA(_jtype, _jname) \ + _jtype CallStatic##_jname##MethodA(jclass clazz, jmethodID methodID, \ + const jvalue* args) \ + { return functions->CallStatic##_jname##MethodA(this, clazz, methodID, \ + args); } + +#define CALL_STATIC_TYPE(_jtype, _jname) \ + CALL_STATIC_TYPE_METHOD(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODV(_jtype, _jname) \ + CALL_STATIC_TYPE_METHODA(_jtype, _jname) + + CALL_STATIC_TYPE(jobject, Object) + CALL_STATIC_TYPE(jboolean, Boolean) + CALL_STATIC_TYPE(jbyte, Byte) + CALL_STATIC_TYPE(jchar, Char) + CALL_STATIC_TYPE(jshort, Short) + CALL_STATIC_TYPE(jint, Int) + CALL_STATIC_TYPE(jlong, Long) + CALL_STATIC_TYPE(jfloat, Float) + CALL_STATIC_TYPE(jdouble, Double) + + void CallStaticVoidMethod(jclass clazz, jmethodID methodID, ...) + { + va_list args; + va_start(args, methodID); + functions->CallStaticVoidMethodV(this, clazz, methodID, args); + va_end(args); + } + void CallStaticVoidMethodV(jclass clazz, jmethodID methodID, va_list args) + { functions->CallStaticVoidMethodV(this, clazz, methodID, args); } + void CallStaticVoidMethodA(jclass clazz, jmethodID methodID, const jvalue* args) + { functions->CallStaticVoidMethodA(this, clazz, methodID, args); } + + jfieldID GetStaticFieldID(jclass clazz, const char* name, const char* sig) + { return functions->GetStaticFieldID(this, clazz, name, sig); } + + jobject GetStaticObjectField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticObjectField(this, clazz, fieldID); } + jboolean GetStaticBooleanField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticBooleanField(this, clazz, fieldID); } + jbyte GetStaticByteField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticByteField(this, clazz, fieldID); } + jchar GetStaticCharField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticCharField(this, clazz, fieldID); } + jshort GetStaticShortField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticShortField(this, clazz, fieldID); } + jint GetStaticIntField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticIntField(this, clazz, fieldID); } + jlong GetStaticLongField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticLongField(this, clazz, fieldID); } + jfloat GetStaticFloatField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticFloatField(this, clazz, fieldID); } + jdouble GetStaticDoubleField(jclass clazz, jfieldID fieldID) + { return functions->GetStaticDoubleField(this, clazz, fieldID); } + + void SetStaticObjectField(jclass clazz, jfieldID fieldID, jobject value) + { functions->SetStaticObjectField(this, clazz, fieldID, value); } + void SetStaticBooleanField(jclass clazz, jfieldID fieldID, jboolean value) + { functions->SetStaticBooleanField(this, clazz, fieldID, value); } + void SetStaticByteField(jclass clazz, jfieldID fieldID, jbyte value) + { functions->SetStaticByteField(this, clazz, fieldID, value); } + void SetStaticCharField(jclass clazz, jfieldID fieldID, jchar value) + { functions->SetStaticCharField(this, clazz, fieldID, value); } + void SetStaticShortField(jclass clazz, jfieldID fieldID, jshort value) + { functions->SetStaticShortField(this, clazz, fieldID, value); } + void SetStaticIntField(jclass clazz, jfieldID fieldID, jint value) + { functions->SetStaticIntField(this, clazz, fieldID, value); } + void SetStaticLongField(jclass clazz, jfieldID fieldID, jlong value) + { functions->SetStaticLongField(this, clazz, fieldID, value); } + void SetStaticFloatField(jclass clazz, jfieldID fieldID, jfloat value) + { functions->SetStaticFloatField(this, clazz, fieldID, value); } + void SetStaticDoubleField(jclass clazz, jfieldID fieldID, jdouble value) + { functions->SetStaticDoubleField(this, clazz, fieldID, value); } + + jstring NewString(const jchar* unicodeChars, jsize len) + { return functions->NewString(this, unicodeChars, len); } + + jsize GetStringLength(jstring string) + { return functions->GetStringLength(this, string); } + + const jchar* GetStringChars(jstring string, jboolean* isCopy) + { return functions->GetStringChars(this, string, isCopy); } + + void ReleaseStringChars(jstring string, const jchar* chars) + { functions->ReleaseStringChars(this, string, chars); } + + jstring NewStringUTF(const char* bytes) + { return functions->NewStringUTF(this, bytes); } + + jsize GetStringUTFLength(jstring string) + { return functions->GetStringUTFLength(this, string); } + + const char* GetStringUTFChars(jstring string, jboolean* isCopy) + { return functions->GetStringUTFChars(this, string, isCopy); } + + void ReleaseStringUTFChars(jstring string, const char* utf) + { functions->ReleaseStringUTFChars(this, string, utf); } + + jsize GetArrayLength(jarray array) + { return functions->GetArrayLength(this, array); } + + jobjectArray NewObjectArray(jsize length, jclass elementClass, + jobject initialElement) + { return functions->NewObjectArray(this, length, elementClass, + initialElement); } + + jobject GetObjectArrayElement(jobjectArray array, jsize index) + { return functions->GetObjectArrayElement(this, array, index); } + + void SetObjectArrayElement(jobjectArray array, jsize index, jobject value) + { functions->SetObjectArrayElement(this, array, index, value); } + + jbooleanArray NewBooleanArray(jsize length) + { return functions->NewBooleanArray(this, length); } + jbyteArray NewByteArray(jsize length) + { return functions->NewByteArray(this, length); } + jcharArray NewCharArray(jsize length) + { return functions->NewCharArray(this, length); } + jshortArray NewShortArray(jsize length) + { return functions->NewShortArray(this, length); } + jintArray NewIntArray(jsize length) + { return functions->NewIntArray(this, length); } + jlongArray NewLongArray(jsize length) + { return functions->NewLongArray(this, length); } + jfloatArray NewFloatArray(jsize length) + { return functions->NewFloatArray(this, length); } + jdoubleArray NewDoubleArray(jsize length) + { return functions->NewDoubleArray(this, length); } + + jboolean* GetBooleanArrayElements(jbooleanArray array, jboolean* isCopy) + { return functions->GetBooleanArrayElements(this, array, isCopy); } + jbyte* GetByteArrayElements(jbyteArray array, jboolean* isCopy) + { return functions->GetByteArrayElements(this, array, isCopy); } + jchar* GetCharArrayElements(jcharArray array, jboolean* isCopy) + { return functions->GetCharArrayElements(this, array, isCopy); } + jshort* GetShortArrayElements(jshortArray array, jboolean* isCopy) + { return functions->GetShortArrayElements(this, array, isCopy); } + jint* GetIntArrayElements(jintArray array, jboolean* isCopy) + { return functions->GetIntArrayElements(this, array, isCopy); } + jlong* GetLongArrayElements(jlongArray array, jboolean* isCopy) + { return functions->GetLongArrayElements(this, array, isCopy); } + jfloat* GetFloatArrayElements(jfloatArray array, jboolean* isCopy) + { return functions->GetFloatArrayElements(this, array, isCopy); } + jdouble* GetDoubleArrayElements(jdoubleArray array, jboolean* isCopy) + { return functions->GetDoubleArrayElements(this, array, isCopy); } + + void ReleaseBooleanArrayElements(jbooleanArray array, jboolean* elems, + jint mode) + { functions->ReleaseBooleanArrayElements(this, array, elems, mode); } + void ReleaseByteArrayElements(jbyteArray array, jbyte* elems, + jint mode) + { functions->ReleaseByteArrayElements(this, array, elems, mode); } + void ReleaseCharArrayElements(jcharArray array, jchar* elems, + jint mode) + { functions->ReleaseCharArrayElements(this, array, elems, mode); } + void ReleaseShortArrayElements(jshortArray array, jshort* elems, + jint mode) + { functions->ReleaseShortArrayElements(this, array, elems, mode); } + void ReleaseIntArrayElements(jintArray array, jint* elems, + jint mode) + { functions->ReleaseIntArrayElements(this, array, elems, mode); } + void ReleaseLongArrayElements(jlongArray array, jlong* elems, + jint mode) + { functions->ReleaseLongArrayElements(this, array, elems, mode); } + void ReleaseFloatArrayElements(jfloatArray array, jfloat* elems, + jint mode) + { functions->ReleaseFloatArrayElements(this, array, elems, mode); } + void ReleaseDoubleArrayElements(jdoubleArray array, jdouble* elems, + jint mode) + { functions->ReleaseDoubleArrayElements(this, array, elems, mode); } + + void GetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + jboolean* buf) + { functions->GetBooleanArrayRegion(this, array, start, len, buf); } + void GetByteArrayRegion(jbyteArray array, jsize start, jsize len, + jbyte* buf) + { functions->GetByteArrayRegion(this, array, start, len, buf); } + void GetCharArrayRegion(jcharArray array, jsize start, jsize len, + jchar* buf) + { functions->GetCharArrayRegion(this, array, start, len, buf); } + void GetShortArrayRegion(jshortArray array, jsize start, jsize len, + jshort* buf) + { functions->GetShortArrayRegion(this, array, start, len, buf); } + void GetIntArrayRegion(jintArray array, jsize start, jsize len, + jint* buf) + { functions->GetIntArrayRegion(this, array, start, len, buf); } + void GetLongArrayRegion(jlongArray array, jsize start, jsize len, + jlong* buf) + { functions->GetLongArrayRegion(this, array, start, len, buf); } + void GetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + jfloat* buf) + { functions->GetFloatArrayRegion(this, array, start, len, buf); } + void GetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + jdouble* buf) + { functions->GetDoubleArrayRegion(this, array, start, len, buf); } + + void SetBooleanArrayRegion(jbooleanArray array, jsize start, jsize len, + const jboolean* buf) + { functions->SetBooleanArrayRegion(this, array, start, len, buf); } + void SetByteArrayRegion(jbyteArray array, jsize start, jsize len, + const jbyte* buf) + { functions->SetByteArrayRegion(this, array, start, len, buf); } + void SetCharArrayRegion(jcharArray array, jsize start, jsize len, + const jchar* buf) + { functions->SetCharArrayRegion(this, array, start, len, buf); } + void SetShortArrayRegion(jshortArray array, jsize start, jsize len, + const jshort* buf) + { functions->SetShortArrayRegion(this, array, start, len, buf); } + void SetIntArrayRegion(jintArray array, jsize start, jsize len, + const jint* buf) + { functions->SetIntArrayRegion(this, array, start, len, buf); } + void SetLongArrayRegion(jlongArray array, jsize start, jsize len, + const jlong* buf) + { functions->SetLongArrayRegion(this, array, start, len, buf); } + void SetFloatArrayRegion(jfloatArray array, jsize start, jsize len, + const jfloat* buf) + { functions->SetFloatArrayRegion(this, array, start, len, buf); } + void SetDoubleArrayRegion(jdoubleArray array, jsize start, jsize len, + const jdouble* buf) + { functions->SetDoubleArrayRegion(this, array, start, len, buf); } + + jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, + jint nMethods) + { return functions->RegisterNatives(this, clazz, methods, nMethods); } + + jint UnregisterNatives(jclass clazz) + { return functions->UnregisterNatives(this, clazz); } + + jint MonitorEnter(jobject obj) + { return functions->MonitorEnter(this, obj); } + + jint MonitorExit(jobject obj) + { return functions->MonitorExit(this, obj); } + + jint GetJavaVM(JavaVM** vm) + { return functions->GetJavaVM(this, vm); } + + void GetStringRegion(jstring str, jsize start, jsize len, jchar* buf) + { functions->GetStringRegion(this, str, start, len, buf); } + + void GetStringUTFRegion(jstring str, jsize start, jsize len, char* buf) + { return functions->GetStringUTFRegion(this, str, start, len, buf); } + + void* GetPrimitiveArrayCritical(jarray array, jboolean* isCopy) + { return functions->GetPrimitiveArrayCritical(this, array, isCopy); } + + void ReleasePrimitiveArrayCritical(jarray array, void* carray, jint mode) + { functions->ReleasePrimitiveArrayCritical(this, array, carray, mode); } + + const jchar* GetStringCritical(jstring string, jboolean* isCopy) + { return functions->GetStringCritical(this, string, isCopy); } + + void ReleaseStringCritical(jstring string, const jchar* carray) + { functions->ReleaseStringCritical(this, string, carray); } + + jweak NewWeakGlobalRef(jobject obj) + { return functions->NewWeakGlobalRef(this, obj); } + + void DeleteWeakGlobalRef(jweak obj) + { functions->DeleteWeakGlobalRef(this, obj); } + + jboolean ExceptionCheck() + { return functions->ExceptionCheck(this); } + + jobject NewDirectByteBuffer(void* address, jlong capacity) + { return functions->NewDirectByteBuffer(this, address, capacity); } + + void* GetDirectBufferAddress(jobject buf) + { return functions->GetDirectBufferAddress(this, buf); } + + jlong GetDirectBufferCapacity(jobject buf) + { return functions->GetDirectBufferCapacity(this, buf); } + + /* added in JNI 1.6 */ + jobjectRefType GetObjectRefType(jobject obj) + { return functions->GetObjectRefType(this, obj); } +#endif /*__cplusplus*/ +}; + + +/* + * JNI invocation interface. + */ +struct JNIInvokeInterface { + void* reserved0; + void* reserved1; + void* reserved2; + + jint (*DestroyJavaVM)(JavaVM*); + jint (*AttachCurrentThread)(JavaVM*, JNIEnv**, void*); + jint (*DetachCurrentThread)(JavaVM*); + jint (*GetEnv)(JavaVM*, void**, jint); + jint (*AttachCurrentThreadAsDaemon)(JavaVM*, JNIEnv**, void*); +}; + +/* + * C++ version. + */ +struct _JavaVM { + const struct JNIInvokeInterface* functions; + +#if defined(__cplusplus) + jint DestroyJavaVM() + { return functions->DestroyJavaVM(this); } + jint AttachCurrentThread(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThread(this, p_env, thr_args); } + jint DetachCurrentThread() + { return functions->DetachCurrentThread(this); } + jint GetEnv(void** env, jint version) + { return functions->GetEnv(this, env, version); } + jint AttachCurrentThreadAsDaemon(JNIEnv** p_env, void* thr_args) + { return functions->AttachCurrentThreadAsDaemon(this, p_env, thr_args); } +#endif /*__cplusplus*/ +}; + +struct JavaVMAttachArgs { + jint version; /* must be >= JNI_VERSION_1_2 */ + const char* name; /* NULL or name of thread as modified UTF-8 str */ + jobject group; /* global ref of a ThreadGroup object, or NULL */ +}; +typedef struct JavaVMAttachArgs JavaVMAttachArgs; + +/* + * JNI 1.2+ initialization. (As of 1.6, the pre-1.2 structures are no + * longer supported.) + */ +typedef struct JavaVMOption { + const char* optionString; + void* extraInfo; +} JavaVMOption; + +typedef struct JavaVMInitArgs { + jint version; /* use JNI_VERSION_1_2 or later */ + + jint nOptions; + JavaVMOption* options; + jboolean ignoreUnrecognized; +} JavaVMInitArgs; + +#ifdef __cplusplus +extern "C" { +#endif +/* + * VM initialization functions. + * + * Note these are the only symbols exported for JNI by the VM. + */ +jint JNI_GetDefaultJavaVMInitArgs(void*); +jint JNI_CreateJavaVM(JavaVM**, JNIEnv**, void*); +jint JNI_GetCreatedJavaVMs(JavaVM**, jsize, jsize*); + +#define JNIIMPORT +#define JNIEXPORT __attribute__ ((visibility ("default"))) +#define JNICALL + +/* + * Prototypes for functions exported by loadable shared libs. These are + * called by JNI, not provided by JNI. + */ +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved); +JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved); + +#ifdef __cplusplus +} +#endif + + +/* + * Manifest constants. + */ +#define JNI_FALSE 0 +#define JNI_TRUE 1 + +#define JNI_VERSION_1_1 0x00010001 +#define JNI_VERSION_1_2 0x00010002 +#define JNI_VERSION_1_4 0x00010004 +#define JNI_VERSION_1_6 0x00010006 + +#define JNI_OK (0) /* no error */ +#define JNI_ERR (-1) /* generic error */ +#define JNI_EDETACHED (-2) /* thread detached from the VM */ +#define JNI_EVERSION (-3) /* JNI version error */ +#define JNI_ENOMEM (-4) /* Out of memory */ +#define JNI_EEXIST (-5) /* VM already created */ +#define JNI_EINVAL (-6) /* Invalid argument */ + +#define JNI_COMMIT 1 /* copy content, do not free buffer */ +#define JNI_ABORT 2 /* free buffer w/o copying back */ + diff --git a/include_platform/nativehelper/JNIPlatformHelp.h b/include_platform/nativehelper/JNIPlatformHelp.h new file mode 100644 index 0000000..70f6136 --- /dev/null +++ b/include_platform/nativehelper/JNIPlatformHelp.h @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2007 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 helper functions. + * + * This file may be included by C or C++ code, which is trouble because jni.h + * uses different typedefs for JNIEnv in each language. + */ +#pragma once + +#include + +#include + +#include + +__BEGIN_DECLS + +/* + * Gets the managed heap array backing a java.nio.Buffer instance. + * + * Returns nullptr if there is no array backing. + * + * This method performs a JNI call to java.nio.NIOAccess.getBaseArray(). + */ +jarray jniGetNioBufferBaseArray(C_JNIEnv* env, jobject nioBuffer); + +/* + * Gets the offset of the current buffer position in bytes from the start of the managed heap + * array backing the buffer. + * + * Returns 0 if there is no array backing. + * + * This method performs a JNI call to java.nio.NIOAccess.getBaseArrayOffset(). + */ +jint jniGetNioBufferBaseArrayOffset(C_JNIEnv* env, jobject nioBuffer); + +/* + * Gets field information from a java.nio.Buffer instance. + * + * Reads the |position|, |limit|, and |elementSizeShift| fields from the buffer instance. + * + * Returns the |address| field of the java.nio.Buffer instance which is only valid (non-zero) when + * the buffer is backed by a direct buffer. + */ +jlong jniGetNioBufferFields(C_JNIEnv* env, + jobject nioBuffer, + /*out*/jint* position, + /*out*/jint* limit, + /*out*/jint* elementSizeShift); + +/* + * Gets the current position from a java.nio.Buffer as a pointer to memory in a fixed buffer. + * + * Returns 0 if |nioBuffer| is not backed by a direct buffer. + * + * This method reads the |address|, |position|, and |elementSizeShift| fields from the + * java.nio.Buffer instance to calculate the pointer address for the current position. + */ +jlong jniGetNioBufferPointer(C_JNIEnv* env, jobject nioBuffer); + +/* + * Clear the cache of constants libnativehelper is using. + */ +void jniUninitializeConstants(); + +__END_DECLS + +/* + * For C++ code, we provide inlines that map to the C functions. g++ always + * inlines these, even on non-optimized builds. + */ + +#if defined(__cplusplus) + +#include + +inline jobject jniCreateFileDescriptor(JNIEnv* env, int fd) { + jobject fileDescriptor = AFileDescriptor_create(env); + if (fileDescriptor != nullptr) { + AFileDescriptor_setFd(env, fileDescriptor, fd); + } + return fileDescriptor; +} + +inline int jniGetFDFromFileDescriptor(JNIEnv* env, jobject fileDescriptor) { + if (fileDescriptor == nullptr) { + return -1; + } + return AFileDescriptor_getFd(env, fileDescriptor); +} + +inline void jniSetFileDescriptorOfFD(JNIEnv* env, jobject fileDescriptor, int value) { + if (fileDescriptor == nullptr) { + jniThrowNullPointerException(env, "fileDescriptor is null"); + return; + } + AFileDescriptor_setFd(env, fileDescriptor, value); +} + +inline jarray jniGetNioBufferBaseArray(JNIEnv* env, jobject nioBuffer) { + return jniGetNioBufferBaseArray(&env->functions, nioBuffer); +} + +inline jint jniGetNioBufferBaseArrayOffset(JNIEnv* env, jobject nioBuffer) { + return jniGetNioBufferBaseArrayOffset(&env->functions, nioBuffer); +} + +inline jlong jniGetNioBufferFields(JNIEnv* env, jobject nioBuffer, + jint* position, jint* limit, jint* elementSizeShift) { + return jniGetNioBufferFields(&env->functions, nioBuffer, + position, limit, elementSizeShift); +} + +inline jlong jniGetNioBufferPointer(JNIEnv* env, jobject nioBuffer) { + return jniGetNioBufferPointer(&env->functions, nioBuffer); +} + +#endif // defined(__cplusplus) diff --git a/include_platform/nativehelper/JniInvocation.h b/include_platform/nativehelper/JniInvocation.h new file mode 100644 index 0000000..746eaed --- /dev/null +++ b/include_platform/nativehelper/JniInvocation.h @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 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 + +#include + +__BEGIN_DECLS + +/* + * The JNI invocation API exists to allow a choice of library responsible for managing virtual + * machines. + */ + +/* + * Opaque structure used to hold JNI invocation internal state. + */ +struct JniInvocationImpl; + +/* + * Creates an instance of a JniInvocationImpl. + */ +struct JniInvocationImpl* JniInvocationCreate(); + +/* + * Associates a library with a JniInvocationImpl instance. The library should export C symbols for + * JNI_GetDefaultJavaVMInitArgs, JNI_CreateJavaVM and JNI_GetDefaultJavaVMInitArgs. + * + * The specified |library| should be the filename of a shared library. The |library| is opened with + * dlopen(3). + * + * If there is an error opening the specified |library|, then function will fallback to the + * default library "libart.so". If the fallback library is successfully used then a warning is + * written to the Android log buffer. Use of the fallback library is not considered an error. + * + * If the fallback library cannot be opened or the expected symbols are not found in the library + * opened, then an error message is written to the Android log buffer and the function returns 0. + * + * Returns true on success, false otherwise. + */ +bool JniInvocationInit(struct JniInvocationImpl* instance, const char* library); + +/* + * Release resources associated with JniInvocationImpl instance. + */ +void JniInvocationDestroy(struct JniInvocationImpl* instance); + +/* + * Gets the default library for JNI invocation. The default library is "libart.so". This value may + * be overridden for debuggable builds using the persist.sys.dalvik.vm.lib.2 system property. + * + * The |library| argument is the preferred library to use on debuggable builds (when + * ro.debuggable=1). If the |library| argument is nullptr, then the system preferred value will be + * queried from persist.sys.dalvik.vm.lib.2 if the caller has provided |buffer| argument. + * + * The |buffer| argument is used for reading system properties in debuggable builds. It is + * optional, but should be provisioned to be PROP_VALUE_MAX bytes if provided to ensure it is + * large enough to hold a system property. + * + * Returns the filename of the invocation library determined from the inputs and system + * properties. The returned value may be |library|, |buffer|, or a pointer to a string constant + * "libart.so". + */ +const char* JniInvocationGetLibrary(const char* library, char* buffer); + +__END_DECLS + +#ifdef __cplusplus + +// JniInvocation adds a layer of indirection for applications using +// the JNI invocation API to allow the JNI implementation to be +// selected dynamically. Apps can specify a specific implementation to +// be used by calling InitJniInvocation. If this is not done, the +// library will chosen based on the value of Android system property +// persist.sys.dalvik.vm.lib on the device, and otherwise fall back to +// a hard-coded default implementation. +class JniInvocation final { + public: + JniInvocation() { + impl_ = JniInvocationCreate(); + } + + ~JniInvocation() { + JniInvocationDestroy(impl_); + } + + // Initialize JNI invocation API. library should specify a valid + // shared library for opening via dlopen providing a JNI invocation + // implementation, or null to allow defaulting via + // persist.sys.dalvik.vm.lib. + bool Init(const char* library) { + return JniInvocationInit(impl_, library) != 0; + } + + // Exposes which library is actually loaded from the given name. The + // buffer of size PROPERTY_VALUE_MAX will be used to load the system + // property for the default library, if necessary. If no buffer is + // provided, the fallback value will be used. + static const char* GetLibrary(const char* library, char* buffer) { + return JniInvocationGetLibrary(library, buffer); + } + + private: + JniInvocation(const JniInvocation&) = delete; + JniInvocation& operator=(const JniInvocation&) = delete; + + JniInvocationImpl* impl_; +}; + +#endif // __cplusplus diff --git a/include_platform_header_only/nativehelper/detail/signature_checker.h b/include_platform_header_only/nativehelper/detail/signature_checker.h new file mode 100644 index 0000000..06ebfa4 --- /dev/null +++ b/include_platform_header_only/nativehelper/detail/signature_checker.h @@ -0,0 +1,1441 @@ +/* + * Copyright (C) 2018 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. + */ + + +/* + * WARNING: Do not include and use these directly. Use jni_macros.h instead! + * The "detail" namespace should be a strong hint not to depend on the internals, + * which could change at any time. + * + * This implements the underlying mechanism for compile-time JNI signature/ctype checking + * and inference. + * + * This file provides the constexpr basic blocks such as strings, arrays, vectors + * as well as the JNI-specific parsing functionality. + * + * Everything is implemented via generic-style (templates without metaprogramming) + * wherever possible. Traditional template metaprogramming is used sparingly. + * + * Everything in this file except ostream<< is constexpr. + */ + +#pragma once + +#include // std::ostream +#include // jni typedefs, JniNativeMethod. +#include // std::common_type, std::remove_cv + +namespace nativehelper { +namespace detail { + +// If CHECK evaluates to false then X_ASSERT will halt compilation. +// +// Asserts meant to be used only within constexpr context. +#if defined(JNI_SIGNATURE_CHECKER_DISABLE_ASSERTS) +# define X_ASSERT(CHECK) do { if ((false)) { (CHECK) ? void(0) : void(0); } } while (false) +#else +# define X_ASSERT(CHECK) \ + ( (CHECK) ? void(0) : jni_assertion_failure(#CHECK) ) +#endif + +// The runtime 'jni_assert' will never get called from a constexpr context; +// instead compilation will abort with a stack trace. +// +// Inspect the frame above this one to see the exact nature of the failure. +inline void jni_assertion_failure(const char* /*msg*/) __attribute__((noreturn)); +inline void jni_assertion_failure(const char* /*msg*/) { + std::terminate(); +} + +// An immutable constexpr string view, similar to std::string_view but for C++14. +// For a mutable string see instead ConstexprVector. +// +// As it is a read-only view into a string, it is not guaranteed to be zero-terminated. +struct ConstexprStringView { + // Implicit conversion from string literal: + // ConstexprStringView str = "hello_world"; + template + constexpr ConstexprStringView(const char (& lit)[N]) // NOLINT: explicit. + : _array(lit), _size(N - 1) { + // Using an array of characters is not allowed because the inferred size would be wrong. + // Use the other constructor instead for that. + X_ASSERT(lit[N - 1] == '\0'); + } + + constexpr ConstexprStringView(const char* ptr, size_t size) + : _array(ptr), _size(size) { + // See the below constructor instead. + X_ASSERT(ptr != nullptr); + } + + // No-arg constructor: Create empty view. + constexpr ConstexprStringView() : _array(""), _size(0u) {} + + constexpr size_t size() const { + return _size; + } + + constexpr bool empty() const { + return size() == 0u; + } + + constexpr char operator[](size_t i) const { + X_ASSERT(i <= size()); + return _array[i]; + } + + // Create substring from this[start..start+len). + constexpr ConstexprStringView substr(size_t start, size_t len) const { + X_ASSERT(start <= size()); + X_ASSERT(len <= size() - start); + + return ConstexprStringView(&_array[start], len); + } + + // Create maximum length substring that begins at 'start'. + constexpr ConstexprStringView substr(size_t start) const { + X_ASSERT(start <= size()); + return substr(start, size() - start); + } + + using const_iterator = const char*; + + constexpr const_iterator begin() const { + return &_array[0]; + } + + constexpr const_iterator end() const { + return &_array[size()]; + } + + private: + const char* _array; // Never-null for simplicity. + size_t _size; +}; + +constexpr bool +operator==(const ConstexprStringView& lhs, const ConstexprStringView& rhs) { + if (lhs.size() != rhs.size()) { + return false; + } + for (size_t i = 0; i < lhs.size(); ++i) { + if (lhs[i] != rhs[i]) { + return false; + } + } + return true; +} + +constexpr bool +operator!=(const ConstexprStringView& lhs, const ConstexprStringView& rhs) { + return !(lhs == rhs); +} + +inline std::ostream& operator<<(std::ostream& os, const ConstexprStringView& str) { + for (char c : str) { + os << c; + } + return os; +} + +constexpr bool IsValidJniDescriptorStart(char shorty) { + constexpr char kValidJniStarts[] = + {'V', 'Z', 'B', 'C', 'S', 'I', 'J', 'F', 'D', 'L', '[', '(', ')'}; + + for (char c : kValidJniStarts) { + if (c == shorty) { + return true; + } + } + + return false; +} + +// A constexpr "vector" that supports storing a variable amount of Ts +// in an array-like interface. +// +// An up-front kMaxSize must be given since constexpr does not support +// dynamic allocations. +template +struct ConstexprVector { + public: + constexpr explicit ConstexprVector() : _size(0u), _array{} { + } + + private: + // Custom iterator to support ptr-one-past-end into the union array without + // undefined behavior. + template + struct VectorIterator { + Elem* ptr; + + constexpr VectorIterator& operator++() { + ++ptr; + return *this; + } + + constexpr VectorIterator operator++(int) const { + VectorIterator tmp(*this); + ++tmp; + return tmp; + } + + constexpr /*T&*/ auto& operator*() { + // Use 'auto' here since using 'T' is incorrect with const_iterator. + return ptr->_value; + } + + constexpr const /*T&*/ auto& operator*() const { + // Use 'auto' here for consistency with above. + return ptr->_value; + } + + constexpr bool operator==(const VectorIterator& other) const { + return ptr == other.ptr; + } + + constexpr bool operator!=(const VectorIterator& other) const { + return !(*this == other); + } + }; + + // Do not require that T is default-constructible by using a union. + struct MaybeElement { + union { + T _value; + }; + }; + + public: + using iterator = VectorIterator; + using const_iterator = VectorIterator; + + constexpr iterator begin() { + return {&_array[0]}; + } + + constexpr iterator end() { + return {&_array[size()]}; + } + + constexpr const_iterator begin() const { + return {&_array[0]}; + } + + constexpr const_iterator end() const { + return {&_array[size()]}; + } + + constexpr void push_back(const T& value) { + X_ASSERT(_size + 1 <= kMaxSize); + + _array[_size]._value = value; + _size++; + } + + // A pop operation could also be added since constexpr T's + // have default destructors, it would just be _size--. + // We do not need a pop() here though. + + constexpr const T& operator[](size_t i) const { + return _array[i]._value; + } + + constexpr T& operator[](size_t i) { + return _array[i]._value; + } + + constexpr size_t size() const { + return _size; + } + private: + + size_t _size; + MaybeElement _array[kMaxSize]; +}; + +// Parsed and validated "long" form of a single JNI descriptor. +// e.g. one of "J", "Ljava/lang/Object;" etc. +struct JniDescriptorNode { + ConstexprStringView longy; + + constexpr JniDescriptorNode(ConstexprStringView longy) : longy(longy) { // NOLINT(google-explicit-constructor) + X_ASSERT(!longy.empty()); + } + constexpr JniDescriptorNode() : longy() {} + + constexpr char shorty() { + // Must be initialized with the non-default constructor. + X_ASSERT(!longy.empty()); + return longy[0]; + } +}; + +inline std::ostream& operator<<(std::ostream& os, const JniDescriptorNode& node) { + os << node.longy; + return os; +} + +// Equivalent of C++17 std::optional. +// +// An optional is essentially a type safe +// union { +// void Nothing, +// T Some; +// }; +// +template +struct ConstexprOptional { + // Create a default optional with no value. + constexpr ConstexprOptional() : _has_value(false), _nothing() { + } + + // Create an optional with a value. + constexpr ConstexprOptional(const T& value) // NOLINT(google-explicit-constructor) + : _has_value(true), _value(value) { + } + + constexpr explicit operator bool() const { + return _has_value; + } + + constexpr bool has_value() const { + return _has_value; + } + + constexpr const T& value() const { + X_ASSERT(has_value()); + return _value; + } + + constexpr const T* operator->() const { + return &(value()); + } + + constexpr const T& operator*() const { + return value(); + } + + private: + bool _has_value; + // The "Nothing" is likely unnecessary but improves readability. + struct Nothing {}; + union { + Nothing _nothing; + T _value; + }; +}; + +template +constexpr bool +operator==(const ConstexprOptional& lhs, const ConstexprOptional& rhs) { + if (lhs && rhs) { + return lhs.value() == rhs.value(); + } + return lhs.has_value() == rhs.has_value(); +} + +template +constexpr bool +operator!=(const ConstexprOptional& lhs, const ConstexprOptional& rhs) { + return !(lhs == rhs); +} + +template +inline std::ostream& operator<<(std::ostream& os, const ConstexprOptional& val) { + if (val) { + os << val.value(); + } + return os; +} + +// Equivalent of std::nullopt +// Allows implicit conversion to any empty ConstexprOptional. +// Mostly useful for macros that need to return an empty constexpr optional. +struct NullConstexprOptional { + template + constexpr operator ConstexprOptional() const { // NOLINT(google-explicit-constructor) + return ConstexprOptional(); + } +}; + +inline std::ostream& operator<<(std::ostream& os, NullConstexprOptional) { + return os; +} + +#if !defined(PARSE_FAILURES_NONFATAL) +// Unfortunately we cannot have custom messages here, as it just prints a stack trace with the +// macros expanded. This is at least more flexible than static_assert which requires a string +// literal. +// NOTE: The message string literal must be on same line as the macro to be seen during a +// compilation error. +#define PARSE_FAILURE(msg) X_ASSERT(! #msg) +#define PARSE_ASSERT_MSG(cond, msg) X_ASSERT(#msg && (cond)) +#define PARSE_ASSERT(cond) X_ASSERT(cond) +#else +#define PARSE_FAILURE(msg) return NullConstexprOptional{}; +#define PARSE_ASSERT_MSG(cond, msg) if (!(cond)) { PARSE_FAILURE(msg); } +#define PARSE_ASSERT(cond) if (!(cond)) { PARSE_FAILURE(""); } +#endif + +// This is a placeholder function and should not be called directly. +constexpr void ParseFailure(const char* msg) { + (void) msg; // intentionally no-op. +} + +// Temporary parse data when parsing a function descriptor. +struct ParseTypeDescriptorResult { + // A single argument descriptor, e.g. "V" or "Ljava/lang/Object;" + ConstexprStringView token; + // The remainder of the function descriptor yet to be parsed. + ConstexprStringView remainder; + + constexpr bool has_token() const { + return token.size() > 0u; + } + + constexpr bool has_remainder() const { + return remainder.size() > 0u; + } + + constexpr JniDescriptorNode as_node() const { + X_ASSERT(has_token()); + return {token}; + } +}; + +// Parse a single type descriptor out of a function type descriptor substring, +// and return the token and the remainder string. +// +// If parsing fails (i.e. illegal syntax), then: +// parses are fatal -> assertion is triggered (default behavior), +// parses are nonfatal -> returns nullopt (test behavior). +constexpr ConstexprOptional +ParseSingleTypeDescriptor(ConstexprStringView single_type, + bool allow_void = false) { + constexpr NullConstexprOptional kUnreachable = {}; + + // Nothing else left. + if (single_type.size() == 0) { + return ParseTypeDescriptorResult{}; + } + + ConstexprStringView token; + ConstexprStringView remainder = single_type.substr(/*start*/1u); + + char c = single_type[0]; + PARSE_ASSERT(IsValidJniDescriptorStart(c)); + + enum State { + kSingleCharacter, + kArray, + kObject + }; + + State state = kSingleCharacter; + + // Parse the first character to figure out if we should parse the rest. + switch (c) { + case '!': { + constexpr bool fast_jni_is_deprecated = false; + PARSE_ASSERT(fast_jni_is_deprecated); + break; + } + case 'V': + if (!allow_void) { + constexpr bool void_type_descriptor_only_allowed_in_return_type = false; + PARSE_ASSERT(void_type_descriptor_only_allowed_in_return_type); + } + [[clang::fallthrough]]; + case 'Z': + case 'B': + case 'C': + case 'S': + case 'I': + case 'J': + case 'F': + case 'D': + token = single_type.substr(/*start*/0u, /*len*/1u); + break; + case 'L': + state = kObject; + break; + case '[': + state = kArray; + break; + default: { + // See JNI Chapter 3: Type Signatures. + PARSE_FAILURE("Expected a valid type descriptor character."); + return kUnreachable; + } + } + + // Possibly parse an arbitary-long remainder substring. + switch (state) { + case kSingleCharacter: + return {{token, remainder}}; + case kArray: { + // Recursively parse the array component, as it's just any non-void type descriptor. + ConstexprOptional + maybe_res = ParseSingleTypeDescriptor(remainder, /*allow_void*/false); + PARSE_ASSERT(maybe_res); // Downstream parsing has asserted, bail out. + + ParseTypeDescriptorResult res = maybe_res.value(); + + // Reject illegal array type descriptors such as "]". + PARSE_ASSERT_MSG(res.has_token(), "All array types must follow by their component type (e.g. ']I', ']]Z', etc. "); + + token = single_type.substr(/*start*/0u, res.token.size() + 1u); + + return {{token, res.remainder}}; + } + case kObject: { + // Parse the fully qualified class, e.g. Lfoo/bar/baz; + // Note checking that each part of the class name is a valid class identifier + // is too complicated (JLS 3.8). + // This simple check simply scans until the next ';'. + bool found_semicolon = false; + size_t semicolon_len = 0; + for (size_t i = 0; i < single_type.size(); ++i) { + switch (single_type[i]) { + case ')': + case '(': + case '[': + PARSE_FAILURE("Object identifiers cannot have ()[ in them."); + break; + } + if (single_type[i] == ';') { + semicolon_len = i + 1; + found_semicolon = true; + break; + } + } + + PARSE_ASSERT(found_semicolon); + + token = single_type.substr(/*start*/0u, semicolon_len); + remainder = single_type.substr(/*start*/semicolon_len); + + bool class_name_is_empty = token.size() <= 2u; // e.g. "L;" + PARSE_ASSERT(!class_name_is_empty); + + return {{token, remainder}}; + } + default: + X_ASSERT(false); + } + + X_ASSERT(false); + return kUnreachable; +} + +// Abstract data type to represent container for Ret(Args,...). +template +struct FunctionSignatureDescriptor { + ConstexprVector args; + T ret; + + static constexpr size_t max_size = kMaxSize; +}; + + +template +inline std::ostream& operator<<( + std::ostream& os, + const FunctionSignatureDescriptor& signature) { + size_t count = 0; + os << "args={"; + for (auto& arg : signature.args) { + os << arg; + + if (count != signature.args.size() - 1) { + os << ","; + } + + ++count; + } + os << "}, ret="; + os << signature.ret; + return os; +} + +// Ret(Args...) of JniDescriptorNode. +template +using JniSignatureDescriptor = FunctionSignatureDescriptor; + +// Parse a JNI function signature descriptor into a JniSignatureDescriptor. +// +// If parsing fails (i.e. illegal syntax), then: +// parses are fatal -> assertion is triggered (default behavior), +// parses are nonfatal -> returns nullopt (test behavior). +template +constexpr ConstexprOptional> +ParseSignatureAsList(ConstexprStringView signature) { + // The list of JNI descriptors cannot possibly exceed the number of characters + // in the JNI string literal. We leverage this to give an upper bound of the strlen. + // This is a bit wasteful but in constexpr there *must* be a fixed upper size for data structures. + ConstexprVector jni_desc_node_list; + JniDescriptorNode return_jni_desc; + + enum State { + kInitial = 0, + kParsingParameters = 1, + kParsingReturnType = 2, + kCompleted = 3, + }; + + State state = kInitial; + + while (!signature.empty()) { + switch (state) { + case kInitial: { + char c = signature[0]; + PARSE_ASSERT_MSG(c == '(', + "First character of a JNI signature must be a '('"); + state = kParsingParameters; + signature = signature.substr(/*start*/1u); + break; + } + case kParsingParameters: { + char c = signature[0]; + if (c == ')') { + state = kParsingReturnType; + signature = signature.substr(/*start*/1u); + break; + } + + ConstexprOptional + res = ParseSingleTypeDescriptor(signature, /*allow_void*/false); + PARSE_ASSERT(res); + + jni_desc_node_list.push_back(res->as_node()); + + signature = res->remainder; + break; + } + case kParsingReturnType: { + ConstexprOptional + res = ParseSingleTypeDescriptor(signature, /*allow_void*/true); + PARSE_ASSERT(res); + + return_jni_desc = res->as_node(); + signature = res->remainder; + state = kCompleted; + break; + } + default: { + // e.g. "()VI" is illegal because the V terminates the signature. + PARSE_FAILURE("Signature had left over tokens after parsing return type"); + break; + } + } + } + + switch (state) { + case kCompleted: + // Everything is ok. + break; + case kParsingParameters: + PARSE_FAILURE("Signature was missing ')'"); + break; + case kParsingReturnType: + PARSE_FAILURE("Missing return type"); + case kInitial: + PARSE_FAILURE("Cannot have an empty signature"); + default: + X_ASSERT(false); + } + + return {{jni_desc_node_list, return_jni_desc}}; +} + +// What kind of JNI does this type belong to? +enum NativeKind { + kNotJni, // Illegal parameter used inside of a function type. + kNormalJniCallingConventionParameter, + kNormalNative, + kFastNative, // Also valid in normal. + kCriticalNative, // Also valid in fast/normal. +}; + +// Is this type final, i.e. it cannot be subtyped? +enum TypeFinal { + kNotFinal, + kFinal // e.g. any primitive or any "final" class such as String. +}; + +// What position is the JNI type allowed to be in? +// Ignored when in a CriticalNative context. +enum NativePositionAllowed { + kNotAnyPosition, + kReturnPosition, + kZerothPosition, + kFirstOrLaterPosition, + kSecondOrLaterPosition, +}; + +constexpr NativePositionAllowed ConvertPositionToAllowed(size_t position) { + switch (position) { + case 0: + return kZerothPosition; + case 1: + return kFirstOrLaterPosition; + default: + return kSecondOrLaterPosition; + } +} + +// Type traits for a JNI parameter type. See below for specializations. +template +struct jni_type_trait { + static constexpr NativeKind native_kind = kNotJni; + static constexpr const char type_descriptor[] = "(illegal)"; + static constexpr NativePositionAllowed position_allowed = kNotAnyPosition; + static constexpr TypeFinal type_finality = kNotFinal; + static constexpr const char type_name[] = "(illegal)"; +}; + +// Access the jni_type_trait from a non-templated constexpr function. +// Identical non-static fields to jni_type_trait, see Reify(). +struct ReifiedJniTypeTrait { + NativeKind native_kind; + ConstexprStringView type_descriptor; + NativePositionAllowed position_allowed; + TypeFinal type_finality; + ConstexprStringView type_name; + + template + static constexpr ReifiedJniTypeTrait Reify() { + // This should perhaps be called 'Type Erasure' except we don't use virtuals, + // so it's not quite the same idiom. + using TR = jni_type_trait; + return {TR::native_kind, + TR::type_descriptor, + TR::position_allowed, + TR::type_finality, + TR::type_name}; + } + + // Find the most similar ReifiedJniTypeTrait corresponding to the type descriptor. + // + // Any type can be found by using the exact canonical type descriptor as listed + // in the jni type traits definitions. + // + // Non-final JNI types have limited support for inexact similarity: + // [[* | [L* -> jobjectArray + // L* -> jobject + // + // Otherwise return a nullopt. + static constexpr ConstexprOptional + MostSimilarTypeDescriptor(ConstexprStringView type_descriptor); +}; + +constexpr bool +operator==(const ReifiedJniTypeTrait& lhs, const ReifiedJniTypeTrait& rhs) { + return lhs.native_kind == rhs.native_kind + && rhs.type_descriptor == lhs.type_descriptor && + lhs.position_allowed == rhs.position_allowed + && rhs.type_finality == lhs.type_finality && + lhs.type_name == rhs.type_name; +} + +inline std::ostream& operator<<(std::ostream& os, const ReifiedJniTypeTrait& rjtt) { + // os << "ReifiedJniTypeTrait<" << rjft.type_name << ">"; + os << rjtt.type_name; + return os; +} + +// Template specialization for any JNI typedefs. +#define JNI_TYPE_TRAIT(jtype, the_type_descriptor, the_native_kind, the_type_finality, the_position) \ +template <> \ +struct jni_type_trait< jtype > { \ + static constexpr NativeKind native_kind = the_native_kind; \ + static constexpr const char type_descriptor[] = the_type_descriptor; \ + static constexpr NativePositionAllowed position_allowed = the_position; \ + static constexpr TypeFinal type_finality = the_type_finality; \ + static constexpr const char type_name[] = #jtype; \ +}; + +#define DEFINE_JNI_TYPE_TRAIT(TYPE_TRAIT_FN) \ +TYPE_TRAIT_FN(jboolean, "Z", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jbyte, "B", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jchar, "C", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jshort, "S", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jint, "I", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jlong, "J", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jfloat, "F", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jdouble, "D", kCriticalNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jobject, "Ljava/lang/Object;", kFastNative, kNotFinal, kFirstOrLaterPosition) \ +TYPE_TRAIT_FN(jclass, "Ljava/lang/Class;", kFastNative, kFinal, kFirstOrLaterPosition) \ +TYPE_TRAIT_FN(jstring, "Ljava/lang/String;", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jarray, "Ljava/lang/Object;", kFastNative, kNotFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jobjectArray, "[Ljava/lang/Object;", kFastNative, kNotFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jbooleanArray, "[Z", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jbyteArray, "[B", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jcharArray, "[C", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jshortArray, "[S", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jintArray, "[I", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jlongArray, "[J", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jfloatArray, "[F", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jdoubleArray, "[D", kFastNative, kFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(jthrowable, "Ljava/lang/Throwable;", kFastNative, kNotFinal, kSecondOrLaterPosition) \ +TYPE_TRAIT_FN(JNIEnv*, "", kNormalJniCallingConventionParameter, kFinal, kZerothPosition) \ +TYPE_TRAIT_FN(void, "V", kCriticalNative, kFinal, kReturnPosition) \ + +DEFINE_JNI_TYPE_TRAIT(JNI_TYPE_TRAIT) + +// See ReifiedJniTypeTrait for documentation. +constexpr ConstexprOptional +ReifiedJniTypeTrait::MostSimilarTypeDescriptor(ConstexprStringView type_descriptor) { +#define MATCH_EXACT_TYPE_DESCRIPTOR_FN(type, type_desc, native_kind, ...) \ + if (type_descriptor == type_desc && native_kind >= kNormalNative) { \ + return { Reify() }; \ + } + + // Attempt to look up by the precise type match first. + DEFINE_JNI_TYPE_TRAIT(MATCH_EXACT_TYPE_DESCRIPTOR_FN); + + // Otherwise, we need to do an imprecise match: + char shorty = type_descriptor.size() >= 1 ? type_descriptor[0] : '\0'; + if (shorty == 'L') { + // Something more specific like Ljava/lang/Throwable, string, etc + // is already matched by the macro-expanded conditions above. + return {Reify()}; + } else if (type_descriptor.size() >= 2) { + auto shorty_shorty = type_descriptor.substr(/*start*/0, /*size*/2u); + if (shorty_shorty == "[[" || shorty_shorty == "[L") { + // JNI arrays are covariant, so any type T[] (T!=primitive) is castable to Object[]. + return {Reify()}; + } + } + + // To handle completely invalid values. + return NullConstexprOptional{}; +} + +// Is this actual JNI position consistent with the expected position? +constexpr bool IsValidJniParameterPosition(NativeKind native_kind, + NativePositionAllowed position, + NativePositionAllowed expected_position) { + X_ASSERT(expected_position != kNotAnyPosition); + + if (native_kind == kCriticalNative) { + // CriticalNatives ignore positions since the first 2 special + // parameters are stripped. + return true; + } + + // Is this a return-only position? + if (expected_position == kReturnPosition) { + if (position != kReturnPosition) { + // void can only be in the return position. + return false; + } + // Don't do the other non-return position checks for a return-only position. + return true; + } + + // JNIEnv* can only be in the first spot. + if (position == kZerothPosition && expected_position != kZerothPosition) { + return false; + // jobject, jclass can be 1st or anywhere afterwards. + } else if (position == kFirstOrLaterPosition && expected_position != kFirstOrLaterPosition) { + return false; + // All other parameters must be in 2nd+ spot, or in the return type. + } else if (position == kSecondOrLaterPosition || position == kReturnPosition) { + if (expected_position != kFirstOrLaterPosition && expected_position != kSecondOrLaterPosition) { + return false; + } + } + + return true; +} + +// Check if a jni parameter type is valid given its position and native_kind. +template +constexpr bool IsValidJniParameter(NativeKind native_kind, NativePositionAllowed position) { + // const,volatile does not affect JNI compatibility since it does not change ABI. + using expected_trait = jni_type_trait::type>; + NativeKind expected_native_kind = expected_trait::native_kind; + + // Most types 'T' are not valid for JNI. + if (expected_native_kind == NativeKind::kNotJni) { + return false; + } + + // The rest of the types might be valid, but it depends on the context (native_kind) + // and also on their position within the parameters. + + // Position-check first. + NativePositionAllowed expected_position = expected_trait::position_allowed; + if (!IsValidJniParameterPosition(native_kind, position, expected_position)) { + return false; + } + + // Ensure the type appropriate is for the native kind. + if (expected_native_kind == kNormalJniCallingConventionParameter) { + // It's always wrong to use a JNIEnv* anywhere but the 0th spot. + if (native_kind == kCriticalNative) { + // CriticalNative does not allow using a JNIEnv*. + return false; + } + + return true; // OK: JniEnv* used in 0th position. + } else if (expected_native_kind == kCriticalNative) { + // CriticalNative arguments are always valid JNI types anywhere used. + return true; + } else if (native_kind == kCriticalNative) { + // The expected_native_kind was non-critical but we are in a critical context. + // Illegal type. + return false; + } + + // Everything else is fine, e.g. fast/normal native + fast/normal native parameters. + return true; +} + +// Is there sufficient number of parameters given the kind of JNI that it is? +constexpr bool IsJniParameterCountValid(NativeKind native_kind, size_t count) { + if (native_kind == kNormalNative || native_kind == kFastNative) { + return count >= 2u; + } else if (native_kind == kCriticalNative) { + return true; + } + + constexpr bool invalid_parameter = false; + X_ASSERT(invalid_parameter); + return false; +} + +// Basic template interface. See below for partial specializations. +// +// Each instantiation will have a 'value' field that determines whether or not +// all of the Args are valid JNI arguments given their native_kind. +template +struct is_valid_jni_argument_type { + // static constexpr bool value = ?; +}; + +template +struct is_valid_jni_argument_type { + static constexpr bool value = true; +}; + +template +struct is_valid_jni_argument_type { + static constexpr bool value = + IsValidJniParameter(native_kind, ConvertPositionToAllowed(position)); +}; + +template +struct is_valid_jni_argument_type { + static constexpr bool value = + IsValidJniParameter(native_kind, ConvertPositionToAllowed(position)) + && is_valid_jni_argument_type::value; +}; + +// This helper is required to decompose the function type into a list of arg types. +template +struct is_valid_jni_function_type_helper; + +template +struct is_valid_jni_function_type_helper { + static constexpr bool value = + IsJniParameterCountValid(native_kind, sizeof...(Args)) + && IsValidJniParameter(native_kind, kReturnPosition) + && is_valid_jni_argument_type::value; +}; + +// Is this function type 'T' a valid C++ function type given the native_kind? +template +constexpr bool IsValidJniFunctionType() { + return is_valid_jni_function_type_helper::value; + // TODO: we could replace template metaprogramming with constexpr by + // using FunctionTypeMetafunction. +} + +// Many parts of std::array is not constexpr until C++17. +template +struct ConstexprArray { + // Intentionally public to conform to std::array. + // This means all constructors are implicit. + // *NOT* meant to be used directly, use the below functions instead. + // + // The reason std::array has it is to support direct-list-initialization, + // e.g. "ConstexprArray{T{...}, T{...}, T{...}, ...};" + // + // Note that otherwise this would need a very complicated variadic + // argument constructor to only support list of Ts. + T _array[N]; + + constexpr size_t size() const { + return N; + } + + using iterator = T*; + using const_iterator = const T*; + + constexpr iterator begin() { + return &_array[0]; + } + + constexpr iterator end() { + return &_array[N]; + } + + constexpr const_iterator begin() const { + return &_array[0]; + } + + constexpr const_iterator end() const { + return &_array[N]; + } + + constexpr T& operator[](size_t i) { + return _array[i]; + } + + constexpr const T& operator[](size_t i) const { + return _array[i]; + } +}; + +// Why do we need this? +// auto x = {1,2,3} creates an initializer_list, +// but they can't be returned because it contains pointers to temporaries. +// auto x[] = {1,2,3} doesn't even work because auto for arrays is not supported. +// +// an alternative would be to pull up std::common_t directly into the call site +// std::common_type_t array[] = {1,2,3} +// but that's even more cludgier. +// +// As the other "stdlib-wannabe" functions, it's weaker than the library +// fundamentals std::make_array but good enough for our use. +template +constexpr auto MakeArray(Args&& ... args) { + return ConstexprArray::type, + sizeof...(Args)>{args...}; +} + +// See below. +template +struct FunctionTypeMetafunction { +}; + +// Enables the "map" operation over the function component types. +template +struct FunctionTypeMetafunction { + // Count how many arguments there are, and add 1 for the return type. + static constexpr size_t + count = sizeof...(Args) + 1u; // args and return type. + + // Return an array where the metafunction 'Func' has been applied + // to every argument type. The metafunction must be returning a common type. + template class Func> + static constexpr auto map_args() { + return map_args_impl(holder < Args > {}...); + } + + // Apply the metafunction 'Func' over the return type. + template class Func> + static constexpr auto map_return() { + return Func{}(); + } + + private: + template + struct holder { + }; + + template class Func, typename Arg0, typename... ArgsRest> + static constexpr auto map_args_impl(holder, holder...) { + // One does not simply call MakeArray with 0 template arguments... + auto array = MakeArray( + Func{}()... + ); + + return array; + } + + template class Func> + static constexpr auto map_args_impl() { + // This overload provides support for MakeArray() with 0 arguments. + using ComponentType = decltype(Func{}()); + + return ConstexprArray{}; + } +}; + +// Apply ReifiedJniTypeTrait::Reify for every function component type. +template +struct ReifyJniTypeMetafunction { + constexpr ReifiedJniTypeTrait operator()() const { + auto res = ReifiedJniTypeTrait::Reify(); + X_ASSERT(res.native_kind != kNotJni); + return res; + } +}; + +// Ret(Args...) where every component is a ReifiedJniTypeTrait. +template +using ReifiedJniSignature = FunctionSignatureDescriptor; + +// Attempts to convert the function type T into a list of ReifiedJniTypeTraits +// that correspond to the function components. +// +// If conversion fails (i.e. non-jni compatible types), then: +// parses are fatal -> assertion is triggered (default behavior), +// parses are nonfatal -> returns nullopt (test behavior). +template ::count> +constexpr ConstexprOptional> +MaybeMakeReifiedJniSignature() { + if (!IsValidJniFunctionType()) { + PARSE_FAILURE("The function signature has one or more types incompatible with JNI."); + } + + ReifiedJniTypeTrait return_jni_trait = + FunctionTypeMetafunction::template map_return(); + + constexpr size_t + kSkipArgumentPrefix = (native_kind != kCriticalNative) ? 2u : 0u; + ConstexprVector args; + auto args_list = + FunctionTypeMetafunction::template map_args(); + size_t args_index = 0; + for (auto& arg : args_list) { + // Ignore the 'JNIEnv*, jobject' / 'JNIEnv*, jclass' prefix, + // as its not part of the function descriptor string. + if (args_index >= kSkipArgumentPrefix) { + args.push_back(arg); + } + + ++args_index; + } + + return {{args, return_jni_trait}}; +} + +#define COMPARE_DESCRIPTOR_CHECK(expr) if (!(expr)) return false +#define COMPARE_DESCRIPTOR_FAILURE_MSG(msg) if ((true)) return false + +// Compares a user-defined JNI descriptor (of a single argument or return value) +// to a reified jni type trait that was derived from the C++ function type. +// +// If comparison fails (i.e. non-jni compatible types), then: +// parses are fatal -> assertion is triggered (default behavior), +// parses are nonfatal -> returns false (test behavior). +constexpr bool +CompareJniDescriptorNodeErased(JniDescriptorNode user_defined_descriptor, + ReifiedJniTypeTrait derived) { + + ConstexprOptional user_reified_opt = + ReifiedJniTypeTrait::MostSimilarTypeDescriptor(user_defined_descriptor.longy); + + if (!user_reified_opt.has_value()) { + COMPARE_DESCRIPTOR_FAILURE_MSG( + "Could not find any JNI C++ type corresponding to the type descriptor"); + } + + char user_shorty = user_defined_descriptor.longy.size() > 0 ? + user_defined_descriptor.longy[0] : + '\0'; + + ReifiedJniTypeTrait user = user_reified_opt.value(); + if (user == derived) { + // If we had a similar match, immediately return success. + return true; + } else if (derived.type_name == "jthrowable") { + if (user_shorty == 'L') { + // Weakly allow any objects to correspond to a jthrowable. + // We do not know the managed type system so we have to be permissive here. + return true; + } else { + COMPARE_DESCRIPTOR_FAILURE_MSG( + "jthrowable must correspond to an object type descriptor"); + } + } else if (derived.type_name == "jarray") { + if (user_shorty == '[') { + // a jarray is the base type for all other array types. Allow. + return true; + } else { + // Ljava/lang/Object; is the root for all array types. + // Already handled above in 'if user == derived'. + COMPARE_DESCRIPTOR_FAILURE_MSG( + "jarray must correspond to array type descriptor"); + } + } + // Otherwise, the comparison has failed and the rest of this is only to + // pick the most appropriate error message. + // + // Note: A weaker form of comparison would allow matching 'Ljava/lang/String;' + // against 'jobject', etc. However the policy choice here is to enforce the strictest + // comparison that we can to utilize the type system to its fullest. + + if (derived.type_finality == kFinal || user.type_finality == kFinal) { + // Final types, e.g. "I", "Ljava/lang/String;" etc must match exactly + // the C++ jni descriptor string ('I' -> jint, 'Ljava/lang/String;' -> jstring). + COMPARE_DESCRIPTOR_FAILURE_MSG( + "The JNI descriptor string must be the exact type equivalent of the " + "C++ function signature."); + } else if (user_shorty == '[') { + COMPARE_DESCRIPTOR_FAILURE_MSG( + "The array JNI descriptor must correspond to j${type}Array or jarray"); + } else if (user_shorty == 'L') { + COMPARE_DESCRIPTOR_FAILURE_MSG( + "The object JNI descriptor must correspond to jobject."); + } else { + X_ASSERT(false); // We should never get here, but either way this means the types did not match + COMPARE_DESCRIPTOR_FAILURE_MSG( + "The JNI type descriptor string does not correspond to the C++ JNI type."); + } +} + +// Matches a user-defined JNI function descriptor against the C++ function type. +// +// If matches fails, then: +// parses are fatal -> assertion is triggered (default behavior), +// parses are nonfatal -> returns false (test behavior). +template +constexpr bool +MatchJniDescriptorWithFunctionType(ConstexprStringView user_function_descriptor) { + constexpr size_t kReifiedMaxSize = FunctionTypeMetafunction::count; + + ConstexprOptional> + reified_signature_opt = + MaybeMakeReifiedJniSignature(); + if (!reified_signature_opt) { + // Assertion handling done by MaybeMakeReifiedJniSignature. + return false; + } + + ConstexprOptional> user_jni_sig_desc_opt = + ParseSignatureAsList(user_function_descriptor); + + if (!user_jni_sig_desc_opt) { + // Assertion handling done by ParseSignatureAsList. + return false; + } + + ReifiedJniSignature + reified_signature = reified_signature_opt.value(); + JniSignatureDescriptor + user_jni_sig_desc = user_jni_sig_desc_opt.value(); + + if (reified_signature.args.size() != user_jni_sig_desc.args.size()) { + COMPARE_DESCRIPTOR_FAILURE_MSG( + "Number of parameters in JNI descriptor string" + "did not match number of parameters in C++ function type"); + } else if (!CompareJniDescriptorNodeErased(user_jni_sig_desc.ret, + reified_signature.ret)) { + // Assertion handling done by CompareJniDescriptorNodeErased. + return false; + } else { + for (size_t i = 0; i < user_jni_sig_desc.args.size(); ++i) { + if (!CompareJniDescriptorNodeErased(user_jni_sig_desc.args[i], + reified_signature.args[i])) { + // Assertion handling done by CompareJniDescriptorNodeErased. + return false; + } + } + } + + return true; +} + +// Supports inferring the JNI function descriptor string from the C++ +// function type when all type components are final. +template +struct InferJniDescriptor { + static constexpr size_t kMaxSize = FunctionTypeMetafunction::count; + + // Convert the C++ function type into a JniSignatureDescriptor which holds + // the canonical (according to jni_traits) descriptors for each component. + // The C++ type -> JNI mapping must be nonambiguous (see jni_macros.h for exact rules). + // + // If conversion fails (i.e. C++ signatures is illegal for JNI, or the types are ambiguous): + // if parsing is fatal -> assertion failure (default behavior) + // if parsing is nonfatal -> returns nullopt (test behavior). + static constexpr ConstexprOptional> FromFunctionType() { + constexpr size_t kReifiedMaxSize = kMaxSize; + ConstexprOptional> + reified_signature_opt = + MaybeMakeReifiedJniSignature(); + if (!reified_signature_opt) { + // Assertion handling done by MaybeMakeReifiedJniSignature. + return NullConstexprOptional{}; + } + + ReifiedJniSignature + reified_signature = reified_signature_opt.value(); + + JniSignatureDescriptor signature_descriptor; + + if (reified_signature.ret.type_finality != kFinal) { + // e.g. jint, jfloatArray, jstring, jclass are ok. jobject, jthrowable, jarray are not. + PARSE_FAILURE("Bad return type. Only unambigous (final) types can be used to infer a signature."); // NOLINT + } + signature_descriptor.ret = + JniDescriptorNode{reified_signature.ret.type_descriptor}; + + for (size_t i = 0; i < reified_signature.args.size(); ++i) { + const ReifiedJniTypeTrait& arg_trait = reified_signature.args[i]; + if (arg_trait.type_finality != kFinal) { + PARSE_FAILURE("Bad parameter type. Only unambigous (final) types can be used to infer a signature."); // NOLINT + } + signature_descriptor.args.push_back(JniDescriptorNode{ + arg_trait.type_descriptor}); + } + + return {signature_descriptor}; + } + + // Calculate the exact string size that the JNI descriptor will be + // at runtime. + // + // Without this we cannot allocate enough space within static storage + // to fit the compile-time evaluated string. + static constexpr size_t CalculateStringSize() { + ConstexprOptional> + signature_descriptor_opt = + FromFunctionType(); + if (!signature_descriptor_opt) { + // Assertion handling done by FromFunctionType. + return 0u; + } + + JniSignatureDescriptor signature_descriptor = + signature_descriptor_opt.value(); + + size_t acc_size = 1u; // All sigs start with '('. + + // Now add every parameter. + for (size_t j = 0; j < signature_descriptor.args.size(); ++j) { + const JniDescriptorNode& arg_descriptor = signature_descriptor.args[j]; + // for (const JniDescriptorNode& arg_descriptor : signature_descriptor.args) { + acc_size += arg_descriptor.longy.size(); + } + + acc_size += 1u; // Add space for ')'. + + // Add space for the return value. + acc_size += signature_descriptor.ret.longy.size(); + + return acc_size; + } + + static constexpr size_t kMaxStringSize = CalculateStringSize(); + using ConstexprStringDescriptorType = ConstexprArray; + + // Convert the JniSignatureDescriptor we get in FromFunctionType() + // into a flat constexpr char array. + // + // This is done by repeated string concatenation at compile-time. + static constexpr ConstexprStringDescriptorType GetString() { + ConstexprStringDescriptorType c_str{}; + + ConstexprOptional> + signature_descriptor_opt = + FromFunctionType(); + if (!signature_descriptor_opt.has_value()) { + // Assertion handling done by FromFunctionType. + c_str[0] = '\0'; + return c_str; + } + + JniSignatureDescriptor signature_descriptor = + signature_descriptor_opt.value(); + + size_t pos = 0u; + c_str[pos++] = '('; + + // Copy all parameter descriptors. + for (size_t j = 0; j < signature_descriptor.args.size(); ++j) { + const JniDescriptorNode& arg_descriptor = signature_descriptor.args[j]; + ConstexprStringView longy = arg_descriptor.longy; + for (size_t i = 0; i < longy.size(); ++i) { + c_str[pos++] = longy[i]; + } + } + + c_str[pos++] = ')'; + + // Copy return descriptor. + ConstexprStringView longy = signature_descriptor.ret.longy; + for (size_t i = 0; i < longy.size(); ++i) { + c_str[pos++] = longy[i]; + } + + X_ASSERT(pos == kMaxStringSize); + + c_str[pos] = '\0'; + + return c_str; + } + + // Turn a pure constexpr string into one that can be accessed at non-constexpr + // time. Note that the 'static constexpr' storage must be in the scope of a + // function (prior to C++17) to avoid linking errors. + static const char* GetStringAtRuntime() { + static constexpr ConstexprStringDescriptorType str = GetString(); + return &str[0]; + } +}; + +// Expression to return JNINativeMethod, performs checking on signature+fn. +#define MAKE_CHECKED_JNI_NATIVE_METHOD(native_kind, name_, signature_, fn) \ + ([]() { \ + using namespace nativehelper::detail; /* NOLINT(google-build-using-namespace) */ \ + static_assert( \ + MatchJniDescriptorWithFunctionType(signature_),\ + "JNI signature doesn't match C++ function type."); \ + /* Suppress implicit cast warnings by explicitly casting. */ \ + return JNINativeMethod { \ + const_cast(name_), \ + const_cast(signature_), \ + reinterpret_cast(&(fn))}; \ + })() + +// Expression to return JNINativeMethod, infers signature from fn. +#define MAKE_INFERRED_JNI_NATIVE_METHOD(native_kind, name_, fn) \ + ([]() { \ + using namespace nativehelper::detail; /* NOLINT(google-build-using-namespace) */ \ + /* Suppress implicit cast warnings by explicitly casting. */ \ + return JNINativeMethod { \ + const_cast(name_), \ + const_cast( \ + InferJniDescriptor::GetStringAtRuntime()), \ + reinterpret_cast(&(fn))}; \ + })() + +} // namespace detail +} // namespace nativehelper + diff --git a/include_platform_header_only/nativehelper/jni_macros.h b/include_platform_header_only/nativehelper/jni_macros.h new file mode 100644 index 0000000..79f23c8 --- /dev/null +++ b/include_platform_header_only/nativehelper/jni_macros.h @@ -0,0 +1,283 @@ +/* + * Copyright (C) 2017 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. + */ + +/** + * Compile-time, zero-cost checking of JNI signatures against their C++ function type. + * This can trigger compile-time assertions if any of the input is invalid: + * (a) The signature specified does not conform to the JNI function descriptor syntax. + * (b) The C++ function is itself an invalid JNI function (e.g. missing JNIEnv*, etc). + * (c) The descriptor does not match the C++ function (e.g. "()V" will not match jint(jint)). + * + * The fundamental macros are as following: + * MAKE_JNI_[FAST_|CRITICAL_]NATIVE_METHOD - Create a checked JNINativeMethod{name, sig, func}. + * MAKE_JNI_[FAST_|CRITICAL_]NATIVE_METHOD_AUTOSIG - Same as above, but infer the JNI signature. + * + * Usage examples: + * // path/to/package/KlassName.java + * class KlassName { + * native jobject normal(int x); + * @FastNative native jobject fast(int x); + * @CriticalNative native int critical(long ptr); + * } + * // path_to_package_KlassName.cpp + * jobject KlassName_normal(JNIEnv*,jobject,jint) {...} + * jobject KlassName_fast(JNIEnv*,jobject,jint) {...} + * jint KlassName_critical(jlong) {...} + * + * // Manually specify each signature: + * JNINativeMethod[] gMethods = { + * MAKE_JNI_NATIVE_METHOD("normal", "(I)Ljava/lang/Object;", KlassName_normal), + * MAKE_JNI_FAST_NATIVE_METHOD("fast", "(I)Ljava/lang/Object;", KlassName_fast), + * MAKE_JNI_CRITICAL_NATIVE_METHOD("critical", "(Z)I", KlassName_critical), + * }; + * + * // Automatically infer the signature: + * JNINativeMethod[] gMethodsAutomaticSignature = { + * MAKE_JNI_NATIVE_METHOD_AUTOSIG("normal", KlassName_normal), + * MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG("fast", KlassName_fast), + * MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG("critical", KlassName_critical), + * }; + * + * // and then call JNIEnv::RegisterNatives with gMethods as usual. + * + * For convenience the following macros are defined: + * [FAST_|CRITICAL_]NATIVE_METHOD - Return JNINativeMethod for class, func name, and signature. + * OVERLOADED_[FAST_|CRITICAL_]NATIVE_METHOD - Same as above but allows a separate func identifier. + * [FAST_|CRITICAL_]NATIVE_METHOD_AUTOSIG - Return JNINativeMethod, sig inferred from function. + * + * The FAST_ prefix corresponds to functions annotated with @FastNative, + * and the CRITICAL_ prefix corresponds to functions annotated with @CriticalNative. + * See dalvik.annotation.optimization.CriticalNative for more details. + * + * ======================================= + * Checking rules + * ======================================= + * + * --------------------------------------- + * JNI descriptor syntax for functions + * + * Refer to "Chapter 3: JNI Types and Data Structures" of the JNI specification + * under the subsection "Type Signatures" table entry "method type". + * + * JNI signatures not conforming to the above syntax are rejected. + * --------------------------------------- + * C++ function types + * + * A normal or @FastNative JNI function type must be of the form + * + * ReturnType (JNIEnv*, jclass|jobject, [ArgTypes...]) {} + * + * A @CriticalNative JNI function type: + * + * must be of the form... ReturnType ([ArgTypes...]){} + * and must not contain any Reference Types. + * + * Refer to "Chapter 3: JNI Types and Data Structures" of the JNI specification + * under the subsection "Primitive Types" and "Reference Types" for the list + * of valid argument/return types. + * + * C++ function types not conforming to the above requirements are rejected. + * --------------------------------------- + * Matching of C++ function type against JNI function descriptor. + * + * Assuming all of the above conditions are met for signature and C++ type validity, + * then matching between the signature and the type validity can occur: + * + * Given a signature (Args...)Ret and the + * C++ function type of the form "CRet fn(JNIEnv*, jclass|jobject, CArgs...)", + * or for @CriticalNative of the form "CRet fn(CArgs...)" + * + * The number of Args... and the number of CArgs... must be equal. + * + * If so, attempt to match every component from the signature and function type + * against each other: + * + * ReturnType: + * V <-> void + * ArgumentType + * + * ArgumentType: + * PrimitiveType + * ReferenceType [except for @CriticalNative] + * + * PrimitiveType: + * Z <-> jboolean + * B <-> jbyte + * C <-> jchar + * S <-> jshort + * I <-> jint + * J <-> jlong + * F <-> jfloat + * D <-> jdouble + * + * ReferenceType: + * Ljava/lang/String; <-> jstring + * Ljava/lang/Class; <-> jclass + * L*; <- jobject + * Ljava/lang/Throwable; -> jthrowable + * L*; <- jthrowable + * [ PrimitiveType <-> ${CPrimitiveType}Array + * [ ReferenceType <-> jobjectArray + * [* <- jarray + * + * Wherein <-> represents a strong match (if the left or right pattern occurs, + * then left must match right, otherwise matching fails). <- and -> represent + * weak matches (that is, other match rules can be still attempted). + * + * Sidenote: Whilst a jobject could also represent a jclass, jstring, etc, + * the stricter approach is taken: the most exact C++ type must be used. + */ + +#pragma once + +// The below basic macros do not perform automatic stringification, +// invoked e.g. as MAKE_JNI_NATIVE_METHOD("some_name", "()V", void_fn) + +// An expression that evaluates to JNINativeMethod { name, signature, function }, +// and applies the above compile-time checking for signature+function. +// The equivalent Java Language code must not be annotated with @FastNative/@CriticalNative. +#define MAKE_JNI_NATIVE_METHOD(name, signature, function) \ + _NATIVEHELPER_JNI_MAKE_METHOD(kNormalNative, name, signature, function) + +// An expression that evaluates to JNINativeMethod { name, signature, function }, +// and applies the above compile-time checking for signature+function. +// The equivalent Java Language code must be annotated with @FastNative. +#define MAKE_JNI_FAST_NATIVE_METHOD(name, signature, function) \ + _NATIVEHELPER_JNI_MAKE_METHOD(kFastNative, name, signature, function) + +// An expression that evaluates to JNINativeMethod { name, signature, function }, +// and applies the above compile-time checking for signature+function. +// The equivalent Java Language code must be annotated with @CriticalNative. +#define MAKE_JNI_CRITICAL_NATIVE_METHOD(name, signature, function) \ + _NATIVEHELPER_JNI_MAKE_METHOD(kCriticalNative, name, signature, function) + +// Automatically signature-inferencing macros are also available, +// which also checks the C++ function types for validity: + +// An expression that evalutes to JNINativeMethod { name, infersig(function), function) } +// by inferring the signature at compile-time. Only works when the C++ function type +// corresponds to one unambigous JNI parameter (e.g. 'jintArray' -> '[I' but 'jobject' -> ???). +// +// The equivalent Java Language code must not be annotated with @FastNative/@CriticalNative. +#define MAKE_JNI_NATIVE_METHOD_AUTOSIG(name, function) \ + _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kNormalNative, name, function) + +// An expression that evalutes to JNINativeMethod { name, infersig(function), function) } +// by inferring the signature at compile-time. Only works when the C++ function type +// corresponds to one unambigous JNI parameter (e.g. 'jintArray' -> '[I' but 'jobject' -> ???). +// +// The equivalent Java Language code must be annotated with @FastNative. +#define MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG(name, function) \ + _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kFastNative, name, function) + +// An expression that evalutes to JNINativeMethod { name, infersig(function), function) } +// by inferring the signature at compile-time. +// +// The equivalent Java Language code must be annotated with @CriticalNative. +#define MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG(name, function) \ + _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kCriticalNative, name, function) + +// Convenience macros when the functions follow the naming convention: +// .java file .cpp file +// JavaLanguageName <-> ${ClassName}_${JavaLanguageName} +// +// Stringification is done automatically, invoked as: +// NATIVE_[FAST_|CRITICAL]_METHOD(ClassName, JavaLanguageName, Signature) +// +// Intended to construct a JNINativeMethod. +// (Assumes the C name is the ClassName_JavaMethodName). +// +// The Java Language code must be annotated with one of (none,@FastNative,@CriticalNative) +// for the (none,FAST_,CRITICAL_) variants of these macros. + +#define NATIVE_METHOD(className, functionName, signature) \ + MAKE_JNI_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName) + +#define OVERLOADED_NATIVE_METHOD(className, functionName, signature, identifier) \ + MAKE_JNI_NATIVE_METHOD(#functionName, signature, className ## _ ## identifier) + +#define NATIVE_METHOD_AUTOSIG(className, functionName) \ + MAKE_JNI_NATIVE_METHOD_AUTOSIG(#functionName, className ## _ ## functionName) + +#define FAST_NATIVE_METHOD(className, functionName, signature) \ + MAKE_JNI_FAST_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName) + +#define OVERLOADED_FAST_NATIVE_METHOD(className, functionName, signature, identifier) \ + MAKE_JNI_FAST_NATIVE_METHOD(#functionName, signature, className ## _ ## identifier) + +#define FAST_NATIVE_METHOD_AUTOSIG(className, functionName) \ + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG(#functionName, className ## _ ## functionName) + +#define CRITICAL_NATIVE_METHOD(className, functionName, signature) \ + MAKE_JNI_CRITICAL_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName) + +#define OVERLOADED_CRITICAL_NATIVE_METHOD(className, functionName, signature, identifier) \ + MAKE_JNI_CRITICAL_NATIVE_METHOD(#functionName, signature, className ## _ ## identifier) + +#define CRITICAL_NATIVE_METHOD_AUTOSIG(className, functionName) \ + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG(#functionName, className ## _ ## functionName) + +//////////////////////////////////////////////////////// +// IMPLEMENTATION ONLY. +// DO NOT USE DIRECTLY. +//////////////////////////////////////////////////////// + +#if defined(__cplusplus) && __cplusplus >= 201402L +#include "nativehelper/detail/signature_checker.h" // for MAKE_CHECKED_JNI_NATIVE_METHOD +#endif + +// Expands to an expression whose type is JNINativeMethod. +// This is for older versions of C++ or C, so it has no compile-time checking. +#define _NATIVEHELPER_JNI_MAKE_METHOD_OLD(kind, name, sig, fn) \ + ( \ + (JNINativeMethod) { \ + (name), \ + (sig), \ + _NATIVEHELPER_JNI_MACRO_CAST(reinterpret_cast, void *)(fn) \ + } \ + ) + +// C++14 or better, use compile-time checking. +#if defined(__cplusplus) && __cplusplus >= 201402L +// Expands to a compound expression whose type is JNINativeMethod. +#define _NATIVEHELPER_JNI_MAKE_METHOD(kind, name, sig, fn) \ + MAKE_CHECKED_JNI_NATIVE_METHOD(kind, name, sig, fn) + +// Expands to a compound expression whose type is JNINativeMethod. +#define _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kind, name, function) \ + MAKE_INFERRED_JNI_NATIVE_METHOD(kind, name, function) + +#else +// Older versions of C++ or C code get the regular macro that's unchecked. +// Expands to a compound expression whose type is JNINativeMethod. +#define _NATIVEHELPER_JNI_MAKE_METHOD(kind, name, sig, fn) \ + _NATIVEHELPER_JNI_MAKE_METHOD_OLD(kind, name, sig, fn) + +// Need C++14 or newer to use the AUTOSIG macros. +#define _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kind, name, function) \ + static_assert(false, "Cannot infer JNI signatures prior to C++14 for function " #function); + +#endif // C++14 check + +// C-style cast for C, C++-style cast for C++ to avoid warnings/errors. +#if defined(__cplusplus) +#define _NATIVEHELPER_JNI_MACRO_CAST(which_cast, to) \ + which_cast +#else +#define _NATIVEHELPER_JNI_MACRO_CAST(which_cast, to) \ + (to) +#endif + diff --git a/libnativehelper.map.txt b/libnativehelper.map.txt new file mode 100644 index 0000000..46769dd --- /dev/null +++ b/libnativehelper.map.txt @@ -0,0 +1,30 @@ +LIBNATIVEHELPER_S { # introduced=S + global: + # NDK API for libnativehelper. + AFileDescriptor_create; + AFileDescriptor_getFd; + AFileDescriptor_setFd; + + # JNI Invocation methods available to platform and apps. + JNI_CreateJavaVM; + JNI_GetDefaultJavaVMInitArgs; + JNI_GetCreatedJavaVMs; + + local: + *; +}; + +LIBNATIVEHELPER_PLATFORM { # platform-only + global: + JniInvocationCreate; + JniInvocationDestroy; + JniInvocationInit; + JniInvocationGetLibrary; + + jniGetNioBufferBaseArray; + jniGetNioBufferBaseArrayOffset; + jniGetNioBufferPointer; + jniGetNioBufferFields; + + jniUninitializeConstants; +}; diff --git a/libnativehelper_lazy.c b/libnativehelper_lazy.c new file mode 100644 index 0000000..2a1c4df --- /dev/null +++ b/libnativehelper_lazy.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "libnativehelper_lazy.h" + +#include +#include + +#include +#include + +#include "jni.h" +#include "log/log.h" + +#include "android/file_descriptor_jni.h" +#include "nativehelper/JNIHelp.h" +#include "nativehelper/JNIPlatformHelp.h" +#include "nativehelper/JniInvocation.h" + +// This file provides a lazy interface to libnativehelper.so to address early boot dependencies. +// Specifically bootanimation now runs before the ART APEX is loaded and libnativehelper.so is +// in the ART APEX. bootanimation does not call any code in libnativehelper. + +// Method pointers to libnativehelper methods are held in array which simplifies checking +// all pointers are initialized. +enum MethodIndex { + // NDK file descriptor API in file_descriptor_jni.h. + k_AFileDescriptor_create, + k_AFileDescriptor_getFd, + k_AFileDescriptor_setFd, + + // JNI_Invocation API declared in jni.h. + k_JNI_CreateJavaVM, + k_JNI_GetCreatedJavaVMs, + k_JNI_GetDefaultJavaVMInitArgs, + + // Methods in JNIPlatformHelp.h. + k_jniGetNioBufferBaseArray, + k_jniGetNioBufferBaseArrayOffset, + k_jniGetNioBufferFields, + k_jniGetNioBufferPointer, + k_jniUninitializeConstants, + + // Methods in JniInvocation.h. + k_JniInvocationCreate, + k_JniInvocationDestroy, + k_JniInvocationGetLibrary, + k_JniInvocationInit, + + // Marker for count of methods + k_MethodCount +}; + +// Table of methods pointers in libnativehelper APIs. +static void* g_Methods[k_MethodCount]; + +// +// Libnativehelper lazy loading. +// + +static atomic_bool gPreventLibnativehelperLoading = false; // Allows tests to block loading. + +void PreventLibnativehelperLazyLoadingForTests() { + atomic_store_explicit(&gPreventLibnativehelperLoading, true, memory_order_release); +} + +static void* LoadLibnativehelper(int dlopen_flags) { + if (atomic_load_explicit(&gPreventLibnativehelperLoading, memory_order_acquire)) { + return NULL; + } + return dlopen("libnativehelper.so", dlopen_flags); +} + +static bool IsLibnativehelperLoaded() { + return LoadLibnativehelper(RTLD_NOLOAD) != NULL; +} + +// +// Initialization and symbol binding. +// + +static void BindSymbol(void* handle, const char* name, enum MethodIndex index) { + void* symbol = dlsym(handle, name); + LOG_ALWAYS_FATAL_IF(symbol == NULL, + "Failed to find symbol '%s' in libnativehelper.so: %s", name, dlerror()); + g_Methods[index] = symbol; +} + +static void InitializeOnce() { + void* handle = LoadLibnativehelper(RTLD_NOW); + LOG_ALWAYS_FATAL_IF(handle == NULL, "Failed to load libnativehelper.so: %s", dlerror()); + +#undef BIND_SYMBOL +#define BIND_SYMBOL(name) BindSymbol(handle, #name, k_ ## name); + + // NDK file descriptor API in file_descriptor_jni.h. + BIND_SYMBOL(AFileDescriptor_create); + BIND_SYMBOL(AFileDescriptor_getFd); + BIND_SYMBOL(AFileDescriptor_setFd); + + // JNI_Invocation API declared in jni.h. + BIND_SYMBOL(JNI_CreateJavaVM); + BIND_SYMBOL(JNI_GetCreatedJavaVMs); + BIND_SYMBOL(JNI_GetDefaultJavaVMInitArgs); + + // Methods in JNIPlatformHelp.h. + BIND_SYMBOL(jniGetNioBufferBaseArray); + BIND_SYMBOL(jniGetNioBufferBaseArrayOffset); + BIND_SYMBOL(jniGetNioBufferFields); + BIND_SYMBOL(jniGetNioBufferPointer); + BIND_SYMBOL(jniUninitializeConstants); + + // Methods in JniInvocation.h. + BIND_SYMBOL(JniInvocationCreate); + BIND_SYMBOL(JniInvocationDestroy); + BIND_SYMBOL(JniInvocationGetLibrary); + BIND_SYMBOL(JniInvocationInit); + +#undef BIND_SYMBOL + + // Check every symbol is bound. + for (int i = 0; i < k_MethodCount; ++i) { + LOG_ALWAYS_FATAL_IF(g_Methods[i] == NULL, + "Uninitialized method in libnativehelper_lazy at index: %d", i); + } +} + +static void EnsureInitialized() { + static pthread_once_t initialized = PTHREAD_ONCE_INIT; + pthread_once(&initialized, InitializeOnce); +} + +#define INVOKE_METHOD(name, method_type, args...) \ + do { \ + EnsureInitialized(); \ + void* method = g_Methods[k_ ## name]; \ + return ((method_type) method)(args); \ + } while (0) + +#define INVOKE_VOID_METHOD(name, method_type, args...) \ + do { \ + EnsureInitialized(); \ + void* method = g_Methods[k_ ## name]; \ + ((method_type) method)(args); \ + } while (0) + +// +// Forwarding for methods in file_descriptor_jni.h. +// + +jobject AFileDescriptor_create(JNIEnv* env) { + typedef jobject (*M)(JNIEnv*); + INVOKE_METHOD(AFileDescriptor_create, M, env); +} + +int AFileDescriptor_getFd(JNIEnv* env, jobject fileDescriptor) { + typedef int (*M)(JNIEnv*, jobject); + INVOKE_METHOD(AFileDescriptor_getFd, M, env, fileDescriptor); +} + +void AFileDescriptor_setFd(JNIEnv* env, jobject fileDescriptor, int fd) { + typedef void (*M)(JNIEnv*, jobject, int); + INVOKE_VOID_METHOD(AFileDescriptor_setFd, M, env, fileDescriptor, fd); +} + +// +// Forwarding for the JNI_Invocation API declarded in jni.h. +// + +// Some code may attempt to use this JNI_Invocation API to establish if there is a VM (b/174768641). +// Because INVOKE_METHOD produces a fatal error if used before libnativehelper.so, we need some +// additional logic for the JNI_Invocation API to allow JNI_GetCreatedJavaVMs to be called even +// if libnativehelper.so is not loaded. +// +// Consequently, we use an atomic variable if a VM is created through this API. But note +// this is not the only way a JavaVM may be created so checking this flag alone is not enough. +static atomic_bool gJavaVmCreatedLazily = false; + +static jint JNI_CreateJavaVMImpl(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { + typedef jint (*M)(JavaVM**, JNIEnv**, void*); + INVOKE_METHOD(JNI_CreateJavaVM, M, p_vm, p_env, vm_args); +} + +jint JNI_CreateJavaVM(JavaVM** p_vm, JNIEnv** p_env, void* vm_args) { + jint status = JNI_CreateJavaVMImpl(p_vm, p_env, vm_args); + if (status == JNI_OK) { + atomic_store_explicit(&gJavaVmCreatedLazily, true, memory_order_release); + } + return status; +} + +jint JNI_GetDefaultJavaVMInitArgs(void* vm_args) { + typedef jint (*M)(void*); + INVOKE_METHOD(JNI_GetDefaultJavaVMInitArgs, M, vm_args); +} + +jint JNI_GetCreatedJavaVMs(JavaVM** p_vm, jsize vm_max, jsize* p_vm_count) { + typedef jint (*M)(JavaVM**, jsize, jsize*); + // If no VMs have been created created lazily and libnativehelper.so has not been loaded + // by other means, then fill-in the VM count as zero and return JNI_OK. + if (!atomic_load_explicit(&gJavaVmCreatedLazily, memory_order_acquire) && + !IsLibnativehelperLoaded()) { + *p_vm_count = 0; + return JNI_OK; + } + INVOKE_METHOD(JNI_GetCreatedJavaVMs, M, p_vm, vm_max, p_vm_count); +} + +// +// Forwarding for methods in JNIPlatformHelp.h. +// + +jarray jniGetNioBufferBaseArray(JNIEnv* env, jobject nioBuffer) { + typedef jarray (*M)(JNIEnv*, jobject); + INVOKE_METHOD(jniGetNioBufferBaseArray, M, env, nioBuffer); +} + +int jniGetNioBufferBaseArrayOffset(JNIEnv* env, jobject nioBuffer) { + typedef int (*M)(JNIEnv*, jobject); + INVOKE_METHOD(jniGetNioBufferBaseArrayOffset, M, env, nioBuffer); +} + +jlong jniGetNioBufferFields(JNIEnv* env, jobject nioBuffer, + jint* position, jint* limit, jint* elementSizeShift) { + typedef jlong (*M)(JNIEnv*, jobject, jint*, jint*, jint*); + INVOKE_METHOD(jniGetNioBufferFields, M, env, nioBuffer, position, limit, + elementSizeShift); +} + +jlong jniGetNioBufferPointer(JNIEnv* env, jobject nioBuffer) { + typedef jlong (*M)(JNIEnv*, jobject); + INVOKE_METHOD(jniGetNioBufferPointer, M, env, nioBuffer); +} + +void jniUninitializeConstants() { + typedef void (*M)(); + INVOKE_VOID_METHOD(jniUninitializeConstants, M); +} + +// +// Forwarding for methods in JniInvocation.h. +// + +struct JniInvocationImpl* JniInvocationCreate() { + typedef struct JniInvocationImpl* (*M)(); + INVOKE_METHOD(JniInvocationCreate, M); +} + +void JniInvocationDestroy(struct JniInvocationImpl* instance) { + typedef void (*M)(struct JniInvocationImpl*); + INVOKE_METHOD(JniInvocationDestroy, M, instance); +} + +bool JniInvocationInit(struct JniInvocationImpl* instance, const char* library) { + typedef bool (*M)(struct JniInvocationImpl*, const char*); + INVOKE_METHOD(JniInvocationInit, M, instance, library); +} + +const char* JniInvocationGetLibrary(const char* library, char* buffer) { + typedef const char* (*M)(const char*, char*); + INVOKE_METHOD(JniInvocationGetLibrary, M, library, buffer); +} diff --git a/libnativehelper_lazy.h b/libnativehelper_lazy.h new file mode 100644 index 0000000..c730837 --- /dev/null +++ b/libnativehelper_lazy.h @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 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 + +__BEGIN_DECLS + +void PreventLibnativehelperLazyLoadingForTests(); + +__END_DECLS \ No newline at end of file diff --git a/tests/Android.bp b/tests/Android.bp new file mode 100644 index 0000000..99ec108 --- /dev/null +++ b/tests/Android.bp @@ -0,0 +1,103 @@ +// Build the unit tests. + +package { + // http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // the below license kinds from "libnativehelper_license": + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["libnativehelper_license"], +} + +cc_defaults { + name: "libnativehelper_test_defaults", + cflags: [ + // Base set of cflags used by all things ART. + "-fno-rtti", + "-ggdb3", + "-Wall", + "-Werror", + "-Wextra", + "-Wstrict-aliasing", + "-fstrict-aliasing", + "-Wunreachable-code", + "-Wredundant-decls", + "-Wshadow", + "-Wunused", + "-fvisibility=protected", + + // Warn about thread safety violations with clang. + "-Wthread-safety", + "-Wthread-safety-negative", + + // Warn if switch fallthroughs aren't annotated. + "-Wimplicit-fallthrough", + + // Enable float equality warnings. + "-Wfloat-equal", + + // Enable warning of converting ints to void*. + "-Wint-to-void-pointer-cast", + + // Enable warning for deprecated language features. + "-Wdeprecated", + + // Disable warning from external/libcxxabi/include/cxxabi.h + "-Wno-deprecated-dynamic-exception-spec", + + // Enable warning for unreachable break & return. + "-Wunreachable-code-break", + "-Wunreachable-code-return", + + // Enable thread annotations for std::mutex, etc. + "-D_LIBCPP_ENABLE_THREAD_SAFETY_ANNOTATIONS", + ], + host_supported: true, + test_options: { + unit_test: true, + }, + tidy: true, +} + +cc_test { + name: "libnativehelper_tests", + defaults: ["libnativehelper_test_defaults"], + test_suites: ["general-tests"], + srcs: [ + "scoped_local_frame_test.cpp", + "scoped_local_ref_test.cpp", + "scoped_primitive_array_test.cpp", + "libnativehelper_api_test.c", + "JniSafeRegisterNativeMethods_test.cpp", + ], + shared_libs: ["libnativehelper"], +} + +cc_test { + name: "libnativehelper_lazy_tests", + defaults: ["libnativehelper_test_defaults"], + test_suites: ["general-tests"], + srcs: ["libnativehelper_lazy_test.cpp"], + shared_libs: ["liblog"], + static_libs: ["libnativehelper_lazy"], +} + +// Tests for internal functions that aren't present in the APEX stub API. Use +// `bootstrap:true` to bypass the stub library. This test won't link when +// prebuilts are preferred, because we cannot link against the source variant +// then. +// TODO(b/180107266): Enable in TEST_MAPPING. Also use a better way than +// `bootstrap:true` - `test_for` ought to work but fails because the test is +// host enabled so host variants of the APEXes are expected. +cc_test { + name: "libnativehelper_internal_tests", + defaults: [ + "art_module_source_build_defaults", + "libnativehelper_test_defaults", + ], + srcs: [ + "ExpandableString_test.cpp", + "JniInvocation_test.cpp", + ], + bootstrap: true, + shared_libs: ["libnativehelper"], +} diff --git a/tests/ExpandableString_test.cpp b/tests/ExpandableString_test.cpp new file mode 100644 index 0000000..7683365 --- /dev/null +++ b/tests/ExpandableString_test.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2020 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. + */ + +#include +#include + +#include +#include + +#include "../ExpandableString.h" + + +TEST(ExpandableString, InitializeAppendRelease) { + const char* kAhoy = "Ahoy!"; + struct ExpandableString s; + ExpandableStringInitialize(&s); + EXPECT_TRUE(s.data == NULL); + EXPECT_EQ(s.dataSize, 0u); + EXPECT_TRUE(ExpandableStringAppend(&s, kAhoy)); + EXPECT_TRUE(s.data != NULL); + EXPECT_GE(s.dataSize, strlen(kAhoy)); + ExpandableStringRelease(&s); + EXPECT_TRUE(s.data == NULL); + EXPECT_GE(s.dataSize, 0u); +} + +TEST(ExpandableString, InitializeWriteRelease) { + const char* kAhoy = "Ahoy!"; + const char* kMercy = "Mercy, Mercy, Mercy!"; + + struct ExpandableString s; + ExpandableStringInitialize(&s); + EXPECT_TRUE(s.data == NULL); + EXPECT_EQ(s.dataSize, 0u); + EXPECT_TRUE(ExpandableStringAssign(&s, kAhoy)); + EXPECT_TRUE(s.data != NULL); + EXPECT_GE(s.dataSize, strlen(kAhoy)); + EXPECT_TRUE(ExpandableStringAssign(&s, kMercy)); + EXPECT_TRUE(s.data != NULL); + EXPECT_GE(s.dataSize, strlen(kMercy)); + EXPECT_TRUE(ExpandableStringAssign(&s, kAhoy)); + EXPECT_TRUE(s.data != NULL); + EXPECT_GE(s.dataSize, strlen(kAhoy)); + ExpandableStringRelease(&s); + EXPECT_TRUE(s.data == NULL); + EXPECT_GE(s.dataSize, 0u); +} + +class ExpandableStringTestFixture : public :: testing::TestWithParam { + protected: + struct ExpandableString expandableString; +}; + +TEST_P(ExpandableStringTestFixture, AppendTest) { + size_t step = GetParam(); + + std::array inputs = { + std::string(step, 'a'), + std::string(step, 'b'), + std::string(step, 'c'), + }; + + for (size_t offset = 0; offset < step; ++offset) { + ExpandableStringInitialize(&expandableString); + + std::string pad(step - 1u, '_'); + EXPECT_TRUE(ExpandableStringAppend(&expandableString, pad.c_str())); + + for (size_t i = 0; i < 4096u; ++i) { + const std::string& appendee = inputs[i % inputs.size()]; + EXPECT_TRUE(ExpandableStringAppend(&expandableString, appendee.c_str())); + size_t requiredSize = pad.size() + i * step + 1u; + EXPECT_GE(expandableString.dataSize, requiredSize); + } + + size_t position = 0u; + for (char c : pad) { + EXPECT_EQ(c, expandableString.data[position]); + position++; + } + for (size_t i = 0; i < 4096; ++i) { + const std::string& expected = inputs[i % inputs.size()]; + EXPECT_EQ(0, strncmp(expected.c_str(), expandableString.data + position, expected.size())); + position += expected.size(); + } + + ExpandableStringRelease(&expandableString); + } +} + +INSTANTIATE_TEST_CASE_P( + AppendTest, + ExpandableStringTestFixture, + ::testing::Values( + 1, 2, 3, 4, 5, 11, 17 + )); \ No newline at end of file diff --git a/tests/JniInvocation_test.cpp b/tests/JniInvocation_test.cpp new file mode 100644 index 0000000..783eddf --- /dev/null +++ b/tests/JniInvocation_test.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2014 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. + */ + +#include "../JniInvocation-priv.h" + +#include +#include + + +static const char* kDefaultJniInvocationLibrary = "libart.so"; +static const char* kTestNonNull = "libartd.so"; +static const char* kTestNonNull2 = "libartd2.so"; + +TEST(JNIInvocation, Debuggable) { + const char* result = JniInvocationGetLibraryWith(nullptr, true, kTestNonNull2); + EXPECT_STREQ(result, kTestNonNull2); + + result = JniInvocationGetLibraryWith(kTestNonNull, true, kTestNonNull2); + EXPECT_STREQ(result, kTestNonNull); + + result = JniInvocationGetLibraryWith(kTestNonNull, true, nullptr); + EXPECT_STREQ(result, kTestNonNull); + + result = JniInvocationGetLibraryWith(nullptr, true, nullptr); + EXPECT_STREQ(result, kDefaultJniInvocationLibrary); +} + +TEST(JNIInvocation, NonDebuggable) { + const char* result = JniInvocationGetLibraryWith(nullptr, false, kTestNonNull2); + EXPECT_STREQ(result, kDefaultJniInvocationLibrary); + + result = JniInvocationGetLibraryWith(kTestNonNull, false, kTestNonNull2); + EXPECT_STREQ(result, kDefaultJniInvocationLibrary); + + result = JniInvocationGetLibraryWith(kTestNonNull, false, nullptr); + EXPECT_STREQ(result, kDefaultJniInvocationLibrary); + + result = JniInvocationGetLibraryWith(nullptr, false, nullptr); + EXPECT_STREQ(result, kDefaultJniInvocationLibrary); +} + +TEST(JNIInvocation, GetDefaultJavaVMInitArgsBeforeInit) { + EXPECT_DEATH(JNI_GetDefaultJavaVMInitArgs(nullptr), "Runtime library not loaded."); +} + +TEST(JNIInvocation, CreateJavaVMBeforeInit) { + JavaVM *vm; + JNIEnv *env; + EXPECT_DEATH(JNI_CreateJavaVM(&vm, &env, nullptr), "Runtime library not loaded."); +} + +TEST(JNIInvocation, GetCreatedJavaVMsBeforeInit) { + jsize vm_count; + JavaVM *vm; + int status = JNI_GetCreatedJavaVMs(&vm, 1, &vm_count); + EXPECT_EQ(status, JNI_OK); + EXPECT_EQ(vm_count, 0); +} diff --git a/tests/JniSafeRegisterNativeMethods_test.cpp b/tests/JniSafeRegisterNativeMethods_test.cpp new file mode 100644 index 0000000..e7b0fc4 --- /dev/null +++ b/tests/JniSafeRegisterNativeMethods_test.cpp @@ -0,0 +1,1281 @@ +/* + * Copyright (C) 2018 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 clang diagnostic push +#pragma clang diagnostic ignored "-Wused-but-marked-unused" +#pragma clang diagnostic ignored "-Wdeprecated" +#include +#pragma clang diagnostic pop +#include + +#define PARSE_FAILURES_NONFATAL // return empty optionals wherever possible instead of asserting. +#include "nativehelper/jni_macros.h" + +// Provide static storage to these values so they can be used in a runtime context. +// This has to be defined local to the test translation unit to avoid ODR violations prior to C++17. +#define STORAGE_FN_FOR_JNI_TRAITS(jtype, ...) \ +constexpr char nativehelper::detail::jni_type_trait::type_descriptor[]; \ +constexpr char nativehelper::detail::jni_type_trait::type_name[]; + +DEFINE_JNI_TYPE_TRAIT(STORAGE_FN_FOR_JNI_TRAITS) + +template +std::string stringify_helper(const T& val) { + std::stringstream ss; + ss << val; + return ss.str(); +} + +#define EXPECT_STRINGIFY_EQ(x, y) EXPECT_EQ(stringify_helper(x), stringify_helper(y)) + +TEST(JniSafeRegisterNativeMethods, StringParsing) { + using namespace nativehelper::detail; // NOLINT + + // Super basic bring-up tests for core functionality. + + { + constexpr ConstexprStringView v_str = "V"; + EXPECT_EQ(1u, v_str.size()); + EXPECT_EQ(false, v_str.empty()); + + std::stringstream ss; + ss << v_str; + EXPECT_EQ("V", ss.str()); + } + + { + auto parse = ParseSingleTypeDescriptor("", /*allow_void*/true); + EXPECT_EQ("", parse->token); + EXPECT_EQ("", parse->remainder); + } + + { + auto parse = ParseSingleTypeDescriptor("V", /*allow_void*/true); + EXPECT_EQ("V", parse->token); + EXPECT_EQ("", parse->remainder); + } + + { + auto parse = ParseSingleTypeDescriptor("[I"); + EXPECT_EQ("[I", parse->token); + EXPECT_EQ("", parse->remainder); + } + + { + auto parse = ParseSingleTypeDescriptor("LObject;"); + EXPECT_EQ("LObject;", parse->token); + EXPECT_EQ("", parse->remainder); + } + + { + auto parse = ParseSingleTypeDescriptor("LBadObject);"); + EXPECT_FALSE(parse.has_value()); + } + + { + auto parse = ParseSingleTypeDescriptor("LBadObject(;"); + EXPECT_FALSE(parse.has_value()); + } + + { + auto parse = ParseSingleTypeDescriptor("LBadObject[;"); + EXPECT_FALSE(parse.has_value()); + } + + // Stringify is used for convenience to make writing out tests easier. + // Transforms as "(XYZ)W" -> "args={X,Y,Z}, ret=W" + +#define PARSE_SIGNATURE_AS_LIST(str) (ParseSignatureAsList(str)) + + { + constexpr auto jni_descriptor = PARSE_SIGNATURE_AS_LIST("()V"); + EXPECT_STRINGIFY_EQ("args={}, ret=V", jni_descriptor); + } + + { + constexpr auto + jni_descriptor = PARSE_SIGNATURE_AS_LIST("()Ljava/lang/Object;"); + EXPECT_STRINGIFY_EQ("args={}, ret=Ljava/lang/Object;", jni_descriptor); + } + + { + constexpr auto jni_descriptor = PARSE_SIGNATURE_AS_LIST("()[I"); + EXPECT_STRINGIFY_EQ("args={}, ret=[I", jni_descriptor); + } + +#define EXPECT_OK_SIGNATURE_PARSE(signature, args, ret) \ + do { \ + constexpr auto jni_descriptor = PARSE_SIGNATURE_AS_LIST(signature); \ + EXPECT_EQ(true, jni_descriptor.has_value()); \ + EXPECT_STRINGIFY_EQ("args={" args "}, ret=" ret, jni_descriptor); \ + } while (0) + + // Exhaustive tests for successful parsing. + + EXPECT_OK_SIGNATURE_PARSE("()V", /*args*/"", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("()Z", /*args*/"", /*ret*/"Z"); + EXPECT_OK_SIGNATURE_PARSE("()B", /*args*/"", /*ret*/"B"); + EXPECT_OK_SIGNATURE_PARSE("()C", /*args*/"", /*ret*/"C"); + EXPECT_OK_SIGNATURE_PARSE("()S", /*args*/"", /*ret*/"S"); + EXPECT_OK_SIGNATURE_PARSE("()I", /*args*/"", /*ret*/"I"); + EXPECT_OK_SIGNATURE_PARSE("()F", /*args*/"", /*ret*/"F"); + EXPECT_OK_SIGNATURE_PARSE("()J", /*args*/"", /*ret*/"J"); + EXPECT_OK_SIGNATURE_PARSE("()D", /*args*/"", /*ret*/"D"); + EXPECT_OK_SIGNATURE_PARSE("()Ljava/lang/Object;", /*args*/"", /*ret*/"Ljava/lang/Object;"); + EXPECT_OK_SIGNATURE_PARSE("()[Ljava/lang/Object;", /*args*/"", /*ret*/"[Ljava/lang/Object;"); + EXPECT_OK_SIGNATURE_PARSE("()[I", /*args*/"", /*ret*/"[I"); + EXPECT_OK_SIGNATURE_PARSE("()[[I", /*args*/"", /*ret*/"[[I"); + EXPECT_OK_SIGNATURE_PARSE("()[[[I", /*args*/"", /*ret*/"[[[I"); + + + EXPECT_OK_SIGNATURE_PARSE("(Z)V", /*args*/"Z", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(B)V", /*args*/"B", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(C)D", /*args*/"C", /*ret*/"D"); + EXPECT_OK_SIGNATURE_PARSE("(S)V", /*args*/"S", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(I)V", /*args*/"I", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(F)V", /*args*/"F", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(J)F", /*args*/"J", /*ret*/"F"); + EXPECT_OK_SIGNATURE_PARSE("(D)V", /*args*/"D", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(Ljava/lang/Object;)V", "Ljava/lang/Object;", "V"); + EXPECT_OK_SIGNATURE_PARSE("([Ljava/lang/Object;)V", + "[Ljava/lang/Object;", + "V"); + EXPECT_OK_SIGNATURE_PARSE("([I)V", /*ret*/"[I", "V"); + EXPECT_OK_SIGNATURE_PARSE("([[I)V", /*ret*/"[[I", "V"); + EXPECT_OK_SIGNATURE_PARSE("([[[I)V", /*ret*/"[[[I", "V"); + + EXPECT_OK_SIGNATURE_PARSE("(ZIJ)V", /*args*/"Z,I,J", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(B[IJ)V", /*args*/"B,[I,J", /*ret*/"V"); + EXPECT_OK_SIGNATURE_PARSE("(Ljava/lang/Object;B)D", + /*args*/"Ljava/lang/Object;,B", + /*ret*/"D"); + EXPECT_OK_SIGNATURE_PARSE("(Ljava/lang/Object;Ljava/lang/String;IF)D", + /*args*/"Ljava/lang/Object;,Ljava/lang/String;,I,F", + /*ret*/"D"); + EXPECT_OK_SIGNATURE_PARSE("([[[Ljava/lang/Object;Ljava/lang/String;IF)D", + /*args*/"[[[Ljava/lang/Object;,Ljava/lang/String;,I,F", + /*ret*/"D"); + + /* + * Test Failures in Parsing + */ + +#define EXPECT_FAILED_SIGNATURE_PARSE(jni_descriptor) \ + EXPECT_STRINGIFY_EQ(ConstexprOptional>{},\ + ParseSignatureAsList(jni_descriptor)) + + // For the failures to work we must turn off 'PARSE_FAILURES_FATAL'. + // Otherwise they immediately cause a crash, which is actually the desired behavior + // when this is used by the end-user in REGISTER_NATIVE_METHOD. + { + EXPECT_FAILED_SIGNATURE_PARSE(""); + EXPECT_FAILED_SIGNATURE_PARSE("A"); + EXPECT_FAILED_SIGNATURE_PARSE(")"); + EXPECT_FAILED_SIGNATURE_PARSE("V"); + EXPECT_FAILED_SIGNATURE_PARSE("("); + EXPECT_FAILED_SIGNATURE_PARSE("(A"); + EXPECT_FAILED_SIGNATURE_PARSE("()"); + EXPECT_FAILED_SIGNATURE_PARSE("()A"); + EXPECT_FAILED_SIGNATURE_PARSE("()VV"); + EXPECT_FAILED_SIGNATURE_PARSE("()L"); + EXPECT_FAILED_SIGNATURE_PARSE("()L;"); + EXPECT_FAILED_SIGNATURE_PARSE("()BAD;"); + EXPECT_FAILED_SIGNATURE_PARSE("()Ljava/lang/Object"); + EXPECT_FAILED_SIGNATURE_PARSE("()Ljava/lang/Object;X"); + + EXPECT_FAILED_SIGNATURE_PARSE("(V)V"); + EXPECT_FAILED_SIGNATURE_PARSE("(ILcat)V"); + EXPECT_FAILED_SIGNATURE_PARSE("([dog)V"); + EXPECT_FAILED_SIGNATURE_PARSE("(IV)V"); + EXPECT_FAILED_SIGNATURE_PARSE("([V)V"); + EXPECT_FAILED_SIGNATURE_PARSE("([[V)V"); + EXPECT_FAILED_SIGNATURE_PARSE("()v"); + EXPECT_FAILED_SIGNATURE_PARSE("()i"); + EXPECT_FAILED_SIGNATURE_PARSE("()f"); + } + +} + +#define EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(expected, expr) \ + { constexpr bool is_valid = (expr); \ + EXPECT_EQ(expected, is_valid) << #expr; \ + } + +// Basic smoke tests for parameter validity. +// See below for more exhaustive tests. +TEST(JniSafeRegisterNativeMethods, ParameterTypes) { + using namespace nativehelper::detail; // NOLINT + EXPECT_TRUE(IsJniParameterCountValid(kCriticalNative, 0u)); + EXPECT_TRUE(IsJniParameterCountValid(kCriticalNative, 1u)); + EXPECT_TRUE(IsJniParameterCountValid(kCriticalNative, 2u)); + EXPECT_TRUE(IsJniParameterCountValid(kCriticalNative, 3u)); + EXPECT_TRUE(IsJniParameterCountValid(kCriticalNative, 4u)); + + EXPECT_FALSE(IsJniParameterCountValid(kNormalNative, 0u)); + EXPECT_FALSE(IsJniParameterCountValid(kNormalNative, 1u)); + EXPECT_TRUE(IsJniParameterCountValid(kNormalNative, 2u)); + EXPECT_TRUE(IsJniParameterCountValid(kNormalNative, 3u)); + EXPECT_TRUE(IsJniParameterCountValid(kNormalNative, 4u)); + + EXPECT_TRUE((IsValidJniParameter(kNormalNative, kReturnPosition))); + EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(true,(is_valid_jni_argument_type::value)); + EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(true,(is_valid_jni_argument_type::value)); + EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(true,(is_valid_jni_argument_type::value)); + EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(false,(is_valid_jni_argument_type::value)); +} + +struct TestReturnAnything { + template + operator T() const { // NOLINT + return T{}; + } +}; + +namespace test_jni { + void empty_fn() {} +} +struct TestJni { + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" + + // Always bad. + static void bad_cptr(const char* ptr) {} + static void* bad_ret_ptr() { return nullptr; } + static JNIEnv* bad_ret_env() { return nullptr; } + static void bad_wrongplace_env(jobject, JNIEnv*) {} + static void bad_wrongplace_env2(jobject, jobject, JNIEnv*) {} + static void v_e(JNIEnv*) {} + static void v_ei(JNIEnv*, jint l) {} + static void v_el(JNIEnv*, jlong l) {} + static void v_et(JNIEnv*, jstring) {} + static jobject o_none() { return nullptr; } + static void bad_noref_jint_norm(JNIEnv*, jclass, jint&) {} + static void bad_noref_jint_crit(jint&) {} + + // Good depending on the context: + + // CriticalNative + static void empty_fn() {} + static jint int_fn() { return 0; } + + static void v_() {} + // Note: volatile,const don't participate in the function signature + // but we still have these here to clarify that it is indeed allowed. + static void v_vol_i(volatile jint) {} + static void v_const_i(const jint) {} + static void v_i(jint) {} + static void v_l(jlong) {} + static void v_lib(jlong, jint, jboolean) {} + static jshort s_lib(jlong, jint, jboolean) { return 0; } + + // Normal or FastNative. + static void v_eo(JNIEnv*, jobject) {} + static void v_eoo(JNIEnv*, jobject, jobject) {} + static void v_ek(JNIEnv*, jclass) {} + static void v_eolib(JNIEnv*, jobject, jlong, jint, jboolean) {} + static jshort s_eolAibA(JNIEnv*, jobject, jlongArray, jint, jbooleanArray) { return 0; } + +#define DEC_TEST_FN_IMPL(name, ret_t, ...) \ + static ret_t name (__VA_ARGS__) { return TestReturnAnything{}; } + +#define DEC_TEST_FN(name, correct, ret_t, ...) \ + DEC_TEST_FN_IMPL(normal_ ## name, ret_t, JNIEnv*, jobject, __VA_ARGS__) \ + DEC_TEST_FN_IMPL(normal2_ ## name, ret_t, JNIEnv*, jclass, __VA_ARGS__) \ + DEC_TEST_FN_IMPL(critical_ ## name, ret_t, __VA_ARGS__) + +#define DEC_TEST_FN0(name, correct, ret_t) \ + DEC_TEST_FN_IMPL(normal_ ## name, ret_t, JNIEnv*, jobject) \ + DEC_TEST_FN_IMPL(normal2_ ## name, ret_t, JNIEnv*, jclass) \ + DEC_TEST_FN_IMPL(critical_ ## name, ret_t) + +#define JNI_TEST_FN(FN, FN0) \ + FN0(a0,CRITICAL,void) \ + FN0(a ,CRITICAL,jboolean) \ + FN0(a1,CRITICAL,jbyte) \ + FN0(g, CRITICAL,jchar) \ + FN0(c, CRITICAL,jshort) \ + FN0(b, CRITICAL,jint) \ + FN0(f, CRITICAL,jlong) \ + FN0(d, CRITICAL,jfloat) \ + FN0(e, CRITICAL,jdouble) \ + FN0(f2,NORMAL ,jobject) \ + FN0(f3,NORMAL ,jclass) \ + FN0(fr,NORMAL ,jstring) \ + FN0(fa,NORMAL ,jarray) \ + FN0(fb,NORMAL ,jobjectArray) \ + FN0(fc,NORMAL ,jbooleanArray) \ + FN0(fd,NORMAL ,jcharArray) \ + FN0(fe,NORMAL ,jshortArray) \ + FN0(ff,NORMAL ,jintArray) \ + FN0(fg,NORMAL ,jlongArray) \ + FN0(fk,NORMAL ,jfloatArray) \ + FN0(fi,NORMAL ,jdoubleArray) \ + FN0(fl,NORMAL ,jthrowable) \ + FN(aa, CRITICAL,jboolean,jboolean) \ + FN(ax, CRITICAL,jbyte,jbyte) \ + FN(ag, CRITICAL,jchar,jchar) \ + FN(ac, CRITICAL,jshort,jshort) \ + FN(ac2,CRITICAL,jshort,jshort,jchar) \ + FN(ab, CRITICAL,jint,jint) \ + FN(af, CRITICAL,jlong,jlong) \ + FN(ad, CRITICAL,jfloat,jfloat) \ + FN(ae, CRITICAL,jdouble,jdouble) \ + FN(af2,NORMAL ,jobject,jobject) \ + FN(af3,NORMAL ,jclass,jclass) \ + FN(afr,NORMAL ,jstring,jstring) \ + FN(afa,NORMAL ,jarray,jarray) \ + FN(afb,NORMAL ,jobjectArray,jobjectArray) \ + FN(afc,NORMAL ,jbooleanArray,jbooleanArray) \ + FN(afd,NORMAL ,jcharArray,jcharArray) \ + FN(afe,NORMAL ,jshortArray,jshortArray) \ + FN(aff,NORMAL ,jintArray,jintArray) \ + FN(afg,NORMAL ,jlongArray,jlongArray) \ + FN(afk,NORMAL ,jfloatArray,jfloatArray) \ + FN(afi,NORMAL ,jdoubleArray,jdoubleArray) \ + FN(agi,NORMAL ,jdoubleArray,jdoubleArray,jobject) \ + FN(afl,NORMAL ,jthrowable,jthrowable) \ + \ + FN0(z0,ILLEGAL ,JNIEnv*) \ + FN(z1, ILLEGAL ,void, JNIEnv*) \ + FN(z2, ILLEGAL ,JNIEnv*, JNIEnv*) \ + FN(z3, ILLEGAL ,void, void*) \ + FN0(z4,ILLEGAL ,void*) \ + +#define JNI_TEST_FN_BOTH(x) JNI_TEST_FN(x,x) + +// we generate a return statement because some functions are non-void. +// disable the useless warning about returning from a non-void function. +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wreturn-type" + JNI_TEST_FN(DEC_TEST_FN, DEC_TEST_FN0); +#pragma clang diagnostic pop + + // TODO: probably should be an x-macro table + // and that way we can add critical/normal to it as well + // and also the type descriptor, and reuse this for multiple tests. + +#pragma clang diagnostic pop +}; +// Note: Using function-local structs does not work. +// Template parameters must have linkage, which function-local structs lack. + +TEST(JniSafeRegisterNativeMethods, FunctionTypes) { + using namespace nativehelper::detail; // NOLINT + // The exact error messages are not tested but they would be seen in the compiler + // stack trace when used from a constexpr context. + +#define IS_VALID_JNI_FUNCTION_TYPE(native_kind, func) \ + (IsValidJniFunctionType()) +#define IS_VALID_NORMAL_JNI_FUNCTION_TYPE(func) IS_VALID_JNI_FUNCTION_TYPE(kNormalNative, func) +#define IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(func) IS_VALID_JNI_FUNCTION_TYPE(kCriticalNative, func) + +#define EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(func) \ + do { \ + EXPECT_FALSE(IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(func)); \ + EXPECT_FALSE(IS_VALID_NORMAL_JNI_FUNCTION_TYPE(func)); \ + } while (false) + +#define EXPECT_NORMAL_JNI_FUNCTION_TYPE(func) \ + do { \ + EXPECT_FALSE(IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(func)); \ + EXPECT_TRUE(IS_VALID_NORMAL_JNI_FUNCTION_TYPE(func)); \ + } while (false) + +#define EXPECT_CRITICAL_JNI_FUNCTION_TYPE(func) \ + do { \ + EXPECT_TRUE(IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(func)); \ + EXPECT_FALSE(IS_VALID_NORMAL_JNI_FUNCTION_TYPE(func)); \ + } while (false) + + { + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::bad_cptr); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::bad_ret_ptr); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::bad_ret_env); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::bad_wrongplace_env); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::bad_wrongplace_env2); + + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(TestJni::empty_fn); + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(test_jni::empty_fn); + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(TestJni::int_fn); + + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(TestJni::v_); + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(TestJni::v_vol_i); + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(TestJni::v_const_i); + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(TestJni::v_i); + EXPECT_CRITICAL_JNI_FUNCTION_TYPE(TestJni::v_l); + + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::v_e); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::v_ei); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::v_el); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::v_et); + + EXPECT_NORMAL_JNI_FUNCTION_TYPE(TestJni::v_eo); + EXPECT_NORMAL_JNI_FUNCTION_TYPE(TestJni::v_ek); + + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::o_none); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::bad_noref_jint_norm); + EXPECT_ILLEGAL_JNI_FUNCTION_TYPE(TestJni::bad_noref_jint_crit); + } + + enum class TestJniKind { + ILLEGAL, + NORMAL, + CRITICAL + }; + + // ILLEGAL signatures are always illegal. + bool kExpected_ILLEGAL_against_NORMAL = false; + bool kExpected_ILLEGAL_against_CRITICAL = false; + // NORMAL signatures are only legal for Normal JNI. + bool kExpected_NORMAL_against_NORMAL = true; + bool kExpected_NORMAL_against_CRITICAL = false; + // CRITICAL signatures are legal for both Normal+Critical JNI. + bool kExpected_CRITICAL_against_CRITICAL = true; + bool kExpected_CRITICAL_against_NORMAL = true; + // Note that we munge normal and critical type signatures separately + // and that a normal_ prefixed is always a bad critical signature, + // and a critical_ prefixed signature is always a bad normal signature. + // See JNI_TEST_FN_MAKE_TEST for the implementation of this logic. + +#undef EXPECTED_FOR +#define EXPECTED_FOR(jni_kind, context) \ + (kExpected_ ## jni_kind ## _against_ ## context) + + { +#define JNI_TEST_FN_MAKE_TEST(name, jni_kind, ...) \ + do { \ + EXPECT_EQ(EXPECTED_FOR(jni_kind, NORMAL), \ + IS_VALID_NORMAL_JNI_FUNCTION_TYPE(TestJni::normal_ ## name)); \ + EXPECT_FALSE(IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(TestJni::normal_ ## name)); \ + EXPECT_EQ(EXPECTED_FOR(jni_kind, NORMAL), \ + IS_VALID_NORMAL_JNI_FUNCTION_TYPE(TestJni::normal2_ ## name)); \ + EXPECT_FALSE(IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(TestJni::normal2_ ## name)); \ + EXPECT_EQ(EXPECTED_FOR(jni_kind, CRITICAL), \ + IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(TestJni::critical_ ## name)); \ + EXPECT_FALSE(IS_VALID_NORMAL_JNI_FUNCTION_TYPE(TestJni::critical_ ## name)); \ + } while (false); + + JNI_TEST_FN_BOTH(JNI_TEST_FN_MAKE_TEST); + } +} + +#define EXPECT_CONSTEXPR_EQ(lhs, rhs) \ + { constexpr auto lhs_val = (lhs); \ + constexpr auto rhs_val = (rhs); \ + EXPECT_EQ(lhs_val, rhs_val) << "LHS: " << #lhs << ", RHS: " << #rhs; \ + } + +TEST(JniSafeRegisterNativeMethods, FunctionTypeDescriptorConversion) { + using namespace nativehelper::detail; // NOLINT + { + constexpr auto cvrt = MaybeMakeReifiedJniSignature(); + ASSERT_TRUE(cvrt.has_value()); + EXPECT_CONSTEXPR_EQ(2u, cvrt->max_size); + EXPECT_CONSTEXPR_EQ(1u, cvrt->args.size()); + EXPECT_STRINGIFY_EQ("args={jint}, ret=void", cvrt.value()); + } + + { + constexpr auto cvrt = MaybeMakeReifiedJniSignature(); + EXPECT_FALSE(cvrt.has_value()); + } + + { + constexpr auto cvrt = MaybeMakeReifiedJniSignature(); + ASSERT_TRUE(cvrt.has_value()); + EXPECT_EQ(2u, cvrt->args.size()); + EXPECT_STRINGIFY_EQ("args={jdoubleArray,jobject}, ret=jdoubleArray", cvrt.value()); + } + + { + constexpr auto cvrt = MaybeMakeReifiedJniSignature(); + ASSERT_TRUE(cvrt.has_value()); + EXPECT_EQ(2u, cvrt->args.size()); + EXPECT_STRINGIFY_EQ("args={jshort,jchar}, ret=jshort", cvrt.value()); + } + + // TODO: use JNI_TEST_FN to generate these tests automatically. +} + +struct test_function_traits { + static int int_returning_function() { return 0; } +}; + +template +struct apply_return_type { + constexpr int operator()() const { + return sizeof(T) == sizeof(int); + } +}; + +#define FN_ARGS_PAIR(fn) decltype(fn), (fn) + +TEST(JniSafeRegisterNativeMethods, FunctionTraits) { + using namespace nativehelper::detail; // NOLINT + using traits_for_int_ret = + FunctionTypeMetafunction; + int applied = traits_for_int_ret::map_return(); + EXPECT_EQ(1, applied); + + auto arr = traits_for_int_ret::map_args(); + EXPECT_EQ(0u, arr.size()); +} + +struct IntHolder { + int value; +}; + +constexpr int GetTestValue(const IntHolder& i) { + return i.value; +} + +constexpr int GetTestValue(int i) { + return i; +} + +template +constexpr size_t SumUpVector(const nativehelper::detail::ConstexprVector& vec) { + size_t s = 0; + for (const T& elem : vec) { + s += static_cast(GetTestValue(elem)); + } + return s; +} + +template +constexpr auto make_test_int_vector() { + using namespace nativehelper::detail; // NOLINT + ConstexprVector vec_int; + vec_int.push_back(T{1}); + vec_int.push_back(T{2}); + vec_int.push_back(T{3}); + vec_int.push_back(T{4}); + vec_int.push_back(T{5}); + return vec_int; +} + +TEST(JniSafeRegisterNativeMethods, ConstexprOptional) { + using namespace nativehelper::detail; // NOLINT + + ConstexprOptional int_opt; + EXPECT_FALSE(int_opt.has_value()); + + int_opt = ConstexprOptional(12345); + EXPECT_EQ(12345, int_opt.value()); + EXPECT_EQ(12345, *int_opt); +} + +TEST(JniSafeRegisterNativeMethods, ConstexprVector) { + using namespace nativehelper::detail; // NOLINT + { + constexpr ConstexprVector vec_int = make_test_int_vector(); + constexpr size_t the_sum = SumUpVector(vec_int); + EXPECT_EQ(15u, the_sum); + } + + { + constexpr ConstexprVector vec_int = make_test_int_vector(); + constexpr size_t the_sum = SumUpVector(vec_int); + EXPECT_EQ(15u, the_sum); + } +} + +// Need this intermediate function to make a JniDescriptorNode from a string literal. +// C++ doesn't do implicit conversion through two+ type constructors. +constexpr nativehelper::detail::JniDescriptorNode MakeNode( + nativehelper::detail::ConstexprStringView str) { + return nativehelper::detail::JniDescriptorNode{str}; +} + +#define EXPECT_EQUALISH_JNI_DESCRIPTORS_IMPL(user_desc, derived, cond) \ + do { \ + constexpr bool res = \ + CompareJniDescriptorNodeErased(MakeNode(user_desc), \ + ReifiedJniTypeTrait::Reify()); \ + (void)res; \ + EXPECT_ ## cond(CompareJniDescriptorNodeErased(MakeNode(user_desc), \ + ReifiedJniTypeTrait::Reify())); \ + } while (0); + +#define EXPECT_EQUALISH_JNI_DESCRIPTORS(user_desc, derived_desc) \ + EXPECT_EQUALISH_JNI_DESCRIPTORS_IMPL(user_desc, derived_desc, TRUE) + +#define EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS(user_desc, derived_desc) \ + EXPECT_EQUALISH_JNI_DESCRIPTORS_IMPL(user_desc, derived_desc, FALSE) + +TEST(JniSafeRegisterNativeMethods, CompareJniDescriptorNodeErased) { + using namespace nativehelper::detail; // NOLINT + EXPECT_EQUALISH_JNI_DESCRIPTORS("V", void); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("V", jint); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Z", jboolean); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Z", void); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Z", jobject); + EXPECT_EQUALISH_JNI_DESCRIPTORS("J", jlong); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("J", jobject); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("J", jthrowable); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("J", jint); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/String;", jstring); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Class;", jclass); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Object;", jobject); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Integer;", jobject); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("[Z", jthrowable); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("[Z", jobjectArray); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Integer;", jintArray); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Integer;", jarray); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Integer;", jarray); + + // Stricter checks. + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Object;", jobjectArray); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/String;", jobject); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Class;", jobject); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("[Z", jobject); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("[Ljava/lang/Object;", jobject); + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Object;", jarray); + + // Permissive checks that are weaker than normal. + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Exception;", jobject); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Error;", jobject); + EXPECT_EQUALISH_JNI_DESCRIPTORS("[Z", jarray); + EXPECT_EQUALISH_JNI_DESCRIPTORS("[I", jarray); + EXPECT_EQUALISH_JNI_DESCRIPTORS("[[Z", jarray); + EXPECT_EQUALISH_JNI_DESCRIPTORS("[[Ljava/lang/Object;", jarray); + + // jthrowable-related checks. + EXPECT_NOT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Throwable;", jobject); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Throwable;", jthrowable); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Exception;", jthrowable); + EXPECT_EQUALISH_JNI_DESCRIPTORS("Ljava/lang/Error;", jthrowable); +} + +#define EXPECT_SIMILAR_TYPE_DESCRIPTOR_MATCH(type_desc, type) \ + do { \ + constexpr auto res = ReifiedJniTypeTrait::MostSimilarTypeDescriptor(type_desc); \ + EXPECT_TRUE((ReifiedJniTypeTrait::MostSimilarTypeDescriptor(type_desc)).has_value()); \ + if (res.has_value()) EXPECT_EQ(ReifiedJniTypeTrait::Reify(), res.value()); \ + } while (false) + +#define EXPECT_SIMILAR_TYPE_DESCRIPTOR_NO_MATCH(type_desc) \ + do { \ + auto res = ReifiedJniTypeTrait::MostSimilarTypeDescriptor(type_desc); \ + EXPECT_FALSE(res.has_value()); \ + } while (false) + +#define JNI_TYPE_TRAIT_MUST_BE_SAME_FN(type_name, type_desc, ...) \ + /* skip jarray because it aliases Ljava/lang/Object; */ \ + do { \ + constexpr auto str_type_name = ConstexprStringView(#type_name); \ + if (str_type_name != "jarray" && str_type_name != "JNIEnv*") { \ + EXPECT_SIMILAR_TYPE_DESCRIPTOR_MATCH(type_desc, type_name); \ + } \ + } while(false); + +TEST(JniSafeRegisterNativeMethods, MostSimilarTypeDescriptor) { + using namespace nativehelper::detail; // NOLINT + EXPECT_SIMILAR_TYPE_DESCRIPTOR_MATCH("Z", jboolean); + EXPECT_SIMILAR_TYPE_DESCRIPTOR_MATCH("[[I", jobjectArray); + EXPECT_SIMILAR_TYPE_DESCRIPTOR_MATCH("[[Z", jobjectArray); + EXPECT_SIMILAR_TYPE_DESCRIPTOR_MATCH("[Ljava/lang/String;", jobjectArray); + EXPECT_SIMILAR_TYPE_DESCRIPTOR_MATCH("[Ljava/lang/Integer;", jobjectArray); + EXPECT_SIMILAR_TYPE_DESCRIPTOR_NO_MATCH("illegal"); + EXPECT_SIMILAR_TYPE_DESCRIPTOR_NO_MATCH("?"); + EXPECT_SIMILAR_TYPE_DESCRIPTOR_NO_MATCH(""); + + DEFINE_JNI_TYPE_TRAIT(JNI_TYPE_TRAIT_MUST_BE_SAME_FN); +} + +#define ENFORCE_CONSTEXPR(expr) \ + static_assert(__builtin_constant_p(expr), "Expression must be constexpr") + +#define EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION_IMPL(cond, native_kind, func, desc) \ + do { \ + ENFORCE_CONSTEXPR((MatchJniDescriptorWithFunctionType< \ + native_kind, \ + decltype(func), \ + func, \ + sizeof(desc)>(desc))); \ + EXPECT_ ## cond((MatchJniDescriptorWithFunctionType< \ + native_kind, \ + decltype(func), \ + func, \ + sizeof(desc)>(desc))); \ + } while(0) + +#define EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(native_kind, func, desc) \ + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION_IMPL(TRUE, native_kind, func, desc) + +#define EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(native_kind, func, desc) \ + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION_IMPL(FALSE, native_kind, func, desc) + +TEST(JniSafeRegisterNativeMethods, MatchJniDescriptorWithFunctionType) { + using namespace nativehelper::detail; // NOLINT + // Bad C++ signature. + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::bad_cptr, "()V"); + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kNormalNative, TestJni::bad_cptr, "()V"); + + // JNI type descriptor is not legal (by itself). + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::v_, "BAD"); + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kNormalNative, TestJni::v_eo, "BAD"); + + // Number of parameters in signature vs C++ function does not match. + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::v_i, "()V"); + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kNormalNative, TestJni::v_eoo, "()V"); + + // Return types don't match. + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::v_, "()Z"); + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kFastNative, TestJni::v_eo, "()Z"); + + // Argument types don't match. + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::v_i, "(Z)V"); + EXPECT_NO_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kNormalNative, + TestJni::v_eoo, + "(Ljava/lang/Class;)V"); + + // OK. + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::v_i, "(I)V"); + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kNormalNative, + TestJni::v_eoo, + "(Ljava/lang/Object;)V"); + + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::v_lib, "(JIZ)V"); + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kNormalNative, TestJni::v_eolib, "(JIZ)V"); + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kCriticalNative, TestJni::s_lib, "(JIZ)S"); + EXPECT_MATCH_JNI_DESCRIPTOR_AGAINST_FUNCTION(kNormalNative, TestJni::s_eolAibA, "([JI[Z)S"); +} + +TEST(JniSafeRegisterNativeMethods, Infer) { + using namespace nativehelper::detail; // NOLINT + { + using Infer_v_eolib_t = InferJniDescriptor; + EXPECT_CONSTEXPR_EQ(6u, Infer_v_eolib_t::kMaxStringSize); + std::string x = Infer_v_eolib_t::GetStringAtRuntime(); + EXPECT_STRINGIFY_EQ("(JIZ)V", x.c_str()); + } + + { + using Infer_v_eolib_t = InferJniDescriptor; + EXPECT_STRINGIFY_EQ("args={[J,I,[Z}, ret=S", Infer_v_eolib_t::FromFunctionType().value()); + EXPECT_CONSTEXPR_EQ(8u, Infer_v_eolib_t::kMaxStringSize); + std::string x = Infer_v_eolib_t::GetStringAtRuntime(); + EXPECT_STRINGIFY_EQ("([JI[Z)S", x.c_str()); + } +} + +// Test the macro definition only. See other tests above for signature-match testing. +TEST(JniSafeRegisterNativeMethods, MakeCheckedJniNativeMethod) { + // Ensure the temporary variables don't conflict with other local vars of same name. + JNINativeMethod tmp_native_method; // shadow test. + (void) tmp_native_method; + bool is_signature_valid = true; // shadow test. + (void) is_signature_valid; + + // Ensure it works with critical. + { + JNINativeMethod m = + MAKE_CHECKED_JNI_NATIVE_METHOD(kCriticalNative, + "v_lib", + "(JIZ)V", + TestJni::v_lib); + (void)m; + } + + // Ensure it works with normal. + { + JNINativeMethod m = + MAKE_CHECKED_JNI_NATIVE_METHOD(kNormalNative, + "v_eolib", + "(JIZ)V", + TestJni::v_eolib); + (void)m; + } + + // Make sure macros properly expand inside of an array. + { + JNINativeMethod m_array[] = { + MAKE_CHECKED_JNI_NATIVE_METHOD(kCriticalNative, + "v_lib", + "(JIZ)V", + TestJni::v_lib), + MAKE_CHECKED_JNI_NATIVE_METHOD(kNormalNative, + "v_eolib", + "(JIZ)V", + TestJni::v_eolib), + }; + (void)m_array; + } + { + JNINativeMethod m_array_direct[] { + MAKE_CHECKED_JNI_NATIVE_METHOD(kCriticalNative, + "v_lib", + "(JIZ)V", + TestJni::v_lib), + MAKE_CHECKED_JNI_NATIVE_METHOD(kNormalNative, + "v_eolib", + "(JIZ)V", + TestJni::v_eolib), + }; + (void)m_array_direct; + } + +} + +static auto sTestCheckedAtFileScope = + MAKE_CHECKED_JNI_NATIVE_METHOD(kCriticalNative, + "v_lib", + "(JIZ)V", + TestJni::v_lib); + +static auto sTestInferredAtFileScope = + MAKE_INFERRED_JNI_NATIVE_METHOD(kCriticalNative, + "v_lib", + TestJni::v_lib); + +TEST(JniSafeRegisterNativeMethods, TestInferredJniNativeMethod) { + (void) sTestCheckedAtFileScope; + (void) sTestInferredAtFileScope; + + // Ensure it works with critical. + { + JNINativeMethod m = + MAKE_INFERRED_JNI_NATIVE_METHOD(kCriticalNative, + "v_lib", + TestJni::v_lib); + (void)m; + } + + // Ensure it works with normal. + { + JNINativeMethod m = + MAKE_INFERRED_JNI_NATIVE_METHOD(kNormalNative, + "v_eolib", + TestJni::v_eolib); + (void)m; + } +} + +static void TestJniMacros_v_lib(jlong, jint, jboolean) {} +static void TestJniMacros_v_lib_od(jlong, jint, jboolean) {} +static void TestJniMacros_v_eolib(JNIEnv*, jobject, jlong, jint, jboolean) {} +static void TestJniMacros_v_eolib_od(JNIEnv*, jobject, jlong, jint, jboolean) {} + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" + +static jint android_os_Parcel_dataSize(jlong) { return 0; } +static jint android_os_Parcel_dataAvail(jlong) { return 0; } +static jint android_os_Parcel_dataPosition(jlong) { return 0; } +static jint android_os_Parcel_dataCapacity(jlong) { return 0; } +static jlong android_os_Parcel_setDataSize(JNIEnv*, jclass, jlong, jint) { return 0; } +static void android_os_Parcel_setDataPosition(jlong, jint) {} +static void android_os_Parcel_setDataCapacity(JNIEnv*, jclass, jlong, jint) {} +static jboolean android_os_Parcel_pushAllowFds(jlong, jboolean) { return true; } +static void android_os_Parcel_restoreAllowFds(jlong, jboolean) {} +static void android_os_Parcel_writeByteArray(JNIEnv*, jclass, jlong, jbyteArray, jint, jint) {} + +static void android_os_Parcel_writeBlob(JNIEnv*, jclass, jlong, jbyteArray, jint, jint) {} +static void android_os_Parcel_writeInt(JNIEnv*, jclass, jlong, jint) {} +static void android_os_Parcel_writeLong(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jlong val) {} +static void android_os_Parcel_writeFloat(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jfloat val) {} +static void android_os_Parcel_writeDouble(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jdouble val) {} +static void android_os_Parcel_writeString(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jstring val) {} +static void android_os_Parcel_writeStrongBinder(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jobject object) {} +static jlong android_os_Parcel_writeFileDescriptor(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jobject object) { return 0; } +static jbyteArray android_os_Parcel_createByteArray(JNIEnv* env, + jclass clazz, + jlong nativePtr) { return nullptr; } + +static jboolean android_os_Parcel_readByteArray(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jbyteArray dest, + jint destLen) { return false; } +static jbyteArray android_os_Parcel_readBlob(JNIEnv* env, + jclass clazz, + jlong nativePtr) { return nullptr; } + +static jint android_os_Parcel_readInt(jlong nativePtr) { return 0; } + +static jlong android_os_Parcel_readLong(jlong nativePtr) { return 0; } + +static jfloat android_os_Parcel_readFloat(jlong nativePtr) { return 0.0f; } +static jdouble android_os_Parcel_readDouble(jlong nativePtr) { return 0.0; } + +static jstring android_os_Parcel_readString(JNIEnv* env, + jclass clazz, + jlong nativePtr) { return nullptr; } + +static jobject android_os_Parcel_readStrongBinder(JNIEnv* env, + jclass clazz, + jlong nativePtr) { return nullptr; } + + +static jobject android_os_Parcel_readFileDescriptor(JNIEnv* env, + jclass clazz, + jlong nativePtr) { return nullptr; } + +static jobject android_os_Parcel_openFileDescriptor(JNIEnv* env, + jclass clazz, + jstring name, + jint mode) { return 0; } + + +static jobject android_os_Parcel_dupFileDescriptor(JNIEnv* env, + jclass clazz, + jobject orig) { return 0; } + + +static void android_os_Parcel_closeFileDescriptor(JNIEnv* env, + jclass clazz, + jobject object) {} + + +static void android_os_Parcel_clearFileDescriptor(JNIEnv* env, + jclass clazz, + jobject object) {} + + +static jlong android_os_Parcel_create(JNIEnv* env, jclass clazz) { return 0; } + + +static jlong android_os_Parcel_freeBuffer(JNIEnv* env, + jclass clazz, + jlong nativePtr) { return 0; } + + +static void android_os_Parcel_destroy(JNIEnv* env, jclass clazz, jlong nativePtr) {} + + +static jbyteArray android_os_Parcel_marshall(JNIEnv* env, + jclass clazz, + jlong nativePtr) { return 0; } + + +static jlong android_os_Parcel_unmarshall(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jbyteArray data, + jint offset, + jint length) { return 0; } + + +static jint android_os_Parcel_compareData(JNIEnv* env, + jclass clazz, + jlong thisNativePtr, + jlong otherNativePtr) { return 0; } + + +static jlong android_os_Parcel_appendFrom(JNIEnv* env, + jclass clazz, + jlong thisNativePtr, + jlong otherNativePtr, + jint offset, + jint length) { return 0; } + + +static jboolean android_os_Parcel_hasFileDescriptors(jlong nativePtr) { return 0; } + + +static void android_os_Parcel_writeInterfaceToken(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jstring name) {} + + +static void android_os_Parcel_enforceInterface(JNIEnv* env, + jclass clazz, + jlong nativePtr, + jstring name) {} + + +static jlong android_os_Parcel_getGlobalAllocSize(JNIEnv* env, jclass clazz) { return 0; } + + +static jlong android_os_Parcel_getGlobalAllocCount(JNIEnv* env, jclass clazz) { return 0; } + + +static jlong android_os_Parcel_getBlobAshmemSize(jlong nativePtr) { return 0; } + +#pragma clang diagnostic pop + +TEST(JniSafeRegisterNativeMethods, ParcelExample) { + // Test a wide range of automatic signature inferencing. + // This is taken from real code in android_os_Parcel.cpp. + + const JNINativeMethod gParcelMethods[] = { + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeDataSize", android_os_Parcel_dataSize), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeDataAvail", android_os_Parcel_dataAvail), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeDataPosition", android_os_Parcel_dataPosition), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeDataCapacity", android_os_Parcel_dataCapacity), + // @FastNative + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG( + "nativeSetDataSize", android_os_Parcel_setDataSize), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeSetDataPosition", android_os_Parcel_setDataPosition), + // @FastNative + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG( + "nativeSetDataCapacity", android_os_Parcel_setDataCapacity), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativePushAllowFds", android_os_Parcel_pushAllowFds), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeRestoreAllowFds", android_os_Parcel_restoreAllowFds), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeWriteByteArray", android_os_Parcel_writeByteArray), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeWriteBlob", android_os_Parcel_writeBlob), + // @FastNative + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG( + "nativeWriteInt", android_os_Parcel_writeInt), + // @FastNative + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG( + "nativeWriteLong", android_os_Parcel_writeLong), + // @FastNative + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG( + "nativeWriteFloat", android_os_Parcel_writeFloat), + // @FastNative + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG( + "nativeWriteDouble", android_os_Parcel_writeDouble), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeWriteString", android_os_Parcel_writeString), + MAKE_JNI_NATIVE_METHOD( + "nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", android_os_Parcel_writeStrongBinder), + MAKE_JNI_NATIVE_METHOD( + "nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", android_os_Parcel_writeFileDescriptor), + + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeCreateByteArray", android_os_Parcel_createByteArray), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeReadByteArray", android_os_Parcel_readByteArray), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeReadBlob", android_os_Parcel_readBlob), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeReadInt", android_os_Parcel_readInt), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeReadLong", android_os_Parcel_readLong), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeReadFloat", android_os_Parcel_readFloat), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeReadDouble", android_os_Parcel_readDouble), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeReadString", android_os_Parcel_readString), + MAKE_JNI_NATIVE_METHOD( + "nativeReadStrongBinder", "(J)Landroid/os/IBinder;", android_os_Parcel_readStrongBinder), + MAKE_JNI_NATIVE_METHOD( + "nativeReadFileDescriptor", "(J)Ljava/io/FileDescriptor;", android_os_Parcel_readFileDescriptor), + MAKE_JNI_NATIVE_METHOD( + "openFileDescriptor", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", android_os_Parcel_openFileDescriptor), + MAKE_JNI_NATIVE_METHOD( + "dupFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", android_os_Parcel_dupFileDescriptor), + MAKE_JNI_NATIVE_METHOD( + "closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", android_os_Parcel_closeFileDescriptor), + MAKE_JNI_NATIVE_METHOD( + "clearFileDescriptor", "(Ljava/io/FileDescriptor;)V", android_os_Parcel_clearFileDescriptor), + + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeCreate", android_os_Parcel_create), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeFreeBuffer", android_os_Parcel_freeBuffer), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeDestroy", android_os_Parcel_destroy), + + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeMarshall", android_os_Parcel_marshall), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeUnmarshall", android_os_Parcel_unmarshall), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeCompareData", android_os_Parcel_compareData), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeAppendFrom", android_os_Parcel_appendFrom), + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeHasFileDescriptors", android_os_Parcel_hasFileDescriptors), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeWriteInterfaceToken", android_os_Parcel_writeInterfaceToken), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "nativeEnforceInterface", android_os_Parcel_enforceInterface), + + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "getGlobalAllocSize", android_os_Parcel_getGlobalAllocSize), + MAKE_JNI_NATIVE_METHOD_AUTOSIG( + "getGlobalAllocCount", android_os_Parcel_getGlobalAllocCount), + + // @CriticalNative + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG( + "nativeGetBlobAshmemSize", android_os_Parcel_getBlobAshmemSize), + }; + + const JNINativeMethod gParcelMethodsExpected[] = { + // @CriticalNative + {"nativeDataSize", "(J)I", (void*)android_os_Parcel_dataSize}, + // @CriticalNative + {"nativeDataAvail", "(J)I", (void*)android_os_Parcel_dataAvail}, + // @CriticalNative + {"nativeDataPosition", "(J)I", (void*)android_os_Parcel_dataPosition}, + // @CriticalNative + {"nativeDataCapacity", "(J)I", (void*)android_os_Parcel_dataCapacity}, + // @FastNative + {"nativeSetDataSize", "(JI)J", (void*)android_os_Parcel_setDataSize}, + // @CriticalNative + {"nativeSetDataPosition", "(JI)V", (void*)android_os_Parcel_setDataPosition}, + // @FastNative + {"nativeSetDataCapacity", "(JI)V", (void*)android_os_Parcel_setDataCapacity}, + + // @CriticalNative + {"nativePushAllowFds", "(JZ)Z", (void*)android_os_Parcel_pushAllowFds}, + // @CriticalNative + {"nativeRestoreAllowFds", "(JZ)V", (void*)android_os_Parcel_restoreAllowFds}, + + {"nativeWriteByteArray", "(J[BII)V", (void*)android_os_Parcel_writeByteArray}, + {"nativeWriteBlob", "(J[BII)V", (void*)android_os_Parcel_writeBlob}, + // @FastNative + {"nativeWriteInt", "(JI)V", (void*)android_os_Parcel_writeInt}, + // @FastNative + {"nativeWriteLong", "(JJ)V", (void*)android_os_Parcel_writeLong}, + // @FastNative + {"nativeWriteFloat", "(JF)V", (void*)android_os_Parcel_writeFloat}, + // @FastNative + {"nativeWriteDouble", "(JD)V", (void*)android_os_Parcel_writeDouble}, + {"nativeWriteString", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeString}, + {"nativeWriteStrongBinder", "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_writeStrongBinder}, + {"nativeWriteFileDescriptor", "(JLjava/io/FileDescriptor;)J", (void*)android_os_Parcel_writeFileDescriptor}, + + {"nativeCreateByteArray", "(J)[B", (void*)android_os_Parcel_createByteArray}, + {"nativeReadByteArray", "(J[BI)Z", (void*)android_os_Parcel_readByteArray}, + {"nativeReadBlob", "(J)[B", (void*)android_os_Parcel_readBlob}, + // @CriticalNative + {"nativeReadInt", "(J)I", (void*)android_os_Parcel_readInt}, + // @CriticalNative + {"nativeReadLong", "(J)J", (void*)android_os_Parcel_readLong}, + // @CriticalNative + {"nativeReadFloat", "(J)F", (void*)android_os_Parcel_readFloat}, + // @CriticalNative + {"nativeReadDouble", "(J)D", (void*)android_os_Parcel_readDouble}, + {"nativeReadString", "(J)Ljava/lang/String;", (void*)android_os_Parcel_readString}, + {"nativeReadStrongBinder", "(J)Landroid/os/IBinder;", (void*)android_os_Parcel_readStrongBinder}, + {"nativeReadFileDescriptor", "(J)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_readFileDescriptor}, + + {"openFileDescriptor", "(Ljava/lang/String;I)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_openFileDescriptor}, + {"dupFileDescriptor", "(Ljava/io/FileDescriptor;)Ljava/io/FileDescriptor;", (void*)android_os_Parcel_dupFileDescriptor}, + {"closeFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_closeFileDescriptor}, + {"clearFileDescriptor", "(Ljava/io/FileDescriptor;)V", (void*)android_os_Parcel_clearFileDescriptor}, + + {"nativeCreate", "()J", (void*)android_os_Parcel_create}, + {"nativeFreeBuffer", "(J)J", (void*)android_os_Parcel_freeBuffer}, + {"nativeDestroy", "(J)V", (void*)android_os_Parcel_destroy}, + + {"nativeMarshall", "(J)[B", (void*)android_os_Parcel_marshall}, + {"nativeUnmarshall", "(J[BII)J", (void*)android_os_Parcel_unmarshall}, + {"nativeCompareData", "(JJ)I", (void*)android_os_Parcel_compareData}, + {"nativeAppendFrom", "(JJII)J", (void*)android_os_Parcel_appendFrom}, + // @CriticalNative + {"nativeHasFileDescriptors", "(J)Z", (void*)android_os_Parcel_hasFileDescriptors}, + {"nativeWriteInterfaceToken", "(JLjava/lang/String;)V", (void*)android_os_Parcel_writeInterfaceToken}, + {"nativeEnforceInterface", "(JLjava/lang/String;)V", (void*)android_os_Parcel_enforceInterface}, + + {"getGlobalAllocSize", "()J", (void*)android_os_Parcel_getGlobalAllocSize}, + {"getGlobalAllocCount", "()J", (void*)android_os_Parcel_getGlobalAllocCount}, + + // @CriticalNative + {"nativeGetBlobAshmemSize", "(J)J", (void*)android_os_Parcel_getBlobAshmemSize}, + }; + + ASSERT_EQ(sizeof(gParcelMethodsExpected)/sizeof(JNINativeMethod), + sizeof(gParcelMethods)/sizeof(JNINativeMethod)); + + + for (size_t i = 0; i < sizeof(gParcelMethods) / sizeof(JNINativeMethod); ++i) { + const JNINativeMethod& actual = gParcelMethods[i]; + const JNINativeMethod& expected = gParcelMethodsExpected[i]; + + EXPECT_STREQ(expected.name, actual.name); + EXPECT_STREQ(expected.signature, actual.signature) << expected.name; + EXPECT_EQ(expected.fnPtr, actual.fnPtr) << expected.name; + } +} + +TEST(JniSafeRegisterNativeMethods, JniMacros) { + JNINativeMethod tmp_native_method; // shadow variable check. + (void)tmp_native_method; + using Infer_t = int; // shadow using check. + Infer_t unused; + (void)unused; + + MAKE_JNI_CRITICAL_NATIVE_METHOD("v_lib", "(JIZ)V", TestJniMacros_v_lib); + MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG("v_lib", TestJniMacros_v_lib); + CRITICAL_NATIVE_METHOD(TestJniMacros, v_lib, "(JIZ)V"); + OVERLOADED_CRITICAL_NATIVE_METHOD(TestJniMacros, v_lib, "(JIZ)V", v_lib_od); + CRITICAL_NATIVE_METHOD_AUTOSIG(TestJniMacros, v_lib); + + MAKE_JNI_FAST_NATIVE_METHOD("v_eolib", "(JIZ)V", TestJniMacros_v_eolib); + MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG("v_eolib", TestJniMacros_v_eolib); + FAST_NATIVE_METHOD(TestJniMacros, v_eolib, "(JIZ)V"); + OVERLOADED_FAST_NATIVE_METHOD(TestJniMacros, v_eolib, "(JIZ)V", v_eolib_od); + FAST_NATIVE_METHOD_AUTOSIG(TestJniMacros, v_eolib); + + MAKE_JNI_NATIVE_METHOD("v_eolib", "(JIZ)V", TestJniMacros_v_eolib); + MAKE_JNI_NATIVE_METHOD_AUTOSIG("v_eolib", TestJniMacros_v_eolib); + NATIVE_METHOD(TestJniMacros, v_eolib, "(JIZ)V"); + OVERLOADED_NATIVE_METHOD(TestJniMacros, v_eolib, "(JIZ)V", v_eolib_od); + NATIVE_METHOD_AUTOSIG(TestJniMacros, v_eolib); + + _NATIVEHELPER_JNI_MAKE_METHOD_OLD(kNormalNative, "v_eolib", "(JIZ)V", TestJniMacros_v_eolib); + tmp_native_method = + _NATIVEHELPER_JNI_MAKE_METHOD_OLD(kNormalNative, "v_eolib", "(JIZ)V", TestJniMacros_v_eolib); +} diff --git a/tests/jni_gtest/Android.bp b/tests/jni_gtest/Android.bp new file mode 100644 index 0000000..2b0b983 --- /dev/null +++ b/tests/jni_gtest/Android.bp @@ -0,0 +1,20 @@ +// Do not use directly. Use the defaults below. +package { + // http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // the below license kinds from "libnativehelper_license": + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["libnativehelper_license"], +} + +cc_library_headers { + name: "jni_gtest_headers", + host_supported: true, + export_include_dirs: ["base"], +} + +cc_defaults { + name: "jni_gtest_defaults", + header_libs: ["jni_gtest_headers"], + shared_libs: ["libnativehelper"], +} diff --git a/tests/jni_gtest/base/nativehelper/jni_gtest.h b/tests/jni_gtest/base/nativehelper/jni_gtest.h new file mode 100644 index 0000000..6af0728 --- /dev/null +++ b/tests/jni_gtest/base/nativehelper/jni_gtest.h @@ -0,0 +1,124 @@ +/* + * Copyright (C) 2018 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 + +#include + +#include +#include + +namespace android { + +// Example test setup following googletest docs: +// +// template +// class TemplatedTest : public JNITestBase { +// ... +// } +// +// typedef ::testing::Types Providers; +// TYPED_TEST_CASE(TemplatedTest, Providers); +// +// TYPED_TEST() { +// // Test code. Use "this->" to access TemplatedTest members. +// } + + + +// Provider is a concept that must follow this structure: +// +// class JNIProvider { +// public: +// JNIProvider(); +// +// void SetUp(); +// JNIEnv* CreateJNIEnv(); +// +// void DestroyJNIEnv(JNIEnv* env); +// void TearDown(); +// } + +template +class JNITestBase : public Test { +protected: + JNITestBase() : provider_(), env_(nullptr), java_vm_(nullptr) { + } + + void SetUp() override { + Test::SetUp(); + provider_.SetUp(); + env_ = provider_.CreateJNIEnv(); + ASSERT_TRUE(env_ != nullptr); + } + + void TearDown() override { + provider_->DestroyJNIEnv(env_); + provider_->TearDown(); + Test::TearDown(); + } + +protected: + Provider provider_; + + JNIEnv* env_; + JavaVM* java_vm_; +}; + +// A mockable implementation of the Provider concept. It is the responsibility +// of the test to stub out any needed functions (all function pointers will be +// null initially). +// +// TODO: Consider googlemock. +class MockJNIProvider { +public: + MockJNIProvider() { + } + + void SetUp() { + // Nothing to here. + } + + // TODO: Spawn threads to allow more envs? + JNIEnv* CreateJNIEnv() { + return CreateMockedJNIEnv().release(); + } + + void DestroyJNIEnv(JNIEnv* env) { + delete env->functions; + delete env; + } + + void TearDown() { + // Nothing to do here. + } + +protected: + std::unique_ptr CreateMockedJNIEnv() { + JNINativeInterface* inf = new JNINativeInterface(); + memset(inf, 0, sizeof(JNINativeInterface)); + + std::unique_ptr ret(new JNIEnv{0}); + ret->functions = inf; + + return ret; + } +}; + +} // namespace android + diff --git a/tests/libnativehelper_api_test.c b/tests/libnativehelper_api_test.c new file mode 100644 index 0000000..8506e17 --- /dev/null +++ b/tests/libnativehelper_api_test.c @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2019 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. + */ + +// All header files with MODULE_API decorated function declarations. +#include "nativehelper/JNIHelp.h" +#include "nativehelper/JniInvocation.h" +#include "nativehelper/toStringArray.h" + +int libnativehelper_api_test_main() { + // The test here is that the headers are properly guarded to support + // compilation with a C compiler. + return 0; +} diff --git a/tests/libnativehelper_lazy_test.cpp b/tests/libnativehelper_lazy_test.cpp new file mode 100644 index 0000000..fd7c0f2 --- /dev/null +++ b/tests/libnativehelper_lazy_test.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "../libnativehelper_lazy.h" + +#include +#include "jni.h" + +#include "nativehelper/JniInvocation.h" +#include "nativehelper/JNIHelp.h" +#include "nativehelper/JNIPlatformHelp.h" + +// The tests here are just for the case when libnativehelper.so cannot be loaded by +// libnativehelper_lazy. +class LibnativehelperLazyTest : public ::testing::Test { + protected: + virtual void SetUp() { + ::testing::Test::SetUp(); + PreventLibnativehelperLazyLoadingForTests(); + } +}; + +static const char* kLoadFailed = "Failed to load libnativehelper.so"; + +TEST_F(LibnativehelperLazyTest, NoLibnativehelperIsForJNIPlatformHelp) { + C_JNIEnv* env = NULL; + EXPECT_DEATH(jniGetNioBufferBaseArray(env, NULL), kLoadFailed); + EXPECT_DEATH(jniGetNioBufferBaseArrayOffset(env, NULL), kLoadFailed); + EXPECT_DEATH(jniGetNioBufferFields(env, NULL, NULL, NULL, NULL), kLoadFailed); + EXPECT_DEATH(jniGetNioBufferPointer(env, NULL), kLoadFailed); + EXPECT_DEATH(jniUninitializeConstants(), kLoadFailed); +} + +TEST_F(LibnativehelperLazyTest, NoLibnativehelperIsForJniInvocation) { + EXPECT_DEATH(JniInvocationCreate(), kLoadFailed); + EXPECT_DEATH(JniInvocationDestroy(NULL), kLoadFailed); + EXPECT_DEATH(JniInvocationGetLibrary("a", NULL), kLoadFailed); + EXPECT_DEATH(JniInvocationInit(NULL, "a"), kLoadFailed); +} + +TEST_F(LibnativehelperLazyTest, NoLibnativehelperIsForJniApi) { + PreventLibnativehelperLazyLoadingForTests(); + + JavaVM* vm = NULL; + JNIEnv* env = NULL; + jsize count = 0; + + EXPECT_DEATH(JNI_GetDefaultJavaVMInitArgs(NULL), kLoadFailed); + EXPECT_DEATH(JNI_CreateJavaVM(&vm, &env, NULL), kLoadFailed); + EXPECT_EQ(JNI_OK, JNI_GetCreatedJavaVMs(&vm, 1, &count)); + EXPECT_EQ(0, count); +} diff --git a/tests/scoped_local_frame_test.cpp b/tests/scoped_local_frame_test.cpp new file mode 100644 index 0000000..c9c3d27 --- /dev/null +++ b/tests/scoped_local_frame_test.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "nativehelper/scoped_local_frame.h" + +// This is a test that scoped headers work independently. + +void TestScopedLocalFrame(JNIEnv* env) { + ScopedLocalFrame slf(env); +} diff --git a/tests/scoped_local_ref_test.cpp b/tests/scoped_local_ref_test.cpp new file mode 100644 index 0000000..3508d21 --- /dev/null +++ b/tests/scoped_local_ref_test.cpp @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "nativehelper/scoped_local_ref.h" + +// This is a test that scoped headers work independently. + +void TestScopedLocalRef(JNIEnv* env) { + ScopedLocalRef slr1(env); + slr1.reset(nullptr); + slr1.get(); +} diff --git a/tests/scoped_primitive_array_test.cpp b/tests/scoped_primitive_array_test.cpp new file mode 100644 index 0000000..6916d45 --- /dev/null +++ b/tests/scoped_primitive_array_test.cpp @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "nativehelper/scoped_primitive_array.h" + +// This is a test that scoped headers work independently. + +void TestScopedPrimitiveArrayRO(JNIEnv* env, jbooleanArray array) { + ScopedBooleanArrayRO sba(env, array); + sba.reset(nullptr); + sba.get(); + sba.size(); +} + +void TestCompilationRW(JNIEnv* env, jintArray array) { + ScopedIntArrayRW sba(env, array); + sba.reset(nullptr); + sba.get(); + sba.size(); + sba[3] = 3; +} diff --git a/tests/scoped_string_chars_test.cpp b/tests/scoped_string_chars_test.cpp new file mode 100644 index 0000000..3617fcd --- /dev/null +++ b/tests/scoped_string_chars_test.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "nativehelper/scoped_string_chars.h" + +// This is a test that scoped headers work independently. + +void TestCompilationScopedStringChars(JNIEnv* env, string s) { + ScopedStringChars ssc(s); + ssc.get(); +} diff --git a/tests/scoped_utf_chars_test.cpp b/tests/scoped_utf_chars_test.cpp new file mode 100644 index 0000000..9764e7a --- /dev/null +++ b/tests/scoped_utf_chars_test.cpp @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "nativehelper/scoped_utf_chars.h" + +// This is a test that scoped headers work independently. + +void TestCompilationScopedUtfChars(JNIEnv* env, jstring s) { + ScopedUtfChars suc(env, s); + suc.c_str(); +} \ No newline at end of file diff --git a/tests_mts/Android.bp b/tests_mts/Android.bp new file mode 100644 index 0000000..9483a62 --- /dev/null +++ b/tests_mts/Android.bp @@ -0,0 +1,72 @@ +// Copyright (C) 2020 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. + +package { + // http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // the below license kinds from "libnativehelper_license": + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["libnativehelper_license"], +} + +java_defaults { + name: "mts_libnativehelper_defaults", + compile_multilib: "both", + libs: [ + "android.test.base.stubs", + ], + static_libs: [ + "ctstestrunner-axt", + "nativetesthelper", + ], + jni_uses_platform_apis: true, + sdk_version: "test_current", + srcs: ["src/com/android/art/libnativehelper/JniHelpTest.java"], + test_suites: [ + "general-tests", + "mts", + ], +} + +android_test { + name: "MtsLibnativehelperTestCases", + defaults: [ + "cts_defaults", + "mts_libnativehelper_defaults", + ], + jni_libs: [ + "libnativehelper_mts_jni", + ], + manifest: "AndroidManifest.xml", + srcs: [ + "src/com/android/art/libnativehelper/LibnativehelperGTests.java", + ], +} + +// Same tests as "MtsLibnativehelperTestCases", but with the jni library +// linked against libnativehelper_lazy.a rather than libnativehelper.so. + +android_test { + name: "MtsLibnativehelperLazyTestCases", + defaults: [ + "cts_defaults", + "mts_libnativehelper_defaults", + ], + jni_libs: [ + "libnativehelper_lazy_mts_jni", + ], + srcs: [ + "src/com/android/art/libnativehelper/LibnativehelperLazyGTests.java", + ], +} diff --git a/tests_mts/AndroidManifest.xml b/tests_mts/AndroidManifest.xml new file mode 100644 index 0000000..98c8523 --- /dev/null +++ b/tests_mts/AndroidManifest.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + diff --git a/tests_mts/OWNERS b/tests_mts/OWNERS new file mode 100644 index 0000000..da3f4a8 --- /dev/null +++ b/tests_mts/OWNERS @@ -0,0 +1,5 @@ +# Bug component: 86431 +enh@google.com +mast@google.com +ngeoffray@google.com +oth@google.com diff --git a/tests_mts/README.md b/tests_mts/README.md new file mode 100644 index 0000000..48d932c --- /dev/null +++ b/tests_mts/README.md @@ -0,0 +1,51 @@ +# libnativehelper MTS tests + +These tests cover the API surface of libnativehelper that is applicable once +the runtime is initialized. + +These tests do not cover the API surface relating to the binding of the ART +runtime (DalvikVM), that preclude the initialization of the runtime, nor do +they cover JNI_CreateJavaVM(). These APIs have been invoked before the test +harness runs these tests. + +API surface not directly covered here are: + +``` + JNI_GetCreatedJavaVMs + + JniInvocationCreate + JniInvocationDestroy + JniInvocationInit + JniInvocationGetLibrary + + jniUninitializeConstants +``` + +`JniInvocationInit()` is responsible for binding the ART runtime and +specifically the following methods: + +``` + JNI_CreateJavaVM + JNI_GetCreatedJavaVMs + JNI_GetDefaultJavaVMInitArgs +``` + +These tests do check that `JNI_GetCreatedJavaVMs()` and +`JNI_GetDefaultJavaVMInitArgs()` behave as expected and are thus asserted to +be correctly bound. `JNI_CreateJavaVM()` cannot be called in these tests +because Android only supports a single runtime per process. + +`JniInvocationInit()` uses `JniInvocationGetLibrary()` to determine which +runtime to load (release, debug, or custom). The code responsible for that +decision is tested comprehensively in `libnativehelper_gtests`. + +`jniUninitializeConstants` is only intended to be called when the runtime is +shutting down and unloading the managed core libraries. + +## Potential Issues + +The test harness depends on libnativehelper_compat_libc++ and the tests +depend on libnativehelper. The former library is a subset of libnativehelper. +There are potential ODR problems if the two libraries having overlapping +global state. It would be better to have two separate test suites for these +two libraries. diff --git a/tests_mts/jni/Android.bp b/tests_mts/jni/Android.bp new file mode 100644 index 0000000..1a8b635 --- /dev/null +++ b/tests_mts/jni/Android.bp @@ -0,0 +1,56 @@ +// Copyright (C) 2020 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. + +package { + // http://go/android-license-faq + // A large-scale-change added 'default_applicable_licenses' to import + // the below license kinds from "libnativehelper_license": + // SPDX-license-identifier-Apache-2.0 + default_applicable_licenses: ["libnativehelper_license"], +} + +cc_defaults { + name: "libnativehelper_jni_defaults", + cflags: [ + "-Wall", + "-Werror", + "-Wno-unused-parameter", + ], + srcs: [ + "jni_invocation_test.cpp", + "jni_helper_jni.cpp", + "libnativehelper_test.cpp", + ], + shared_libs: ["liblog"], + static_libs: ["libgmock_ndk"], + stl: "c++_static", + // libnativetesthelper_jni depends on libnativehelper_compat_libc++. + // At the time of writing there is no duplicated global state in the + // libnativehelper sources between these functions. Should this change, + // then there could be ODR problems here. + whole_static_libs: ["libnativetesthelper_jni"], + tidy: true, +} + +cc_library_shared { + name: "libnativehelper_mts_jni", + defaults: ["libnativehelper_jni_defaults"], + shared_libs: ["libnativehelper"], +} + +cc_library_shared { + name: "libnativehelper_lazy_mts_jni", + defaults: ["libnativehelper_jni_defaults"], + static_libs: ["libnativehelper_lazy"], +} diff --git a/tests_mts/jni/jni_helper_jni.cpp b/tests_mts/jni/jni_helper_jni.cpp new file mode 100644 index 0000000..65a8f13 --- /dev/null +++ b/tests_mts/jni/jni_helper_jni.cpp @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2020 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. + */ + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "libnativehelper_test.h" + +namespace { + +static void throwException(JNIEnv* env, jclass /*clazz*/, jstring className, jstring message) { + ScopedUtfChars c(env, className); + ScopedUtfChars m(env, message); + jniThrowException(env, c.c_str(), m.c_str()); +} + +static void throwExceptionWithIntFormat(JNIEnv* env, + jclass /*clazz*/, + jstring className, + jstring format, + jint value) { + ScopedUtfChars c(env, className); + ScopedUtfChars f(env, format); + jniThrowExceptionFmt(env, c.c_str(), f.c_str(), value); +} + +static void throwNullPointerException(JNIEnv* env, + jclass /*clazz*/, + jstring message) { + ScopedUtfChars m(env, message); + jniThrowNullPointerException(env, m.c_str()); +} + +static void throwRuntimeException(JNIEnv* env, jclass /*clazz*/, jstring message) { + ScopedUtfChars m(env, message); + jniThrowRuntimeException(env, m.c_str()); +} + +static void throwIOException(JNIEnv* env, jclass /*clazz*/, jint cause) { + jniThrowIOException(env, cause); +} + +static void throwErrnoException(JNIEnv* env, jclass /*clazz*/, jstring functionName, jint cause) { + ScopedUtfChars m(env, functionName); + jniThrowErrnoException(env, m.c_str(), cause); +} + +static void logException(JNIEnv* env, jclass /*clazz*/, jthrowable throwable) { + jniLogException(env, ANDROID_LOG_VERBOSE, __FILE__, throwable); +} + +static jobject fileDescriptorCreate(JNIEnv* env, jclass /*clazz*/, jint unix_fd) { + return jniCreateFileDescriptor(env, unix_fd); +} + +static jint fileDescriptorGetFD(JNIEnv* env, jclass /*clazz*/, jobject jiofd) { + return jniGetFDFromFileDescriptor(env, jiofd); +} + +static void fileDescriptorSetFD(JNIEnv* env, jclass /*clazz*/, jobject jiofd, jint unix_fd) { + jniSetFileDescriptorOfFD(env, jiofd, unix_fd); +} + +static jstring createString(JNIEnv* env, jclass /*clazz*/, jstring value) { + ScopedStringChars ssc(env, value); + return jniCreateString(env, ssc.get(), ssc.size()); +} + +} // namespace + +JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) { + JNIEnv* env; + if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) { + return JNI_ERR; + } + + static const JNINativeMethod methods[] = { + MAKE_JNI_NATIVE_METHOD("throwException", + "(Ljava/lang/String;Ljava/lang/String;)V", + throwException), + MAKE_JNI_NATIVE_METHOD("throwExceptionWithIntFormat", + "(Ljava/lang/String;Ljava/lang/String;I)V", + throwExceptionWithIntFormat), + MAKE_JNI_NATIVE_METHOD("throwNullPointerException", + "(Ljava/lang/String;)V", + throwNullPointerException), + MAKE_JNI_NATIVE_METHOD("throwRuntimeException", + "(Ljava/lang/String;)V", + throwRuntimeException), + MAKE_JNI_NATIVE_METHOD("throwIOException", + "(I)V", + throwIOException), + MAKE_JNI_NATIVE_METHOD("throwErrnoException", + "(Ljava/lang/String;I)V", + throwErrnoException), + MAKE_JNI_NATIVE_METHOD("logException", + "(Ljava/lang/Throwable;)V", + logException), + MAKE_JNI_NATIVE_METHOD("fileDescriptorCreate", + "(I)Ljava/io/FileDescriptor;", + fileDescriptorCreate), + MAKE_JNI_NATIVE_METHOD("fileDescriptorGetFD", + "(Ljava/io/FileDescriptor;)I", + fileDescriptorGetFD), + MAKE_JNI_NATIVE_METHOD("fileDescriptorSetFD", + "(Ljava/io/FileDescriptor;I)V", + fileDescriptorSetFD), + MAKE_JNI_NATIVE_METHOD("createString", + "(Ljava/lang/String;)Ljava/lang/String;", + createString), + }; + int rc = jniRegisterNativeMethods(env, + "com/android/art/libnativehelper/JniHelpTest", + methods, + std::size(methods)); + if (rc != JNI_OK) return rc; + + return JNI_VERSION_1_6; +} diff --git a/tests_mts/jni/jni_invocation_test.cpp b/tests_mts/jni/jni_invocation_test.cpp new file mode 100644 index 0000000..1cf0ec7 --- /dev/null +++ b/tests_mts/jni/jni_invocation_test.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2020 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. + */ + +#include "libnativehelper_test.h" + +TEST_F(LibnativehelperTest, GetCreatedJavaVMs) { + JavaVM* createdVMs[2] = { nullptr, nullptr }; + jsize count; + ASSERT_NE(nullptr, mEnv); + ASSERT_EQ(JNI_OK, JNI_GetCreatedJavaVMs(&createdVMs[0], 2, &count)); + ASSERT_EQ(1, count); + ASSERT_NE(nullptr, createdVMs[0]); + ASSERT_EQ(nullptr, createdVMs[1]); + + JavaVM* currentVM; + ASSERT_EQ(JNI_OK, mEnv->GetJavaVM(¤tVM)); + ASSERT_EQ(createdVMs[0], currentVM); +} + +TEST_F(LibnativehelperTest, GetDefaultJavaVMInitArgs) { + JavaVMOption options[1]; + JavaVMInitArgs initArgs; + initArgs.version = JNI_VERSION_1_6; + initArgs.nOptions = 0; + initArgs.options = options; + initArgs.ignoreUnrecognized = JNI_TRUE; + // ART does not support JNI_GetDefaultJavaVMInitArgs(), should this change it'll need a test. + ASSERT_EQ(JNI_ERR, JNI_GetDefaultJavaVMInitArgs(&initArgs)); +} diff --git a/tests_mts/jni/libnativehelper_test.cpp b/tests_mts/jni/libnativehelper_test.cpp new file mode 100644 index 0000000..5371004 --- /dev/null +++ b/tests_mts/jni/libnativehelper_test.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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. + */ + +#include + +#include + +void LibnativehelperTest::SetUp() { + int result = GetJavaVM()->GetEnv(reinterpret_cast(&mEnv), JNI_VERSION_1_6); + EXPECT_EQ(JNI_OK, result); + EXPECT_NE(nullptr, mEnv); +} + +void LibnativehelperTest::TearDown() { + mEnv = nullptr; +} diff --git a/tests_mts/jni/libnativehelper_test.h b/tests_mts/jni/libnativehelper_test.h new file mode 100644 index 0000000..0d3ed20 --- /dev/null +++ b/tests_mts/jni/libnativehelper_test.h @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2020 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 + +#include + +class LibnativehelperTest : public ::testing::Test { + protected: + virtual void SetUp() override; + virtual void TearDown() override; + + JNIEnv* mEnv; +}; diff --git a/tests_mts/src/com/android/art/libnativehelper/JniHelpTest.java b/tests_mts/src/com/android/art/libnativehelper/JniHelpTest.java new file mode 100644 index 0000000..7e8a0e9 --- /dev/null +++ b/tests_mts/src/com/android/art/libnativehelper/JniHelpTest.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020 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. + */ + +package com.android.art.libnativehelper; + +import android.test.AndroidTestCase; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.SoftReference; +import java.nio.Buffer; +import java.nio.ByteBuffer; +import java.nio.ShortBuffer; +import java.nio.IntBuffer; +import java.nio.FloatBuffer; +import java.nio.DoubleBuffer; + +import android.system.ErrnoException; + +import org.junit.Assert; + +public class JniHelpTest extends AndroidTestCase { + private static native void throwException(String className, String message); + private static native void throwExceptionWithIntFormat(String className, + String format, + int value); + private static native void throwNullPointerException(String message); + private static native void throwRuntimeException(String message); + private static native void throwIOException(int cause) throws IOException; + private static native void throwErrnoException(String fileName, int cause) throws ErrnoException; + private static native void logException(Throwable throwable); + + private static native FileDescriptor fileDescriptorCreate(int unixFd); + private static native int fileDescriptorGetFD(FileDescriptor jiofd); + private static native void fileDescriptorSetFD(FileDescriptor jiofd, int unixFd); + + private static native String createString(String input); + + public void testThrowException() { + final String message = "Because test"; + try { + throwException("java/lang/RuntimeException", message); + fail("Unreachable"); + } catch (RuntimeException e) { + assertEquals(message, e.getMessage()); + } + } + + public void testThrowExceptionWithIntFormat() { + final String format = "Because test %d"; + try { + throwExceptionWithIntFormat("java/lang/RuntimeException", format, 101); + fail("Unreachable"); + } catch (RuntimeException e) { + assertEquals("Because test 101", e.getMessage()); + } + } + + public void testThrowNullPointerException() { + final String message = "Because another test"; + try { + throwNullPointerException(message); + fail("Unreachable"); + } catch (NullPointerException e) { + assertEquals(message, e.getMessage()); + } + } + + public void testThrowRuntimeException() { + final String message = "Because test again"; + try { + throwRuntimeException(message); + fail("Unreachable"); + } catch (RuntimeException e) { + assertEquals(message, e.getMessage()); + } + } + + public void testIOException() { + String s1 = null; + try { + throwIOException(70); + fail("Unreachable"); + } catch (IOException e) { + s1 = e.getMessage(); + } + assertNotNull(s1); + + String s2 = null; + try { + throwIOException(71); + fail("Unreachable"); + } catch (IOException e) { + s2 = e.getMessage(); + } + assertNotNull(s2); + + assertFalse(s1.equals(s2)); + } + + public void testErrnoException() { + final String functionName = "execve"; + final int err = 42; + try { + throwErrnoException(functionName, err); + fail("Unreachable"); + } catch (ErrnoException e) { + // The message contains the function name as well as the string for the errno, just only + // check the first part of the message + assertTrue("Function name", e.getMessage().startsWith(functionName)); + assertEquals(err, e.errno); + } + } + + public void testLogException() { + try { + throw new RuntimeException("Exception for logging test"); + } catch (RuntimeException e) { + // TODO: Mock/redirect logcat to test output is logged appropriately. + // Or add extend JNIHelp API to write to a buffer or file. + logException(e); + } + } + + public void testFileDescriptorCreate() { + int [] unix_fds = { -999, -1, 0, 1, 1000 }; + for (int unix_fd : unix_fds) { + FileDescriptor f0 = fileDescriptorCreate(unix_fd); + assertNotNull(f0); + assertSame(f0.getClass(), FileDescriptor.class); + } + } + + public void testFileDescriptorGetNull() { + assertEquals(-1, fileDescriptorGetFD(null)); + } + + public void testFileDescriptorGetNonNull() { + final int UNIX_FD = 77; + FileDescriptor jiofd = fileDescriptorCreate(UNIX_FD); + assertEquals(UNIX_FD, fileDescriptorGetFD(jiofd)); + } + + public void testFileDescriptorSetNull() { + try { + fileDescriptorSetFD(null, 1); + fail("Expected NullPointerException to be thrown."); + } catch (NullPointerException e) {} + } + + public void testFileDescriptorSetNonNull() { + final int UNIX_FD = 127; + FileDescriptor jiofd = fileDescriptorCreate(0); + fileDescriptorSetFD(jiofd, UNIX_FD); + assertEquals(UNIX_FD, fileDescriptorGetFD(jiofd)); + } + + public void testCreateString() { + String input = "The treacherous mountain path lay ahead."; + String output = createString(input); + assertEquals(input, output); + assertNotSame(input, output); + } +} diff --git a/tests_mts/src/com/android/art/libnativehelper/LibnativehelperGTests.java b/tests_mts/src/com/android/art/libnativehelper/LibnativehelperGTests.java new file mode 100644 index 0000000..e85e59c --- /dev/null +++ b/tests_mts/src/com/android/art/libnativehelper/LibnativehelperGTests.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 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. + */ + +package com.android.art.libnativehelper; + +import com.android.gtestrunner.GtestRunner; +import com.android.gtestrunner.TargetLibrary; + +import org.junit.runner.RunWith; + +@RunWith(GtestRunner.class) +@TargetLibrary("nativehelper_mts_jni") +public class LibnativehelperGTests {} diff --git a/tests_mts/src/com/android/art/libnativehelper/LibnativehelperLazyGTests.java b/tests_mts/src/com/android/art/libnativehelper/LibnativehelperLazyGTests.java new file mode 100644 index 0000000..7b53213 --- /dev/null +++ b/tests_mts/src/com/android/art/libnativehelper/LibnativehelperLazyGTests.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2020 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. + */ + +package com.android.art.libnativehelper; + +import com.android.gtestrunner.GtestRunner; +import com.android.gtestrunner.TargetLibrary; + +import org.junit.runner.RunWith; + +@RunWith(GtestRunner.class) +@TargetLibrary("nativehelper_lazy_mts_jni") +public class LibnativehelperLazyGTests {}