Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Android] Fix crash in Java8 Optional handling #30071

Merged
merged 1 commit into from
Oct 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 71 additions & 37 deletions src/lib/support/JniReferences.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <lib/support/CodeUtils.h>
#include <lib/support/JniReferences.h>
#include <lib/support/JniTypeWrappers.h>
#include <string>

namespace chip {

Expand All @@ -35,7 +36,7 @@ void JniReferences::SetJavaVm(JavaVM * jvm, const char * clsType)
JNIEnv * env = GetEnvForCurrentThread();
// Any chip.devicecontroller.* class will work here - just need something to call getClassLoader() on.
jclass chipClass = env->FindClass(clsType);
VerifyOrReturn(chipClass != nullptr, ChipLogError(Support, "clsType can not found"));
VerifyOrReturn(chipClass != nullptr, ChipLogError(Support, "clsType can not be found"));

jclass classClass = env->FindClass("java/lang/Class");
jclass classLoaderClass = env->FindClass("java/lang/ClassLoader");
Expand All @@ -47,6 +48,36 @@ void JniReferences::SetJavaVm(JavaVM * jvm, const char * clsType)
chip::JniReferences::GetInstance().GetClassRef(env, "java/util/List", mListClass);
chip::JniReferences::GetInstance().GetClassRef(env, "java/util/ArrayList", mArrayListClass);
chip::JniReferences::GetInstance().GetClassRef(env, "java/util/HashMap", mHashMapClass);

// Determine if the Java code has proper Java 8 support or not.
// The class and method chosen here are arbitrary, all we care about is
// looking up any method that has an Optional parameter.
jclass controllerParamsClass = env->FindClass("chip/devicecontroller/ControllerParams");
VerifyOrReturn(controllerParamsClass != nullptr, ChipLogError(Support, "controllerParamsClass is nullptr"));

jmethodID getCountryCodeMethod = env->GetMethodID(controllerParamsClass, "getCountryCode", "()Ljava/util/Optional;");
if (getCountryCodeMethod == nullptr)
{
// GetMethodID will have thrown an exception previously if it returned nullptr.
env->ExceptionClear();
VerifyOrReturn(env->GetMethodID(controllerParamsClass, "getCountryCode", "()Lj$/util/Optional;") != nullptr,
ChipLogError(Support, "Method getCountryCode can not be found"));
use_java8_optional = false;
}
else
{
use_java8_optional = true;
}

if (use_java8_optional)
{
chip::JniReferences::GetInstance().GetClassRef(env, "java/util/Optional", mOptionalClass);
}
else
{
chip::JniReferences::GetInstance().GetClassRef(env, "j$/util/Optional", mOptionalClass);
}
VerifyOrReturn(mOptionalClass != nullptr, ChipLogError(Support, "mOptionalClass is nullptr"));
}

JNIEnv * JniReferences::GetEnvForCurrentThread()
Expand Down Expand Up @@ -90,11 +121,11 @@ CHIP_ERROR JniReferences::GetLocalClassRef(JNIEnv * env, const char * clsType, j
{
jclass cls = nullptr;

// Try `j$/util/Optional` when enabling Java8.
if (strcmp(clsType, "java/util/Optional") == 0)
// Try `j$/util/Optional` when enabling Java8. Check whether mOptionalClass
// is null because this method is used to originally set mOptionalClass.
if (mOptionalClass != nullptr && (strcmp(clsType, "java/util/Optional") == 0 || strcmp(clsType, "j$/util/Optional") == 0))
{
cls = env->FindClass("j$/util/Optional");
env->ExceptionClear();
cls = mOptionalClass;
}

if (cls == nullptr)
Expand Down Expand Up @@ -129,6 +160,18 @@ CHIP_ERROR JniReferences::N2J_ByteArray(JNIEnv * env, const uint8_t * inArray, j
return err;
}

static std::string StrReplaceAll(const std::string & source, const std::string & from, const std::string & to)
{
std::string newString = source;
size_t pos = 0;
while ((pos = newString.find(from, pos)) != std::string::npos)
{
newString.replace(pos, from.length(), to);
pos += to.length();
}
return newString;
}

CHIP_ERROR JniReferences::FindMethod(JNIEnv * env, jobject object, const char * methodName, const char * methodSignature,
jmethodID * methodId)
{
Expand All @@ -146,21 +189,14 @@ CHIP_ERROR JniReferences::FindMethod(JNIEnv * env, jobject object, const char *
return CHIP_NO_ERROR;
}

// Try `j$` when enabling Java8.
std::string methodSignature_java8_str(methodSignature);
size_t pos = methodSignature_java8_str.find("java/util/Optional");
if (pos != std::string::npos)
std::string method_signature = methodSignature;
if (!use_java8_optional)
{
// Replace all "java/util/Optional" with "j$/util/Optional".
while (pos != std::string::npos)
{
methodSignature_java8_str.replace(pos, strlen("java/util/Optional"), "j$/util/Optional");
pos = methodSignature_java8_str.find("java/util/Optional");
}
*methodId = env->GetMethodID(javaClass, methodName, methodSignature_java8_str.c_str());
env->ExceptionClear();
method_signature = StrReplaceAll(method_signature, "java/util/Optional", "j$/util/Optional");
}

*methodId = env->GetMethodID(javaClass, methodName, method_signature.data());

VerifyOrReturnError(*methodId != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND);

return CHIP_NO_ERROR;
Expand Down Expand Up @@ -226,24 +262,23 @@ void JniReferences::ThrowError(JNIEnv * env, jclass exceptionCls, CHIP_ERROR err

CHIP_ERROR JniReferences::CreateOptional(jobject objectToWrap, jobject & outOptional)
{
JNIEnv * env = GetEnvForCurrentThread();
jclass optionalCls;
chip::JniReferences::GetInstance().GetClassRef(env, "java/util/Optional", optionalCls);
VerifyOrReturnError(optionalCls != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND);
chip::JniClass jniClass(optionalCls);
VerifyOrReturnError(mOptionalClass != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND);

jmethodID ofMethod = env->GetStaticMethodID(optionalCls, "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;");
env->ExceptionClear();
JNIEnv * const env = GetEnvForCurrentThread();
VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV);

// Try `Lj$/util/Optional;` when enabling Java8.
if (ofMethod == nullptr)
jmethodID ofMethod = nullptr;
if (use_java8_optional)
{
ofMethod = env->GetStaticMethodID(optionalCls, "ofNullable", "(Ljava/lang/Object;)Lj$/util/Optional;");
env->ExceptionClear();
ofMethod = env->GetStaticMethodID(mOptionalClass, "ofNullable", "(Ljava/lang/Object;)Ljava/util/Optional;");
}
else
{
ofMethod = env->GetStaticMethodID(mOptionalClass, "ofNullable", "(Ljava/lang/Object;)Lj$/util/Optional;");
}

VerifyOrReturnError(ofMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND);
outOptional = env->CallStaticObjectMethod(optionalCls, ofMethod, objectToWrap);

outOptional = env->CallStaticObjectMethod(mOptionalClass, ofMethod, objectToWrap);

VerifyOrReturnError(!env->ExceptionCheck(), CHIP_JNI_ERROR_EXCEPTION_THROWN);

Expand All @@ -252,13 +287,12 @@ CHIP_ERROR JniReferences::CreateOptional(jobject objectToWrap, jobject & outOpti

CHIP_ERROR JniReferences::GetOptionalValue(jobject optionalObj, jobject & optionalValue)
{
JNIEnv * env = GetEnvForCurrentThread();
jclass optionalCls;
chip::JniReferences::GetInstance().GetClassRef(env, "java/util/Optional", optionalCls);
VerifyOrReturnError(optionalCls != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND);
chip::JniClass jniClass(optionalCls);
VerifyOrReturnError(mOptionalClass != nullptr, CHIP_JNI_ERROR_TYPE_NOT_FOUND);

JNIEnv * const env = GetEnvForCurrentThread();
VerifyOrReturnError(env != nullptr, CHIP_JNI_ERROR_NO_ENV);

jmethodID isPresentMethod = env->GetMethodID(optionalCls, "isPresent", "()Z");
jmethodID isPresentMethod = env->GetMethodID(mOptionalClass, "isPresent", "()Z");
VerifyOrReturnError(isPresentMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND);
jboolean isPresent = optionalObj && env->CallBooleanMethod(optionalObj, isPresentMethod);

Expand All @@ -268,7 +302,7 @@ CHIP_ERROR JniReferences::GetOptionalValue(jobject optionalObj, jobject & option
return CHIP_NO_ERROR;
}

jmethodID getMethod = env->GetMethodID(optionalCls, "get", "()Ljava/lang/Object;");
jmethodID getMethod = env->GetMethodID(mOptionalClass, "get", "()Ljava/lang/Object;");
VerifyOrReturnError(getMethod != nullptr, CHIP_JNI_ERROR_METHOD_NOT_FOUND);
optionalValue = env->CallObjectMethod(optionalObj, getMethod);
return CHIP_NO_ERROR;
Expand Down
4 changes: 4 additions & 0 deletions src/lib/support/JniReferences.h
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,12 @@ class JniReferences
jobject mClassLoader = nullptr;
jmethodID mFindClassMethod = nullptr;

// These are global refs and therefore safe to persist.
jclass mHashMapClass = nullptr;
jclass mListClass = nullptr;
jclass mArrayListClass = nullptr;
jclass mOptionalClass = nullptr;

bool use_java8_optional = false;
};
} // namespace chip
Loading