blob: b4ea68eff71fb788610ea97a3c756f3db898973b [file] [log] [blame]
/*
* 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