Skip to content

Commit 9e610c8

Browse files
ptomatoedusperoni
authored andcommitted
fix: Avoid setting the same property on an ObjectTemplate twice
Due to Kotlin extension methods with the same names as Java methods, we may be setting the same property twice on a class's prototype template. The V8 API does not allow this and will fail a dcheck. Since there is no API to check if a property has already been added to v8::ObjectTemplate, wrap the prototype template in a small class PrototypeTemplateFiller which keeps track of what's already been added and avoids adding it again.
1 parent 9dfae65 commit 9e610c8

File tree

3 files changed

+154
-53
lines changed

3 files changed

+154
-53
lines changed

test-app/app/src/main/assets/app/tests/kotlin/extensions/testExtensionFunctionsSupport.js

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,4 +116,27 @@ describe("Tests Kotlin extension functions support", function () {
116116
expect(hasException).toBe(false);
117117
});
118118

119+
describe("Kotlin extension functions that shadow Java functions", function () {
120+
let handler;
121+
beforeEach(function () {
122+
handler = new android.os.Handler(android.os.Looper.getMainLooper());
123+
});
124+
125+
it("should call the Java function", function (done) {
126+
const run = jasmine.createSpy("java.lang.Runnable.run").and.callFake(function () {
127+
done();
128+
});
129+
const javaRunnable = new java.lang.Runnable({run});
130+
handler.postAtTime(javaRunnable, 1);
131+
})
132+
133+
it("should call the Kotlin function", function (done) {
134+
const invoke = jasmine.createSpy("kotlin.jvm.functions.Function0.invoke").and.callFake(function () {
135+
done();
136+
});
137+
const cancelToken = new java.lang.Object();
138+
const kotlinFunc = new kotlin.jvm.functions.Function0({invoke});
139+
handler.postAtTime(1, cancelToken, kotlinFunc);
140+
})
141+
});
119142
});

test-app/runtime/src/main/cpp/MetadataNode.cpp

Lines changed: 102 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -483,23 +483,86 @@ void MetadataNode::SuperAccessorGetterCallback(Local<Name> property, const Prope
483483
}
484484
}
485485

486-
std::vector<MetadataNode::MethodCallbackData*> MetadataNode::SetInstanceMembers(Isolate* isolate, Local<FunctionTemplate>& ctorFuncTemplate, Local<ObjectTemplate>& prototypeTemplate, vector<MethodCallbackData*>& instanceMethodsCallbackData, const vector<MethodCallbackData*>& baseInstanceMethodsCallbackData, MetadataTreeNode* treeNode) {
486+
class MetadataNode::PrototypeTemplateFiller {
487+
public:
488+
explicit PrototypeTemplateFiller(Local<ObjectTemplate> protoTemplate)
489+
: m_protoTemplate(protoTemplate) {}
490+
491+
void FillPrototypeMethod(Isolate *isolate, const std::string& name,
492+
MethodCallbackData* methodInfo) {
493+
if (m_addedNames.count(name) != 0) {
494+
DEBUG_WRITE("Not defining method prototype.%s which has already been added", name.c_str());
495+
return;
496+
}
497+
498+
Local<External> funcData = External::New(isolate, methodInfo);
499+
Local<FunctionTemplate> funcTemplate = FunctionTemplate::New(isolate, MetadataNode::MethodCallback, funcData);
500+
Local<String> nameString = ArgConverter::ConvertToV8String(isolate, name);
501+
m_protoTemplate->Set(nameString, funcTemplate);
502+
m_addedNames.insert(name);
503+
}
504+
505+
void FillPrototypeField(Isolate* isolate, const std::string& name, FieldCallbackData* fieldInfo,
506+
AccessControl access = AccessControl::DEFAULT) {
507+
if (m_addedNames.count(name) != 0) {
508+
DEBUG_WRITE("Not defining field prototype.%s which already exists", name.c_str());
509+
return;
510+
}
511+
512+
Local<External> fieldData = External::New(isolate, fieldInfo);
513+
Local<Name> nameString = ArgConverter::ConvertToV8String(isolate, name);
514+
m_protoTemplate->SetAccessor(nameString, MetadataNode::FieldAccessorGetterCallback,
515+
MetadataNode::FieldAccessorSetterCallback, fieldData, access,
516+
PropertyAttribute::DontDelete);
517+
m_addedNames.insert(name);
518+
}
519+
520+
void FillPrototypeProperty(Isolate* isolate, const std::string& name,
521+
PropertyCallbackData* propertyInfo) {
522+
if (m_addedNames.count(name) != 0) {
523+
DEBUG_WRITE("Not defining property prototype.%s which already exists", name.c_str());
524+
return;
525+
}
526+
527+
Local<External> propertyData = External::New(isolate, propertyInfo);
528+
Local<Name> nameString = ArgConverter::ConvertToV8String(isolate, name);
529+
m_protoTemplate->SetAccessor(nameString, MetadataNode::PropertyAccessorGetterCallback,
530+
MetadataNode::PropertyAccessorSetterCallback, propertyData,
531+
AccessControl::DEFAULT, PropertyAttribute::DontDelete);
532+
m_addedNames.insert(name);
533+
}
534+
535+
private:
536+
Local<ObjectTemplate> m_protoTemplate;
537+
std::unordered_set<std::string> m_addedNames;
538+
};
539+
540+
std::vector<MetadataNode::MethodCallbackData*> MetadataNode::SetInstanceMembers(
541+
Isolate* isolate, Local<FunctionTemplate>& ctorFuncTemplate,
542+
PrototypeTemplateFiller& protoFiller,
543+
vector<MethodCallbackData*>& instanceMethodsCallbackData,
544+
const vector<MethodCallbackData*>& baseInstanceMethodsCallbackData,
545+
MetadataTreeNode* treeNode) {
487546
auto hasCustomMetadata = treeNode->metadata != nullptr;
488547

489548
if (hasCustomMetadata) {
490-
return SetInstanceMembersFromRuntimeMetadata(isolate, ctorFuncTemplate, prototypeTemplate, instanceMethodsCallbackData, baseInstanceMethodsCallbackData, treeNode);
491-
} else {
492-
SetInstanceFieldsFromStaticMetadata(isolate, ctorFuncTemplate, prototypeTemplate, instanceMethodsCallbackData, baseInstanceMethodsCallbackData, treeNode);
493-
return SetInstanceMethodsFromStaticMetadata(isolate, ctorFuncTemplate, prototypeTemplate, instanceMethodsCallbackData, baseInstanceMethodsCallbackData, treeNode);
549+
return SetInstanceMembersFromRuntimeMetadata(
550+
isolate, protoFiller, instanceMethodsCallbackData,
551+
baseInstanceMethodsCallbackData, treeNode);
494552
}
553+
554+
SetInstanceFieldsFromStaticMetadata(isolate, protoFiller, treeNode);
555+
return SetInstanceMethodsFromStaticMetadata(
556+
isolate, ctorFuncTemplate, protoFiller, instanceMethodsCallbackData,
557+
baseInstanceMethodsCallbackData, treeNode);
495558
}
496559

497-
vector<MetadataNode::MethodCallbackData *> MetadataNode::SetInstanceMethodsFromStaticMetadata(Isolate *isolate,
498-
Local<FunctionTemplate> &ctorFuncTemplate,
499-
Local<ObjectTemplate> &prototypeTemplate,
500-
vector<MethodCallbackData *> &instanceMethodsCallbackData,
501-
const vector<MethodCallbackData *> &baseInstanceMethodsCallbackData,
502-
MetadataTreeNode *treeNode) {
560+
vector<MetadataNode::MethodCallbackData *> MetadataNode::SetInstanceMethodsFromStaticMetadata(
561+
Isolate *isolate, Local<FunctionTemplate> &ctorFuncTemplate,
562+
PrototypeTemplateFiller& protoFiller,
563+
vector<MethodCallbackData *> &instanceMethodsCallbackData,
564+
const vector<MethodCallbackData *> &baseInstanceMethodsCallbackData,
565+
MetadataTreeNode *treeNode) {
503566
SET_PROFILER_FRAME();
504567

505568
std::vector<MethodCallbackData *> instanceMethodData;
@@ -520,7 +583,6 @@ vector<MetadataNode::MethodCallbackData *> MetadataNode::SetInstanceMethodsFromS
520583
MethodCallbackData *callbackData = nullptr;
521584

522585
auto context = isolate->GetCurrentContext();
523-
auto origin = Constants::APP_ROOT_FOLDER_PATH + GetOrCreateInternal(treeNode)->m_name;
524586

525587
std::unordered_map<std::string, MethodCallbackData *> collectedExtensionMethodDatas;
526588

@@ -535,10 +597,7 @@ vector<MetadataNode::MethodCallbackData *> MetadataNode::SetInstanceMethodsFromS
535597
entry.name);
536598
if (callbackData == nullptr) {
537599
callbackData = new MethodCallbackData(this);
538-
auto funcData = External::New(isolate, callbackData);
539-
auto funcTemplate = FunctionTemplate::New(isolate, MethodCallback, funcData);
540-
auto funcName = ArgConverter::ConvertToV8String(isolate, entry.name);
541-
prototypeTemplate->Set(funcName, funcTemplate);
600+
protoFiller.FillPrototypeMethod(isolate, entry.name, callbackData);
542601

543602
lastMethodName = entry.name;
544603
std::pair<std::string, MethodCallbackData *> p(entry.name, callbackData);
@@ -581,25 +640,24 @@ vector<MetadataNode::MethodCallbackData *> MetadataNode::SetInstanceMethodsFromS
581640
callbackData->parent = *itFound;
582641
}
583642

584-
auto funcData = External::New(isolate, callbackData);
585-
auto funcTemplate = FunctionTemplate::New(isolate, MethodCallback, funcData);
586-
587-
auto funcName = ArgConverter::ConvertToV8String(isolate, entry.name);
588-
589643
if (s_profilerEnabled) {
644+
Local<External> funcData = External::New(isolate, callbackData);
645+
Local<FunctionTemplate> funcTemplate = FunctionTemplate::New(isolate, MethodCallback, funcData);
590646
auto func = funcTemplate->GetFunction(context).ToLocalChecked();
647+
std::string origin = Constants::APP_ROOT_FOLDER_PATH + GetOrCreateInternal(treeNode)->m_name;
591648
Local<Function> wrapperFunc = Wrap(isolate, func, entry.name, origin,
592649
false /* isCtorFunc */);
593650
Local<Function> ctorFunc = ctorFuncTemplate->GetFunction(context).ToLocalChecked();
594651
Local<Value> protoVal;
595652
ctorFunc->Get(context,
596653
ArgConverter::ConvertToV8String(isolate, "prototype")).ToLocal(
597654
&protoVal);
655+
Local<String> funcName = ArgConverter::ConvertToV8String(isolate, entry.name);
598656
if (!protoVal.IsEmpty() && !protoVal->IsUndefined() && !protoVal->IsNull()) {
599657
protoVal.As<Object>()->Set(context, funcName, wrapperFunc);
600658
}
601659
} else {
602-
prototypeTemplate->Set(funcName, funcTemplate);
660+
protoFiller.FillPrototypeMethod(isolate, entry.name, callbackData);
603661
}
604662

605663
lastMethodName = entry.name;
@@ -628,7 +686,8 @@ MetadataNode::MethodCallbackData *MetadataNode::tryGetExtensionMethodCallbackDat
628686
return nullptr;
629687
}
630688

631-
void MetadataNode::SetInstanceFieldsFromStaticMetadata(Isolate* isolate, Local<FunctionTemplate>& ctorFuncTemplate, Local<ObjectTemplate>& prototypeTemplate, vector<MethodCallbackData*>& instanceMethodsCallbackData, const vector<MethodCallbackData*>& baseInstanceMethodsCallbackData, MetadataTreeNode* treeNode) {
689+
void MetadataNode::SetInstanceFieldsFromStaticMetadata(
690+
Isolate* isolate, PrototypeTemplateFiller& prototypeTemplate, MetadataTreeNode* treeNode) {
632691
SET_PROFILER_FRAME();
633692

634693
Local<Function> ctorFunction;
@@ -665,12 +724,9 @@ void MetadataNode::SetInstanceFieldsFromStaticMetadata(Isolate* isolate, Local<F
665724
curPtr += sizeof(uint16_t);
666725
for (auto i = 0; i < instanceFieldCout; i++) {
667726
auto entry = s_metadataReader.ReadInstanceFieldEntry(&curPtr);
668-
669-
auto fieldName = ArgConverter::ConvertToV8String(isolate, entry.name);
670727
auto fieldInfo = new FieldCallbackData(entry);
671728
fieldInfo->declaringType = curType;
672-
auto fieldData = External::New(isolate, fieldInfo);
673-
prototypeTemplate->SetAccessor(fieldName, FieldAccessorGetterCallback, FieldAccessorSetterCallback, fieldData, AccessControl::DEFAULT, PropertyAttribute::DontDelete);
729+
prototypeTemplate.FillPrototypeField(isolate, entry.name, fieldInfo);
674730
}
675731

676732
auto kotlinPropertiesCount = *reinterpret_cast<uint16_t*>(curPtr);
@@ -699,12 +755,15 @@ void MetadataNode::SetInstanceFieldsFromStaticMetadata(Isolate* isolate, Local<F
699755
}
700756

701757
auto propertyInfo = new PropertyCallbackData(propertyName, getterMethodName, setterMethodName);
702-
auto propertyData = External::New(isolate, propertyInfo);
703-
prototypeTemplate->SetAccessor(ArgConverter::ConvertToV8String(isolate, propertyName), PropertyAccessorGetterCallback, PropertyAccessorSetterCallback, propertyData, AccessControl::DEFAULT, PropertyAttribute::DontDelete);
758+
prototypeTemplate.FillPrototypeProperty(isolate, propertyName, propertyInfo);
704759
}
705760
}
706761

707-
vector<MetadataNode::MethodCallbackData*> MetadataNode::SetInstanceMembersFromRuntimeMetadata(Isolate* isolate, Local<FunctionTemplate>& ctorFuncTemplate, Local<ObjectTemplate>& prototypeTemplate, vector<MethodCallbackData*>& instanceMethodsCallbackData, const vector<MethodCallbackData*>& baseInstanceMethodsCallbackData, MetadataTreeNode* treeNode) {
762+
vector<MetadataNode::MethodCallbackData*> MetadataNode::SetInstanceMembersFromRuntimeMetadata(
763+
Isolate* isolate, PrototypeTemplateFiller& protoFiller,
764+
vector<MethodCallbackData*>& instanceMethodsCallbackData,
765+
const vector<MethodCallbackData*>& baseInstanceMethodsCallbackData,
766+
MetadataTreeNode* treeNode) {
708767
SET_PROFILER_FRAME();
709768

710769
assert(treeNode->metadata != nullptr);
@@ -757,18 +816,14 @@ vector<MetadataNode::MethodCallbackData*> MetadataNode::SetInstanceMembersFromRu
757816
callbackData->parent = *itFound;
758817
}
759818

760-
auto funcData = External::New(isolate, callbackData);
761-
auto funcTemplate = FunctionTemplate::New(isolate, MethodCallback, funcData);
762-
auto funcName = ArgConverter::ConvertToV8String(isolate, entry.name);
763-
prototypeTemplate->Set(funcName, funcTemplate);
819+
protoFiller.FillPrototypeMethod(isolate, entry.name, callbackData);
764820
lastMethodName = entry.name;
765821
}
766822
callbackData->candidates.push_back(entry);
767823
} else if (chKind == 'F') {
768-
auto fieldName = ArgConverter::ConvertToV8String(isolate, entry.name);
769-
auto fieldData = External::New(isolate, new FieldCallbackData(entry));
824+
auto* fieldInfo = new FieldCallbackData(entry);
770825
auto access = entry.isFinal ? AccessControl::ALL_CAN_READ : AccessControl::DEFAULT;
771-
prototypeTemplate->SetAccessor(fieldName, FieldAccessorGetterCallback, FieldAccessorSetterCallback, fieldData, access, PropertyAttribute::DontDelete);
826+
protoFiller.FillPrototypeField(isolate, entry.name, fieldInfo, access);
772827
}
773828
}
774829
return instanceMethodData;
@@ -974,11 +1029,13 @@ Local<FunctionTemplate> MetadataNode::GetConstructorFunctionTemplate(Isolate* is
9741029
break;
9751030
}
9761031

977-
auto prototypeTemplate = ctorFuncTemplate->PrototypeTemplate();
1032+
PrototypeTemplateFiller protoFiller{ctorFuncTemplate->PrototypeTemplate()};
9781033

979-
auto instanceMethodData = node->SetInstanceMembers(isolate, ctorFuncTemplate, prototypeTemplate, instanceMethodsCallbackData, baseInstanceMethodsCallbackData, treeNode);
1034+
auto instanceMethodData = node->SetInstanceMembers(
1035+
isolate, ctorFuncTemplate, protoFiller, instanceMethodsCallbackData,
1036+
baseInstanceMethodsCallbackData, treeNode);
9801037
if (!skippedBaseTypes.empty()) {
981-
node->SetMissingBaseMethods(isolate, skippedBaseTypes, instanceMethodData, prototypeTemplate);
1038+
node->SetMissingBaseMethods(isolate, skippedBaseTypes, instanceMethodData, protoFiller);
9821039
}
9831040

9841041
auto context = isolate->GetCurrentContext();
@@ -2075,7 +2132,10 @@ bool MetadataNode::CheckClassHierarchy(JEnv& env, jclass currentClass, MetadataT
20752132
* This method handles scenrios like Bundle/BaseBundle class hierarchy change in API level 21.
20762133
* See https://github.com/NativeScript/android-runtime/issues/628
20772134
*/
2078-
void MetadataNode::SetMissingBaseMethods(Isolate* isolate, const vector<MetadataTreeNode*>& skippedBaseTypes, const vector<MethodCallbackData*>& instanceMethodData, Local<ObjectTemplate>& prototypeTemplate) {
2135+
void MetadataNode::SetMissingBaseMethods(
2136+
Isolate* isolate, const vector<MetadataTreeNode*>& skippedBaseTypes,
2137+
const vector<MethodCallbackData*>& instanceMethodData,
2138+
PrototypeTemplateFiller& protoFiller) {
20792139
for (auto treeNode: skippedBaseTypes) {
20802140
uint8_t* curPtr = s_metadataReader.GetValueData() + treeNode->offsetValue + 1;
20812141

@@ -2111,11 +2171,7 @@ void MetadataNode::SetMissingBaseMethods(Isolate* isolate, const vector<Metadata
21112171

21122172
if (callbackData == nullptr) {
21132173
callbackData = new MethodCallbackData(this);
2114-
2115-
auto funcData = External::New(isolate, callbackData);
2116-
auto funcTemplate = FunctionTemplate::New(isolate, MethodCallback, funcData);
2117-
auto funcName = ArgConverter::ConvertToV8String(isolate, entry.name);
2118-
prototypeTemplate->Set(funcName, funcTemplate);
2174+
protoFiller.FillPrototypeMethod(isolate, entry.name, callbackData);
21192175
}
21202176

21212177
bool foundSameSig = false;

0 commit comments

Comments
 (0)