Skip to content

fix: handle missing child classes when querying native classes #1718

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

Merged
merged 1 commit into from
Aug 1, 2022
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
47 changes: 41 additions & 6 deletions test-app/runtime/src/main/cpp/JEnv.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -723,16 +723,31 @@ jclass JEnv::FindClass(const string &className) {
jclass global_class = CheckForClassInCache(className);

if (global_class == nullptr) {
auto classIsMissing = CheckForClassMissingCache(className);
// class is missing. Set the same JNI error we had when we tried to find it the first time
if (classIsMissing != nullptr) {
m_env->Throw(classIsMissing);
return nullptr;
}
jclass tmp = m_env->FindClass(className.c_str());

if (m_env->ExceptionCheck() == JNI_TRUE) {
m_env->ExceptionClear();
string cannonicalClassName = Util::ConvertFromJniToCanonicalName(className);
jstring s = m_env->NewStringUTF(cannonicalClassName.c_str());
tmp = static_cast<jclass>(m_env->CallStaticObjectMethod(RUNTIME_CLASS,
GET_CACHED_CLASS_METHOD_ID, s));

m_env->DeleteLocalRef(s);
string cannonicalClassName = Util::ConvertFromJniToCanonicalName(className);
jstring s = m_env->NewStringUTF(cannonicalClassName.c_str());
tmp = static_cast<jclass>(m_env->CallStaticObjectMethod(RUNTIME_CLASS,
GET_CACHED_CLASS_METHOD_ID, s));

m_env->DeleteLocalRef(s);
// we failed our static class check
// if we continue, we will crash (C++ level)
// so just return null and let the runtime deal with the NativeScriptException
if (m_env->ExceptionCheck() == JNI_TRUE) {
auto tmpException = m_env->ExceptionOccurred();
m_env->ExceptionClear();
m_env->Throw(InsertClassIntoMissingCache(className, tmpException));
return nullptr;
}
}

global_class = InsertClassIntoCache(className, tmp);
Expand Down Expand Up @@ -760,6 +775,25 @@ jclass JEnv::InsertClassIntoCache(const string &className, jclass &tmp) {
return global_class;
}

jthrowable JEnv::CheckForClassMissingCache(const string &className) {
jthrowable throwable = nullptr;
auto itFound = s_missingClasses.find(className);

if (itFound != s_missingClasses.end()) {
throwable = itFound->second;
}

return throwable;
}

jthrowable JEnv::InsertClassIntoMissingCache(const string &className,const jthrowable &tmp) {
auto throwable = reinterpret_cast<jthrowable>(m_env->NewGlobalRef(tmp));
s_missingClasses.insert(make_pair(className, throwable));
m_env->DeleteLocalRef(tmp);

return throwable;
}

jobject JEnv::NewDirectByteBuffer(void *address, jlong capacity) {
jobject jo = m_env->NewDirectByteBuffer(address, capacity);
CheckForJavaException();
Expand Down Expand Up @@ -822,6 +856,7 @@ void JEnv::CheckForJavaException() {

JavaVM *JEnv::s_jvm = nullptr;
map<string, jclass> JEnv::s_classCache;
map<string, jthrowable> JEnv::s_missingClasses;
jclass JEnv::RUNTIME_CLASS = nullptr;
jmethodID JEnv::GET_CACHED_CLASS_METHOD_ID = nullptr;

Expand Down
11 changes: 11 additions & 0 deletions test-app/runtime/src/main/cpp/JEnv.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,16 @@ class JEnv {
*/
jclass InsertClassIntoCache(const std::string& className, jclass& tmp);


/*
* The "CheckForClassMissing" will check if a class has been checked and it was missing, if it is, it will return the original throwable
* this is useful for rethrowing exceptions if they were caught in the previous attempt of loading it.
* if it is not: it will return "nullptr".
*/
jthrowable CheckForClassMissingCache(const std::string& className);

jthrowable InsertClassIntoMissingCache(const std::string& className, const jthrowable& tmp);

jobject NewDirectByteBuffer(void* address, jlong capacity);
void* GetDirectBufferAddress(jobject buf);
jlong GetDirectBufferCapacity(jobject buf);
Expand Down Expand Up @@ -333,6 +343,7 @@ class JEnv {
static jmethodID GET_CACHED_CLASS_METHOD_ID;

static std::map<std::string, jclass> s_classCache;
static std::map<std::string, jthrowable> s_missingClasses;
};
}

Expand Down
25 changes: 23 additions & 2 deletions test-app/runtime/src/main/cpp/MetadataNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -884,6 +884,10 @@ void MetadataNode::SetInnerTypes(Isolate* isolate, Local<Function>& ctorFunction

// The call to GetConstructorFunctionTemplate bootstraps the ctor function for the childNode
auto innerTypeCtorFuncTemplate = childNode->GetConstructorFunctionTemplate(isolate, curChild);
if(innerTypeCtorFuncTemplate.IsEmpty()) {
// this class does not exist, so just ignore it
continue;
}
auto innerTypeCtorFunc = Local<Function>::New(isolate, *GetOrCreateInternal(curChild)->GetPersistentConstructorFunction(isolate));
auto innerTypeName = ArgConverter::ConvertToV8String(isolate, curChild->name);
ctorFunction->Set(context, innerTypeName, innerTypeCtorFunc);
Expand Down Expand Up @@ -917,6 +921,25 @@ Local<FunctionTemplate> MetadataNode::GetConstructorFunctionTemplate(Isolate* is
//

auto node = GetOrCreateInternal(treeNode);


JEnv env;
// if we already have an exception (which will be rethrown later)
// then we don't want to ignore the next exception
bool ignoreFindClassException = env.ExceptionCheck() == JNI_FALSE;
auto currentClass = env.FindClass(node->m_name);
if (ignoreFindClassException && env.ExceptionCheck()) {
env.ExceptionClear();
// JNI found an exception looking up this class
// but we don't care, because this means this class doesn't exist
// like when you try to get a class that only exists in a higher API level
ctorFuncTemplate.Clear();
auto pft = new Persistent<FunctionTemplate>(isolate, ctorFuncTemplate);
CtorCacheData ctorCacheItem(pft, instanceMethodsCallbackData);
cache->CtorFuncCache.insert(make_pair(treeNode, ctorCacheItem));
return ctorFuncTemplate;
}

auto ctorCallbackData = External::New(isolate, node);
auto isInterface = s_metadataReader.IsNodeTypeInterface(treeNode->type);
auto funcCallback = isInterface ? InterfaceConstructorCallback : ClassConstructorCallback;
Expand All @@ -926,8 +949,6 @@ Local<FunctionTemplate> MetadataNode::GetConstructorFunctionTemplate(Isolate* is
Local<Function> baseCtorFunc;
std::vector<MethodCallbackData*> baseInstanceMethodsCallbackData;
auto tmpTreeNode = treeNode;
JEnv env;
auto currentClass = env.FindClass(node->m_name);
std::vector<MetadataTreeNode*> skippedBaseTypes;
while (true) {
auto baseTreeNode = s_metadataReader.GetBaseClassNode(tmpTreeNode);
Expand Down