commit b23f8b6eebc2d24b49ac25cf6ef0f55d76421849 Author: cpeng Date: Mon Aug 25 08:35:43 2025 +0800 android 13 from xiaosuan 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 {}