/*
 * Copyright (C) 2017 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.
 */

/**
 * Compile-time, zero-cost checking of JNI signatures against their C++ function type.
 * This can trigger compile-time assertions if any of the input is invalid:
 *     (a) The signature specified does not conform to the JNI function descriptor syntax.
 *     (b) The C++ function is itself an invalid JNI function (e.g. missing JNIEnv*, etc).
 *     (c) The descriptor does not match the C++ function (e.g. "()V" will not match jint(jint)).
 *
 * The fundamental macros are as following:
 *   MAKE_JNI_[FAST_|CRITICAL_]NATIVE_METHOD - Create a checked JNINativeMethod{name, sig, func}.
 *   MAKE_JNI_[FAST_|CRITICAL_]NATIVE_METHOD_AUTOSIG - Same as above, but infer the JNI signature.
 *
 * Usage examples:
 *     // path/to/package/KlassName.java
 *     class KlassName {
 *         native jobject normal(int x);
 *         @FastNative native jobject fast(int x);
 *         @CriticalNative native int critical(long ptr);
 *     }
 *     // path_to_package_KlassName.cpp
 *     jobject KlassName_normal(JNIEnv*,jobject,jint) {...}
 *     jobject KlassName_fast(JNIEnv*,jobject,jint) {...}
 *     jint KlassName_critical(jlong) {...}
 *
 *     // Manually specify each signature:
 *     JNINativeMethod[] gMethods = {
 *         MAKE_JNI_NATIVE_METHOD("normal", "(I)Ljava/lang/Object;", KlassName_normal),
 *         MAKE_JNI_FAST_NATIVE_METHOD("fast", "(I)Ljava/lang/Object;", KlassName_fast),
 *         MAKE_JNI_CRITICAL_NATIVE_METHOD("critical", "(Z)I", KlassName_critical),
 *     };
 *
 *     // Automatically infer the signature:
 *     JNINativeMethod[] gMethodsAutomaticSignature = {
 *         MAKE_JNI_NATIVE_METHOD_AUTOSIG("normal", KlassName_normal),
 *         MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG("fast", KlassName_fast),
 *         MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG("critical", KlassName_critical),
 *     };
 *
 *     // and then call JNIEnv::RegisterNatives with gMethods as usual.
 *
 * For convenience the following macros are defined:
 *   [FAST_|CRITICAL_]NATIVE_METHOD - Return JNINativeMethod for class, func name, and signature.
 *   OVERLOADED_[FAST_|CRITICAL_]NATIVE_METHOD - Same as above but allows a separate func identifier.
 *   [FAST_|CRITICAL_]NATIVE_METHOD_AUTOSIG - Return JNINativeMethod, sig inferred from function.
 *
 * The FAST_ prefix corresponds to functions annotated with @FastNative,
 * and the CRITICAL_ prefix corresponds to functions annotated with @CriticalNative.
 * See dalvik.annotation.optimization.CriticalNative for more details.
 *
 * =======================================
 * Checking rules
 * =======================================
 *
 * ---------------------------------------
 * JNI descriptor syntax for functions
 *
 * Refer to "Chapter 3: JNI Types and Data Structures" of the JNI specification
 * under the subsection "Type Signatures" table entry "method type".
 *
 * JNI signatures not conforming to the above syntax are rejected.
 * ---------------------------------------
 * C++ function types
 *
 * A normal or @FastNative JNI function type must be of the form
 *
 *     ReturnType (JNIEnv*, jclass|jobject, [ArgTypes...]) {}
 *
 * A @CriticalNative JNI function type:
 *
 *   must be of the form...  ReturnType ([ArgTypes...]){}
 *   and must not contain any Reference Types.
 *
 * Refer to "Chapter 3: JNI Types and Data Structures" of the JNI specification
 * under the subsection "Primitive Types" and "Reference Types" for the list
 * of valid argument/return types.
 *
 * C++ function types not conforming to the above requirements are rejected.
 * ---------------------------------------
 * Matching of C++ function type against JNI function descriptor.
 *
 * Assuming all of the above conditions are met for signature and C++ type validity,
 * then matching between the signature and the type validity can occur:
 *
 * Given a signature (Args...)Ret and the
 *     C++ function type of the form "CRet fn(JNIEnv*, jclass|jobject, CArgs...)",
 *     or for @CriticalNative of the form "CRet fn(CArgs...)"
 *
 * The number of Args... and the number of CArgs... must be equal.
 *
 * If so, attempt to match every component from the signature and function type
 * against each other:
 *
 * ReturnType:
 *     V <-> void
 *     ArgumentType
 *
 * ArgumentType:
 *     PrimitiveType
 *     ReferenceType  [except for @CriticalNative]
 *
 * PrimitiveType:
 *     Z <-> jboolean
 *     B <-> jbyte
 *     C <-> jchar
 *     S <-> jshort
 *     I <-> jint
 *     J <-> jlong
 *     F <-> jfloat
 *     D <-> jdouble
 *
 * ReferenceType:
 *     Ljava/lang/String;    <-> jstring
 *     Ljava/lang/Class;     <-> jclass
 *     L*;                   <-  jobject
 *     Ljava/lang/Throwable;  -> jthrowable
 *     L*;                   <-  jthrowable
 *     [ PrimitiveType       <-> ${CPrimitiveType}Array
 *     [ ReferenceType       <-> jobjectArray
 *     [*                    <-  jarray
 *
 * Wherein <-> represents a strong match (if the left or right pattern occurs,
 * then left must match right, otherwise matching fails). <- and -> represent
 * weak matches (that is, other match rules can be still attempted).
 *
 * Sidenote: Whilst a jobject could also represent a jclass, jstring, etc,
 * the stricter approach is taken: the most exact C++ type must be used.
 */

#ifndef NATIVEHELPER_JNI_MACROS_H
#define NATIVEHELPER_JNI_MACROS_H

// The below basic macros do not perform automatic stringification,
// invoked e.g. as MAKE_JNI_NATIVE_METHOD("some_name", "()V", void_fn)

// An expression that evaluates to JNINativeMethod { name, signature, function },
//   and applies the above compile-time checking for signature+function.
// The equivalent Java Language code must not be annotated with @FastNative/@CriticalNative.
#define MAKE_JNI_NATIVE_METHOD(name, signature, function)                      \
  _NATIVEHELPER_JNI_MAKE_METHOD(kNormalNative, name, signature, function)

// An expression that evaluates to JNINativeMethod { name, signature, function },
//   and applies the above compile-time checking for signature+function.
// The equivalent Java Language code must be annotated with @FastNative.
#define MAKE_JNI_FAST_NATIVE_METHOD(name, signature, function)                 \
  _NATIVEHELPER_JNI_MAKE_METHOD(kFastNative, name, signature, function)

// An expression that evaluates to JNINativeMethod { name, signature, function },
//   and applies the above compile-time checking for signature+function.
// The equivalent Java Language code must be annotated with @CriticalNative.
#define MAKE_JNI_CRITICAL_NATIVE_METHOD(name, signature, function)             \
  _NATIVEHELPER_JNI_MAKE_METHOD(kCriticalNative, name, signature, function)

// Automatically signature-inferencing macros are also available,
// which also checks the C++ function types for validity:

// An expression that evalutes to JNINativeMethod { name, infersig(function), function) }
// by inferring the signature at compile-time. Only works when the C++ function type
// corresponds to one unambigous JNI parameter (e.g. 'jintArray' -> '[I' but 'jobject' -> ???).
//
// The equivalent Java Language code must not be annotated with @FastNative/@CriticalNative.
#define MAKE_JNI_NATIVE_METHOD_AUTOSIG(name, function)                         \
  _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kNormalNative, name, function)

// An expression that evalutes to JNINativeMethod { name, infersig(function), function) }
// by inferring the signature at compile-time. Only works when the C++ function type
// corresponds to one unambigous JNI parameter (e.g. 'jintArray' -> '[I' but 'jobject' -> ???).
//
// The equivalent Java Language code must be annotated with @FastNative.
#define MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG(name, function)                    \
  _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kFastNative, name, function)

// An expression that evalutes to JNINativeMethod { name, infersig(function), function) }
// by inferring the signature at compile-time.
//
// The equivalent Java Language code must be annotated with @CriticalNative.
#define MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG(name, function)                 \
  _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kCriticalNative, name, function)

// Convenience macros when the functions follow the naming convention:
//       .java file           .cpp file
//       JavaLanguageName <-> ${ClassName}_${JavaLanguageName}
//
// Stringification is done automatically, invoked as:
//   NATIVE_[FAST_|CRITICAL]_METHOD(ClassName, JavaLanguageName, Signature)
//
// Intended to construct a JNINativeMethod.
//   (Assumes the C name is the ClassName_JavaMethodName).
//
// The Java Language code must be annotated with one of (none,@FastNative,@CriticalNative)
// for the (none,FAST_,CRITICAL_) variants of these macros.

#define NATIVE_METHOD(className, functionName, signature)                \
  MAKE_JNI_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName)

#define OVERLOADED_NATIVE_METHOD(className, functionName, signature, identifier) \
  MAKE_JNI_NATIVE_METHOD(#functionName, signature, className ## _ ## identifier)

#define NATIVE_METHOD_AUTOSIG(className, functionName) \
  MAKE_JNI_NATIVE_METHOD_AUTOSIG(#functionName, className ## _ ## functionName)

#define FAST_NATIVE_METHOD(className, functionName, signature)           \
  MAKE_JNI_FAST_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName)

#define OVERLOADED_FAST_NATIVE_METHOD(className, functionName, signature, identifier) \
  MAKE_JNI_FAST_NATIVE_METHOD(#functionName, signature, className ## _ ## identifier)

#define FAST_NATIVE_METHOD_AUTOSIG(className, functionName) \
  MAKE_JNI_FAST_NATIVE_METHOD_AUTOSIG(#functionName, className ## _ ## functionName)

#define CRITICAL_NATIVE_METHOD(className, functionName, signature)           \
  MAKE_JNI_CRITICAL_NATIVE_METHOD(#functionName, signature, className ## _ ## functionName)

#define OVERLOADED_CRITICAL_NATIVE_METHOD(className, functionName, signature, identifier) \
  MAKE_JNI_CRITICAL_NATIVE_METHOD(#functionName, signature, className ## _ ## identifier)

#define CRITICAL_NATIVE_METHOD_AUTOSIG(className, functionName) \
  MAKE_JNI_CRITICAL_NATIVE_METHOD_AUTOSIG(#functionName, className ## _ ## functionName)

////////////////////////////////////////////////////////
//                IMPLEMENTATION ONLY.
//                DO NOT USE DIRECTLY.
////////////////////////////////////////////////////////

#if defined(__cplusplus) && __cplusplus >= 201402L
#include "nativehelper/detail/signature_checker.h"  // for MAKE_CHECKED_JNI_NATIVE_METHOD
#endif

// Expands to an expression whose type is JNINativeMethod.
// This is for older versions of C++ or C, so it has no compile-time checking.
#define _NATIVEHELPER_JNI_MAKE_METHOD_OLD(kind, name, sig, fn)     \
  (                                                                \
    (JNINativeMethod) {                                            \
        (name),                                                    \
        (sig),                                                     \
        _NATIVEHELPER_JNI_MACRO_CAST(reinterpret_cast, void *)(fn) \
    }                                                              \
  )

// C++14 or better, use compile-time checking.
#if defined(__cplusplus) && __cplusplus >= 201402L
// Expands to a compound expression whose type is JNINativeMethod.
#define _NATIVEHELPER_JNI_MAKE_METHOD(kind, name, sig, fn) \
  MAKE_CHECKED_JNI_NATIVE_METHOD(kind, name, sig, fn)

// Expands to a compound expression whose type is JNINativeMethod.
#define _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kind, name, function) \
  MAKE_INFERRED_JNI_NATIVE_METHOD(kind, name, function)

#else
// Older versions of C++ or C code get the regular macro that's unchecked.
// Expands to a compound expression whose type is JNINativeMethod.
#define _NATIVEHELPER_JNI_MAKE_METHOD(kind, name, sig, fn)         \
  _NATIVEHELPER_JNI_MAKE_METHOD_OLD(kind, name, sig, fn)

// Need C++14 or newer to use the AUTOSIG macros.
#define _NATIVEHELPER_JNI_MAKE_METHOD_AUTOSIG(kind, name, function) \
  static_assert(false, "Cannot infer JNI signatures prior to C++14 for function " #function);

#endif  // C++14 check

// C-style cast for C, C++-style cast for C++ to avoid warnings/errors.
#if defined(__cplusplus)
#define _NATIVEHELPER_JNI_MACRO_CAST(which_cast, to) \
    which_cast<to>
#else
#define _NATIVEHELPER_JNI_MACRO_CAST(which_cast, to) \
    (to)
#endif

#endif  // NATIVEHELPER_JNI_MACROS_H
