1442 lines
49 KiB
C++
1442 lines
49 KiB
C++
/*
|
|
* 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);
|
|
}
|
|
|
|
// 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
|
|
|