aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorandroid-build-team Robot <android-build-team-robot@google.com>2018-02-28 08:21:30 +0000
committerandroid-build-team Robot <android-build-team-robot@google.com>2018-02-28 08:21:30 +0000
commitafd3a633ab30cb17758543b4bbf2e8165812ce7c (patch)
treec65bfe54620ab04d2fbcfea059988c01f994de54
parentc4e2b1fba922bf6f521def3d316c741e07b02a2d (diff)
parent6b56b692bc74a19f293e378c0dffea15460f4870 (diff)
downloadlibnativehelper-android-wear-9.0.0_r7.tar.gz
Snap for 4625912 from 6b56b692bc74a19f293e378c0dffea15460f4870 to pi-releaseandroid-wear-9.0.0_r9android-wear-9.0.0_r8android-wear-9.0.0_r7android-wear-9.0.0_r6android-wear-9.0.0_r5android-wear-9.0.0_r4android-wear-9.0.0_r34android-wear-9.0.0_r33android-wear-9.0.0_r32android-wear-9.0.0_r31android-wear-9.0.0_r30android-wear-9.0.0_r3android-wear-9.0.0_r29android-wear-9.0.0_r28android-wear-9.0.0_r27android-wear-9.0.0_r26android-wear-9.0.0_r25android-wear-9.0.0_r24android-wear-9.0.0_r23android-wear-9.0.0_r22android-wear-9.0.0_r21android-wear-9.0.0_r20android-wear-9.0.0_r2android-wear-9.0.0_r19android-wear-9.0.0_r18android-wear-9.0.0_r17android-wear-9.0.0_r16android-wear-9.0.0_r15android-wear-9.0.0_r14android-wear-9.0.0_r13android-wear-9.0.0_r12android-wear-9.0.0_r11android-wear-9.0.0_r10android-wear-9.0.0_r1android-vts-9.0_r9android-vts-9.0_r8android-vts-9.0_r7android-vts-9.0_r6android-vts-9.0_r5android-vts-9.0_r4android-vts-9.0_r19android-vts-9.0_r18android-vts-9.0_r17android-vts-9.0_r16android-vts-9.0_r15android-vts-9.0_r14android-vts-9.0_r13android-vts-9.0_r12android-vts-9.0_r11android-vts-9.0_r10android-security-9.0.0_r76android-security-9.0.0_r75android-security-9.0.0_r74android-security-9.0.0_r73android-security-9.0.0_r72android-security-9.0.0_r71android-security-9.0.0_r70android-security-9.0.0_r69android-security-9.0.0_r68android-security-9.0.0_r67android-security-9.0.0_r66android-security-9.0.0_r65android-security-9.0.0_r64android-security-9.0.0_r63android-security-9.0.0_r62android-cts-9.0_r9android-cts-9.0_r8android-cts-9.0_r7android-cts-9.0_r6android-cts-9.0_r5android-cts-9.0_r4android-cts-9.0_r3android-cts-9.0_r20android-cts-9.0_r2android-cts-9.0_r19android-cts-9.0_r18android-cts-9.0_r17android-cts-9.0_r16android-cts-9.0_r15android-cts-9.0_r14android-cts-9.0_r13android-cts-9.0_r12android-cts-9.0_r11android-cts-9.0_r10android-cts-9.0_r1android-9.0.0_r9android-9.0.0_r8android-9.0.0_r7android-9.0.0_r61android-9.0.0_r60android-9.0.0_r6android-9.0.0_r59android-9.0.0_r58android-9.0.0_r57android-9.0.0_r56android-9.0.0_r55android-9.0.0_r54android-9.0.0_r53android-9.0.0_r52android-9.0.0_r51android-9.0.0_r50android-9.0.0_r5android-9.0.0_r49android-9.0.0_r48android-9.0.0_r3android-9.0.0_r2android-9.0.0_r18android-9.0.0_r17android-9.0.0_r10android-9.0.0_r1security-pi-releasepie-vts-releasepie-security-releasepie-s2-releasepie-release-2pie-releasepie-r2-s2-releasepie-r2-s1-releasepie-r2-releasepie-platform-releasepie-gsipie-cuttlefish-testingpie-cts-release
Change-Id: I7481c5218a745924766dc0a7845ee64de4c14403
-rw-r--r--platform_include/nativehelper/detail/signature_checker.h1437
-rw-r--r--platform_include/nativehelper/jni_macros.h283
-rw-r--r--tests/Android.bp50
-rw-r--r--tests/JniSafeRegisterNativeMethods_test.cpp882
4 files changed, 2613 insertions, 39 deletions
diff --git a/platform_include/nativehelper/detail/signature_checker.h b/platform_include/nativehelper/detail/signature_checker.h
new file mode 100644
index 0000000..b4ea68e
--- /dev/null
+++ b/platform_include/nativehelper/detail/signature_checker.h
@@ -0,0 +1,1437 @@
+/*
+ * 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 <iostream> // std::ostream
+#include <jni.h> // jni typedefs, JniNativeMethod.
+#include <type_traits> // 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<char>.
+//
+// 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<size_t N>
+ 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);
+ }
+
+ // Implicit conversion from nullptr, creates empty view.
+ // ConstexprStringView str = nullptr;
+ explicit constexpr ConstexprStringView(const decltype(nullptr)&)
+ : _array(""), _size(0u) {
+ }
+
+ // 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(start + len <= size());
+
+ 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 IsValidJniDescriptorShorty(char shorty) {
+ constexpr char kValidJniTypes[] =
+ {'V', 'Z', 'B', 'C', 'S', 'I', 'J', 'F', 'D', 'L', '[', '(', ')'};
+
+ for (char c : kValidJniTypes) {
+ 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<typename T, size_t kMaxSize>
+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<typename Elem>
+ struct VectorIterator {
+ Elem* ptr;
+
+ constexpr VectorIterator& operator++() {
+ ++ptr;
+ return *this;
+ }
+
+ constexpr VectorIterator operator++(int) const {
+ VectorIterator tmp(*this);
+ ++tmp;
+ return tmp;
+ }
+
+ constexpr auto& operator*() {
+ // Use 'auto' here since using 'T' is incorrect with const_iterator.
+ return ptr->_value;
+ }
+
+ constexpr const T& operator*() const {
+ 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<MaybeElement>;
+ using const_iterator = VectorIterator<const MaybeElement>;
+
+ 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: explicit.
+ 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<typename T>
+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)
+ : _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());
+ }
+
+ private:
+ bool _has_value;
+ // The "Nothing" is likely unnecessary but improves readability.
+ struct Nothing {};
+ union {
+ Nothing _nothing;
+ T _value;
+ };
+};
+
+template<typename T>
+constexpr bool
+operator==(const ConstexprOptional<T>& lhs, const ConstexprOptional<T>& rhs) {
+ if (lhs && rhs) {
+ return lhs.value() == rhs.value();
+ }
+ return lhs.has_value() == rhs.has_value();
+}
+
+template<typename T>
+constexpr bool
+operator!=(const ConstexprOptional<T>& lhs, const ConstexprOptional<T>& rhs) {
+ return !(lhs == rhs);
+}
+
+template<typename T>
+inline std::ostream& operator<<(std::ostream& os, const ConstexprOptional<T>& val) {
+ if (val) {
+ os << val.value();
+ }
+ return os;
+}
+
+// Equivalent of std::nullopt
+// Allows implicit conversion to any empty ConstexprOptional<T>.
+// Mostly useful for macros that need to return an empty constexpr optional.
+struct NullConstexprOptional {
+ template<typename T>
+ constexpr operator ConstexprOptional<T>() const {
+ return ConstexprOptional<T>();
+ }
+};
+
+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<ParseTypeDescriptorResult>
+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(IsValidJniDescriptorShorty(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<ParseTypeDescriptorResult>
+ 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) {
+ 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<typename T, size_t kMaxSize>
+struct FunctionSignatureDescriptor {
+ ConstexprVector<T, kMaxSize> args;
+ T ret;
+
+ static constexpr size_t max_size = kMaxSize;
+};
+
+
+template<typename T, size_t kMaxSize>
+inline std::ostream& operator<<(std::ostream& os,
+ const FunctionSignatureDescriptor<T,
+ kMaxSize>& 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<size_t kMaxSize>
+using JniSignatureDescriptor = FunctionSignatureDescriptor<JniDescriptorNode,
+ kMaxSize>;
+
+// 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<size_t kMaxSize>
+constexpr ConstexprOptional<JniSignatureDescriptor<kMaxSize>>
+ParseSignatureAsList(ConstexprStringView signature) {
+ // The list of JNI descritors 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<JniDescriptorNode, kMaxSize> 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<ParseTypeDescriptorResult>
+ 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<ParseTypeDescriptorResult>
+ 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<typename T>
+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<T> 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<typename T>
+ 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<T>;
+ 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<ReifiedJniTypeTrait>
+ 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& rjft) {
+ // os << "ReifiedJniTypeTrait<" << rjft.type_name << ">";
+ os << rjft.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>
+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<type>() }; \
+ }
+
+ // 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<jobject>()};
+ } 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<jobjectArray>()};
+ }
+ }
+
+ // To handle completely invalid values.
+ return NullConstexprOptional{};
+}
+
+// Check if a jni parameter type is valid given its position and native_kind.
+template <typename T>
+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<typename std::remove_cv<T>::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. CriticalNatives ignore positions since the first 2 special parameters are stripped.
+ while (native_kind != kCriticalNative) {
+ NativePositionAllowed expected_position = expected_trait::position_allowed;
+ X_ASSERT(expected_position != kNotAnyPosition);
+
+ // 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.
+ break;
+ }
+
+ // 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;
+ }
+ }
+
+ break;
+ }
+
+ // 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<NativeKind native_kind, size_t position, typename ... Args>
+struct is_valid_jni_argument_type {
+ // static constexpr bool value = ?;
+};
+
+template<NativeKind native_kind, size_t position>
+struct is_valid_jni_argument_type<native_kind, position> {
+ static constexpr bool value = true;
+};
+
+template<NativeKind native_kind, size_t position, typename T>
+struct is_valid_jni_argument_type<native_kind, position, T> {
+ static constexpr bool value =
+ IsValidJniParameter<T>(native_kind, ConvertPositionToAllowed(position));
+};
+
+template<NativeKind native_kind, size_t position, typename T, typename ... Args>
+struct is_valid_jni_argument_type<native_kind, position, T, Args...> {
+ static constexpr bool value =
+ IsValidJniParameter<T>(native_kind, ConvertPositionToAllowed(position))
+ && is_valid_jni_argument_type<native_kind,
+ position + 1,
+ Args...>::value;
+};
+
+// This helper is required to decompose the function type into a list of arg types.
+template<NativeKind native_kind, typename T, T fn>
+struct is_valid_jni_function_type_helper;
+
+template<NativeKind native_kind, typename R, typename ... Args, R fn(Args...)>
+struct is_valid_jni_function_type_helper<native_kind, R(Args...), fn> {
+ static constexpr bool value =
+ IsJniParameterCountValid(native_kind, sizeof...(Args))
+ && IsValidJniParameter<R>(native_kind, kReturnPosition)
+ && is_valid_jni_argument_type<native_kind, /*position*/
+ 0,
+ Args...>::value;
+};
+
+// Is this function type 'T' a valid C++ function type given the native_kind?
+template<NativeKind native_kind, typename T, T fn>
+constexpr bool IsValidJniFunctionType() {
+ return is_valid_jni_function_type_helper<native_kind, T, fn>::value;
+ // TODO: we could replace template metaprogramming with constexpr by
+ // using FunctionTypeMetafunction.
+}
+
+// Many parts of std::array is not constexpr until C++17.
+template<typename T, size_t N>
+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, sz>{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<Args...> 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<typename... Args>
+constexpr auto MakeArray(Args&& ... args) {
+ return ConstexprArray<typename std::common_type<Args...>::type,
+ sizeof...(Args)>{args...};
+}
+
+// See below.
+template<typename T, T fn>
+struct FunctionTypeMetafunction {
+};
+
+// Enables the "map" operation over the function component types.
+template<typename R, typename ... Args, R fn(Args...)>
+struct FunctionTypeMetafunction<R(Args...), fn> {
+ // 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<template<typename Arg> class Func>
+ static constexpr auto map_args() {
+ return map_args_impl<Func>(holder < Args > {}...);
+ }
+
+ // Apply the metafunction 'Func' over the return type.
+ template<template<typename Ret> class Func>
+ static constexpr auto map_return() {
+ return Func<R>{}();
+ }
+
+ private:
+ template<typename T>
+ struct holder {
+ };
+
+ template<template<typename Arg> class Func, typename Arg0, typename... ArgsRest>
+ static constexpr auto map_args_impl(holder<Arg0>, holder<ArgsRest>...) {
+ // One does not simply call MakeArray with 0 template arguments...
+ auto array = MakeArray(
+ Func<Args>{}()...
+ );
+
+ return array;
+ }
+
+ template<template<typename Arg> class Func>
+ static constexpr auto map_args_impl() {
+ // This overload provides support for MakeArray() with 0 arguments.
+ using ComponentType = decltype(Func<void>{}());
+
+ return ConstexprArray<ComponentType, /*size*/0u>{};
+ }
+};
+
+// Apply ReifiedJniTypeTrait::Reify<T> for every function component type.
+template<typename T>
+struct ReifyJniTypeMetafunction {
+ constexpr ReifiedJniTypeTrait operator()() const {
+ auto res = ReifiedJniTypeTrait::Reify<T>();
+ X_ASSERT(res.native_kind != kNotJni);
+ return res;
+ }
+};
+
+// Ret(Args...) where every component is a ReifiedJniTypeTrait.
+template<size_t kMaxSize>
+using ReifiedJniSignature = FunctionSignatureDescriptor<ReifiedJniTypeTrait,
+ kMaxSize>;
+
+// 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 <NativeKind native_kind,
+ typename T,
+ T fn,
+ size_t kMaxSize = FunctionTypeMetafunction<T, fn>::count>
+constexpr ConstexprOptional<ReifiedJniSignature<kMaxSize>>
+MaybeMakeReifiedJniSignature() {
+ if (!IsValidJniFunctionType<native_kind, T, fn>()) {
+ PARSE_FAILURE("The function signature has one or more types incompatible with JNI.");
+ }
+
+ ReifiedJniTypeTrait return_jni_trait =
+ FunctionTypeMetafunction<T,
+ fn>::template map_return<ReifyJniTypeMetafunction>();
+
+ constexpr size_t
+ kSkipArgumentPrefix = (native_kind != kCriticalNative) ? 2u : 0u;
+ ConstexprVector<ReifiedJniTypeTrait, kMaxSize> args;
+ auto args_list =
+ FunctionTypeMetafunction<T, fn>::template map_args<ReifyJniTypeMetafunction>();
+ 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<ReifiedJniTypeTrait> 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<NativeKind native_kind, typename T, T fn, size_t kMaxSize>
+constexpr bool
+MatchJniDescriptorWithFunctionType(ConstexprStringView user_function_descriptor) {
+ constexpr size_t kReifiedMaxSize = FunctionTypeMetafunction<T, fn>::count;
+
+ ConstexprOptional<ReifiedJniSignature<kReifiedMaxSize>>
+ reified_signature_opt =
+ MaybeMakeReifiedJniSignature<native_kind, T, fn>();
+ if (!reified_signature_opt) {
+ // Assertion handling done by MaybeMakeReifiedJniSignature.
+ return false;
+ }
+
+ ConstexprOptional<JniSignatureDescriptor<kMaxSize>> user_jni_sig_desc_opt =
+ ParseSignatureAsList<kMaxSize>(user_function_descriptor);
+
+ if (!user_jni_sig_desc_opt) {
+ // Assertion handling done by ParseSignatureAsList.
+ return false;
+ }
+
+ ReifiedJniSignature<kReifiedMaxSize>
+ reified_signature = reified_signature_opt.value();
+ JniSignatureDescriptor<kMaxSize>
+ 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<NativeKind native_kind, typename T, T fn>
+struct InferJniDescriptor {
+ static constexpr size_t kMaxSize = FunctionTypeMetafunction<T, fn>::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<JniSignatureDescriptor<kMaxSize>> FromFunctionType() {
+ constexpr size_t kReifiedMaxSize = kMaxSize;
+ ConstexprOptional<ReifiedJniSignature<kReifiedMaxSize>>
+ reified_signature_opt =
+ MaybeMakeReifiedJniSignature<native_kind, T, fn>();
+ if (!reified_signature_opt) {
+ // Assertion handling done by MaybeMakeReifiedJniSignature.
+ return NullConstexprOptional{};
+ }
+
+ ReifiedJniSignature<kReifiedMaxSize>
+ reified_signature = reified_signature_opt.value();
+
+ JniSignatureDescriptor<kReifiedMaxSize> 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<JniSignatureDescriptor<kMaxSize>>
+ signature_descriptor_opt =
+ FromFunctionType();
+ if (!signature_descriptor_opt) {
+ // Assertion handling done by FromFunctionType.
+ return 0u;
+ }
+
+ JniSignatureDescriptor<kMaxSize> 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<char,
+ kMaxStringSize + 1>;
+
+ static constexpr bool kAllowPartialStrings = false;
+
+ // 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<JniSignatureDescriptor<kMaxSize>>
+ signature_descriptor_opt =
+ FromFunctionType();
+ if (!signature_descriptor_opt.has_value()) {
+ // Assertion handling done by FromFunctionType.
+ c_str[0] = '\0';
+ return c_str;
+ }
+
+ JniSignatureDescriptor<kMaxSize> 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) {
+ if (kAllowPartialStrings && pos >= kMaxStringSize) {
+ break;
+ }
+ c_str[pos++] = longy[i];
+ }
+ }
+
+ if (!kAllowPartialStrings || pos < kMaxStringSize) {
+ c_str[pos++] = ')';
+ }
+
+ // Copy return descriptor.
+ ConstexprStringView longy = signature_descriptor.ret.longy;
+ for (size_t i = 0; i < longy.size(); ++i) {
+ if (kAllowPartialStrings && pos >= kMaxStringSize) {
+ break;
+ }
+ c_str[pos++] = longy[i];
+ }
+
+ if (!kAllowPartialStrings) {
+ 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; \
+ static_assert( \
+ MatchJniDescriptorWithFunctionType<native_kind, \
+ decltype(fn), \
+ fn, \
+ sizeof(signature_)>(signature_),\
+ "JNI signature doesn't match C++ function type."); \
+ /* Suppress implicit cast warnings by explicitly casting. */ \
+ return JNINativeMethod { \
+ const_cast<decltype(JNINativeMethod::name)>(name_), \
+ const_cast<decltype(JNINativeMethod::signature)>(signature_), \
+ reinterpret_cast<void*>(&fn)}; \
+ })()
+
+// Expression to return JNINativeMethod, infers signature from fn.
+#define MAKE_INFERRED_JNI_NATIVE_METHOD(native_kind, name_, fn) \
+ ([]() { \
+ using namespace nativehelper::detail; \
+ /* Suppress implicit cast warnings by explicitly casting. */ \
+ return JNINativeMethod { \
+ const_cast<decltype(JNINativeMethod::name)>(name_), \
+ const_cast<decltype(JNINativeMethod::signature)>( \
+ InferJniDescriptor<native_kind, \
+ decltype(fn), \
+ fn>::GetStringAtRuntime()), \
+ reinterpret_cast<void*>(&fn)}; \
+ })()
+
+} // namespace detail
+} // namespace nativehelper
+
diff --git a/platform_include/nativehelper/jni_macros.h b/platform_include/nativehelper/jni_macros.h
index b628bf7..1276038 100644
--- a/platform_include/nativehelper/jni_macros.h
+++ b/platform_include/nativehelper/jni_macros.h
@@ -14,71 +14,276 @@
* limitations under the License.
*/
-/*
- * JNI helper macros.
+/**
+ * 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.
*
- * Only intended to be used in the platform.
+ * 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.
*/
#ifndef NATIVEHELPER_JNI_MACROS_H
#define NATIVEHELPER_JNI_MACROS_H
+// 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).
-#ifndef NATIVE_METHOD
-#define NATIVE_METHOD(className, functionName, signature) \
- { #functionName, \
- signature, \
- _NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## functionName) \
- }
+//
+// The Java Language code must be annotated with one of (none,@FastNative,@CriticalNative)
+// for the (none,FAST_,CRITICAL_) variants of these macros.
+
+#ifdef NATIVE_METHOD // Remove definition from JniConstants.h
+#undef NATIVE_METHOD
#endif
-// Intended to construct a JNINativeMethod (when the C name doesn't match the Java name).
-// (Assumes the C name is the ClassName_Identifier).
-#ifndef OVERLOADED_NATIVE_METHOD
+#define NATIVE_METHOD(className, functionName, signature) \
+ MAKE_JNI_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName)
+
#define OVERLOADED_NATIVE_METHOD(className, functionName, signature, identifier) \
- { #functionName, \
- signature, \
- _NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## identifier) \
- }
-#endif
+ MAKE_JNI_NATIVE_METHOD(#functionName, signature, className ## _ ## identifier)
+
+#define NATIVE_METHOD_AUTOSIG(className, functionName) \
+ MAKE_JNI_NATIVE_METHOD_AUTOSIG(#functionName, className ## _ ## functionName)
-// Used for methods that are annotated with @FastNative on the managed side.
-// See NATIVE_METHOD for usage.
-#ifndef FAST_NATIVE_METHOD
#define FAST_NATIVE_METHOD(className, functionName, signature) \
- { #functionName, \
- signature, \
- _NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## functionName) \
- }
-#endif
+ MAKE_JNI_FAST_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName)
-// Used for methods that are annotated with @FastNative on the managed side,
-// and when the C-name doesn't match the Java-name.
-//
-// See OVERLOADED_NATIVE_METHOD for usage.
-#ifndef OVERLOADED_FAST_NATIVE_METHOD
#define OVERLOADED_FAST_NATIVE_METHOD(className, functionName, signature, identifier) \
- { #functionName, \
- signature, \
- _NATIVEHELPER_JNI_MACRO_CAST(void*) (className ## _ ## identifier) \
- }
-#endif
+ 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(to) \
- reinterpret_cast<to>
+#define _NATIVEHELPER_JNI_MACRO_CAST(which_cast, to) \
+ which_cast<to>
#else
-#define _NATIVEHELPER_JNI_MACRO_CAST(to) \
+#define _NATIVEHELPER_JNI_MACRO_CAST(which_cast, to) \
(to)
#endif
-#endif
+#endif // NATIVEHELPER_JNI_MACROS_H
diff --git a/tests/Android.bp b/tests/Android.bp
index e6cbf5c..a5bdee9 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -8,3 +8,53 @@ cc_test {
cflags: ["-Wall", "-Werror"],
shared_libs: ["libnativehelper"],
}
+
+cc_test {
+ name: "JniSafeRegisterNativeMethods_test",
+ host_supported: true,
+ srcs: ["JniSafeRegisterNativeMethods_test.cpp"],
+
+ 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 of wrong unused annotations.
+ "-Wused-but-marked-unused",
+
+ // Enable warning for deprecated language features.
+ "-Wdeprecated",
+
+ // 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",
+ ],
+
+ shared_libs: ["libnativehelper"],
+}
diff --git a/tests/JniSafeRegisterNativeMethods_test.cpp b/tests/JniSafeRegisterNativeMethods_test.cpp
new file mode 100644
index 0000000..5a1f8e9
--- /dev/null
+++ b/tests/JniSafeRegisterNativeMethods_test.cpp
@@ -0,0 +1,882 @@
+/*
+ * 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-dynamic-exception-spec"
+#pragma clang diagnostic ignored "-Wdeprecated"
+#include <gtest/gtest.h>
+#pragma clang diagnostic pop
+#include <sstream>
+
+#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<jtype>::type_descriptor[]; \
+constexpr char nativehelper::detail::jni_type_trait<jtype>::type_name[];
+
+DEFINE_JNI_TYPE_TRAIT(STORAGE_FN_FOR_JNI_TRAITS)
+
+template <typename T>
+auto stringify_helper(const T& val) -> decltype(std::stringstream().str()) { // suppress incorrect warnings about compiler not support 'auto'
+ 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; \
+
+ // 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);
+ }
+
+ // 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<sizeof(str)>(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<JniSignatureDescriptor<sizeof(jni_descriptor)>>{},\
+ ParseSignatureAsList<sizeof(jni_descriptor)>(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;
+ 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<void>(kNormalNative, kReturnPosition)));
+ EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(true,(is_valid_jni_argument_type<kNormalNative, /*pos*/0u, JNIEnv*>::value));
+ EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(true,(is_valid_jni_argument_type<kNormalNative, /*pos*/1u, jobject>::value));
+ EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(true,(is_valid_jni_argument_type<kNormalNative, /*pos*/1u, jclass>::value));
+ EXPECT_IS_VALID_JNI_ARGUMENT_TYPE(false,(is_valid_jni_argument_type<kNormalNative, /*pos*/1u, jstring>::value));
+}
+
+struct TestReturnAnything {
+ template <typename T>
+ operator T() const {
+ 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_() {}
+ 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;
+ // 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<native_kind, decltype(func), (func)>())
+#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_EITHER_JNI_FUNCTION_TYPE(func) \
+ do { \
+ EXPECT_TRUE(IS_VALID_CRITICAL_JNI_FUNCTION_TYPE(func)); \
+ EXPECT_TRUE(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;
+ {
+ constexpr auto cvrt = MaybeMakeReifiedJniSignature<kCriticalNative,
+ decltype(TestJni::v_i),
+ TestJni::v_i>();
+ EXPECT_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<kNormalNative,
+ decltype(TestJni::v_i),
+ TestJni::v_i>();
+ EXPECT_FALSE(cvrt.has_value());
+ }
+
+ {
+ constexpr auto cvrt = MaybeMakeReifiedJniSignature<kNormalNative,
+ decltype(TestJni::normal_agi),
+ TestJni::normal_agi>();
+ EXPECT_TRUE(cvrt.has_value());
+ EXPECT_EQ(2u, cvrt->args.size());
+ EXPECT_STRINGIFY_EQ("args={jdoubleArray,jobject}, ret=jdoubleArray", cvrt.value());
+ }
+
+ {
+ constexpr auto cvrt = MaybeMakeReifiedJniSignature<kCriticalNative,
+ decltype(TestJni::critical_ac2),
+ TestJni::critical_ac2>();
+ EXPECT_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 <typename T>
+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;
+ using traits_for_int_ret = FunctionTypeMetafunction<FN_ARGS_PAIR(test_function_traits::int_returning_function)>;
+ int applied = traits_for_int_ret::map_return<apply_return_type>();
+ EXPECT_EQ(1, applied);
+
+ auto arr = traits_for_int_ret::map_args<apply_return_type>();
+ 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 <typename T, size_t kMaxSize>
+constexpr size_t SumUpVector(const nativehelper::detail::ConstexprVector<T, kMaxSize>& vec) {
+ size_t s = 0;
+ for (const T& elem : vec) {
+ s += static_cast<size_t>(GetTestValue(elem));
+ }
+ return s;
+}
+
+template <typename T>
+constexpr auto make_test_int_vector() {
+ using namespace nativehelper::detail;
+ ConstexprVector<T, 5> 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, ConstexprVector) {
+ using namespace nativehelper::detail;
+ {
+ constexpr ConstexprVector<IntHolder, 5> vec_int = make_test_int_vector<IntHolder>();
+ constexpr size_t the_sum = SumUpVector(vec_int);
+ EXPECT_EQ(15u, the_sum);
+ }
+
+ {
+ constexpr ConstexprVector<int, 5> vec_int = make_test_int_vector<int>();
+ 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<derived>()); \
+ (void)res; \
+ EXPECT_ ## cond(CompareJniDescriptorNodeErased(MakeNode(user_desc), ReifiedJniTypeTrait::Reify<derived>())); \
+ } 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;
+ 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<type>(), 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;
+ 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;
+ // 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;
+ {
+ using Infer_v_eolib_t = InferJniDescriptor<kNormalNative,
+ decltype(TestJni::v_eolib),
+ TestJni::v_eolib>;
+ 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<kNormalNative,
+ decltype(TestJni::s_eolAibA),
+ TestJni::s_eolAibA>;
+ 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) {}
+
+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);
+}