/* * 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 <exception> #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); } // No-arg constructor: Create empty view. constexpr ConstexprStringView() : _array(""), _size(0u) {} constexpr size_t size() const { return _size; } constexpr bool empty() const { return size() == 0u; } constexpr char operator[](size_t i) const { X_ASSERT(i <= size()); return _array[i]; } // Create substring from this[start..start+len). constexpr ConstexprStringView substr(size_t start, size_t len) const { X_ASSERT(start <= size()); X_ASSERT(len <= size() - start); return ConstexprStringView(&_array[start], len); } // Create maximum length substring that begins at 'start'. constexpr ConstexprStringView substr(size_t start) const { X_ASSERT(start <= size()); return substr(start, size() - start); } using const_iterator = const char*; constexpr const_iterator begin() const { return &_array[0]; } constexpr const_iterator end() const { return &_array[size()]; } private: const char* _array; // Never-null for simplicity. size_t _size; }; constexpr bool operator==(const ConstexprStringView& lhs, const ConstexprStringView& rhs) { if (lhs.size() != rhs.size()) { return false; } for (size_t i = 0; i < lhs.size(); ++i) { if (lhs[i] != rhs[i]) { return false; } } return true; } constexpr bool operator!=(const ConstexprStringView& lhs, const ConstexprStringView& rhs) { return !(lhs == rhs); } inline std::ostream& operator<<(std::ostream& os, const ConstexprStringView& str) { for (char c : str) { os << c; } return os; } constexpr bool IsValidJniDescriptorStart(char shorty) { constexpr char kValidJniStarts[] = {'V', 'Z', 'B', 'C', 'S', 'I', 'J', 'F', 'D', 'L', '[', '(', ')'}; for (char c : kValidJniStarts) { if (c == shorty) { return true; } } return false; } // A constexpr "vector" that supports storing a variable amount of Ts // in an array-like interface. // // An up-front kMaxSize must be given since constexpr does not support // dynamic allocations. template<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 /*T&*/ auto& operator*() { // Use 'auto' here since using 'T' is incorrect with const_iterator. return ptr->_value; } constexpr const /*T&*/ auto& operator*() const { // Use 'auto' here for consistency with above. return ptr->_value; } constexpr bool operator==(const VectorIterator& other) const { return ptr == other.ptr; } constexpr bool operator!=(const VectorIterator& other) const { return !(*this == other); } }; // Do not require that T is default-constructible by using a union. struct MaybeElement { union { T _value; }; }; public: using iterator = VectorIterator<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(google-explicit-constructor) X_ASSERT(!longy.empty()); } constexpr JniDescriptorNode() : longy() {} constexpr char shorty() { // Must be initialized with the non-default constructor. X_ASSERT(!longy.empty()); return longy[0]; } }; inline std::ostream& operator<<(std::ostream& os, const JniDescriptorNode& node) { os << node.longy; return os; } // Equivalent of C++17 std::optional. // // An optional is essentially a type safe // union { // void Nothing, // T Some; // }; // template<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) // NOLINT(google-explicit-constructor) : _has_value(true), _value(value) { } constexpr explicit operator bool() const { return _has_value; } constexpr bool has_value() const { return _has_value; } constexpr const T& value() const { X_ASSERT(has_value()); return _value; } constexpr const T* operator->() const { return &(value()); } constexpr const T& operator*() const { return value(); } private: bool _has_value; // The "Nothing" is likely unnecessary but improves readability. struct Nothing {}; union { Nothing _nothing; T _value; }; }; template<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 { // NOLINT(google-explicit-constructor) 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(IsValidJniDescriptorStart(c)); enum State { kSingleCharacter, kArray, kObject }; State state = kSingleCharacter; // Parse the first character to figure out if we should parse the rest. switch (c) { case '!': { constexpr bool fast_jni_is_deprecated = false; PARSE_ASSERT(fast_jni_is_deprecated); break; } case 'V': if (!allow_void) { constexpr bool void_type_descriptor_only_allowed_in_return_type = false; PARSE_ASSERT(void_type_descriptor_only_allowed_in_return_type); } [[clang::fallthrough]]; case 'Z': case 'B': case 'C': case 'S': case 'I': case 'J': case 'F': case 'D': token = single_type.substr(/*start*/0u, /*len*/1u); break; case 'L': state = kObject; break; case '[': state = kArray; break; default: { // See JNI Chapter 3: Type Signatures. PARSE_FAILURE("Expected a valid type descriptor character."); return kUnreachable; } } // Possibly parse an arbitary-long remainder substring. switch (state) { case kSingleCharacter: return {{token, remainder}}; case kArray: { // Recursively parse the array component, as it's just any non-void type descriptor. ConstexprOptional<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) { switch (single_type[i]) { case ')': case '(': case '[': PARSE_FAILURE("Object identifiers cannot have ()[ in them."); break; } if (single_type[i] == ';') { semicolon_len = i + 1; found_semicolon = true; break; } } PARSE_ASSERT(found_semicolon); token = single_type.substr(/*start*/0u, semicolon_len); remainder = single_type.substr(/*start*/semicolon_len); bool class_name_is_empty = token.size() <= 2u; // e.g. "L;" PARSE_ASSERT(!class_name_is_empty); return {{token, remainder}}; } default: X_ASSERT(false); } X_ASSERT(false); return kUnreachable; } // Abstract data type to represent container for Ret(Args,...). template<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 descriptors cannot possibly exceed the number of characters // in the JNI string literal. We leverage this to give an upper bound of the strlen. // This is a bit wasteful but in constexpr there *must* be a fixed upper size for data structures. ConstexprVector<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& rjtt) { // os << "ReifiedJniTypeTrait<" << rjft.type_name << ">"; os << rjtt.type_name; return os; } // Template specialization for any JNI typedefs. #define JNI_TYPE_TRAIT(jtype, the_type_descriptor, the_native_kind, the_type_finality, the_position) \ template <> \ struct jni_type_trait< jtype > { \ static constexpr NativeKind native_kind = the_native_kind; \ static constexpr const char type_descriptor[] = the_type_descriptor; \ static constexpr NativePositionAllowed position_allowed = the_position; \ static constexpr TypeFinal type_finality = the_type_finality; \ static constexpr const char type_name[] = #jtype; \ }; #define DEFINE_JNI_TYPE_TRAIT(TYPE_TRAIT_FN) \ TYPE_TRAIT_FN(jboolean, "Z", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jbyte, "B", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jchar, "C", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jshort, "S", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jint, "I", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jlong, "J", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jfloat, "F", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jdouble, "D", kCriticalNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jobject, "Ljava/lang/Object;", kFastNative, kNotFinal, kFirstOrLaterPosition) \ TYPE_TRAIT_FN(jclass, "Ljava/lang/Class;", kFastNative, kFinal, kFirstOrLaterPosition) \ TYPE_TRAIT_FN(jstring, "Ljava/lang/String;", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jarray, "Ljava/lang/Object;", kFastNative, kNotFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jobjectArray, "[Ljava/lang/Object;", kFastNative, kNotFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jbooleanArray, "[Z", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jbyteArray, "[B", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jcharArray, "[C", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jshortArray, "[S", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jintArray, "[I", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jlongArray, "[J", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jfloatArray, "[F", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jdoubleArray, "[D", kFastNative, kFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(jthrowable, "Ljava/lang/Throwable;", kFastNative, kNotFinal, kSecondOrLaterPosition) \ TYPE_TRAIT_FN(JNIEnv*, "", kNormalJniCallingConventionParameter, kFinal, kZerothPosition) \ TYPE_TRAIT_FN(void, "V", kCriticalNative, kFinal, kReturnPosition) \ DEFINE_JNI_TYPE_TRAIT(JNI_TYPE_TRAIT) // See ReifiedJniTypeTrait for documentation. constexpr ConstexprOptional<ReifiedJniTypeTrait> 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{}; } // Is this actual JNI position consistent with the expected position? constexpr bool IsValidJniParameterPosition(NativeKind native_kind, NativePositionAllowed position, NativePositionAllowed expected_position) { X_ASSERT(expected_position != kNotAnyPosition); if (native_kind == kCriticalNative) { // CriticalNatives ignore positions since the first 2 special // parameters are stripped. return true; } // Is this a return-only position? if (expected_position == kReturnPosition) { if (position != kReturnPosition) { // void can only be in the return position. return false; } // Don't do the other non-return position checks for a return-only position. return true; } // JNIEnv* can only be in the first spot. if (position == kZerothPosition && expected_position != kZerothPosition) { return false; // jobject, jclass can be 1st or anywhere afterwards. } else if (position == kFirstOrLaterPosition && expected_position != kFirstOrLaterPosition) { return false; // All other parameters must be in 2nd+ spot, or in the return type. } else if (position == kSecondOrLaterPosition || position == kReturnPosition) { if (expected_position != kFirstOrLaterPosition && expected_position != kSecondOrLaterPosition) { return false; } } return true; } // Check if a jni parameter type is valid given its position and native_kind. template <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. NativePositionAllowed expected_position = expected_trait::position_allowed; if (!IsValidJniParameterPosition(native_kind, position, expected_position)) { return false; } // Ensure the type appropriate is for the native kind. if (expected_native_kind == kNormalJniCallingConventionParameter) { // It's always wrong to use a JNIEnv* anywhere but the 0th spot. if (native_kind == kCriticalNative) { // CriticalNative does not allow using a JNIEnv*. return false; } return true; // OK: JniEnv* used in 0th position. } else if (expected_native_kind == kCriticalNative) { // CriticalNative arguments are always valid JNI types anywhere used. return true; } else if (native_kind == kCriticalNative) { // The expected_native_kind was non-critical but we are in a critical context. // Illegal type. return false; } // Everything else is fine, e.g. fast/normal native + fast/normal native parameters. return true; } // Is there sufficient number of parameters given the kind of JNI that it is? constexpr bool IsJniParameterCountValid(NativeKind native_kind, size_t count) { if (native_kind == kNormalNative || native_kind == kFastNative) { return count >= 2u; } else if (native_kind == kCriticalNative) { return true; } constexpr bool invalid_parameter = false; X_ASSERT(invalid_parameter); return false; } // Basic template interface. See below for partial specializations. // // Each instantiation will have a 'value' field that determines whether or not // all of the Args are valid JNI arguments given their native_kind. template<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>; // 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) { c_str[pos++] = longy[i]; } } c_str[pos++] = ')'; // Copy return descriptor. ConstexprStringView longy = signature_descriptor.ret.longy; for (size_t i = 0; i < longy.size(); ++i) { c_str[pos++] = longy[i]; } X_ASSERT(pos == kMaxStringSize); c_str[pos] = '\0'; return c_str; } // Turn a pure constexpr string into one that can be accessed at non-constexpr // time. Note that the 'static constexpr' storage must be in the scope of a // function (prior to C++17) to avoid linking errors. static const char* GetStringAtRuntime() { static constexpr ConstexprStringDescriptorType str = GetString(); return &str[0]; } }; // Expression to return JNINativeMethod, performs checking on signature+fn. #define MAKE_CHECKED_JNI_NATIVE_METHOD(native_kind, name_, signature_, fn) \ ([]() { \ using namespace nativehelper::detail; /* NOLINT(google-build-using-namespace) */ \ static_assert( \ MatchJniDescriptorWithFunctionType<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; /* NOLINT(google-build-using-namespace) */ \ /* 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