diff options
author | android-build-team Robot <android-build-team-robot@google.com> | 2018-02-28 08:21:30 +0000 |
---|---|---|
committer | android-build-team Robot <android-build-team-robot@google.com> | 2018-02-28 08:21:30 +0000 |
commit | afd3a633ab30cb17758543b4bbf2e8165812ce7c (patch) | |
tree | c65bfe54620ab04d2fbcfea059988c01f994de54 | |
parent | c4e2b1fba922bf6f521def3d316c741e07b02a2d (diff) | |
parent | 6b56b692bc74a19f293e378c0dffea15460f4870 (diff) | |
download | libnativehelper-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.h | 1437 | ||||
-rw-r--r-- | platform_include/nativehelper/jni_macros.h | 283 | ||||
-rw-r--r-- | tests/Android.bp | 50 | ||||
-rw-r--r-- | tests/JniSafeRegisterNativeMethods_test.cpp | 882 |
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); +} |