From 1c9b0141a5071a47904adecfbddaa6dd97aec18c Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 26 Oct 2025 12:49:02 +0200 Subject: [PATCH 1/5] feat: Added support for struct reference index access --- NativeScript/runtime/Interop.h | 2 + NativeScript/runtime/Interop.mm | 51 +++++++++++ NativeScript/runtime/Reference.cpp | 86 ++++++++++++++----- NativeScript/runtime/Reference.h | 6 +- .../app/tests/Marshalling/ReferenceTests.js | 51 +++++++++++ 5 files changed, 173 insertions(+), 23 deletions(-) diff --git a/NativeScript/runtime/Interop.h b/NativeScript/runtime/Interop.h index a2582374..3980be17 100644 --- a/NativeScript/runtime/Interop.h +++ b/NativeScript/runtime/Interop.h @@ -119,9 +119,11 @@ class Interop { static id CallInitializer(v8::Local context, const MethodMeta* methodMeta, id target, Class clazz, V8Args& args); static v8::Local CallFunction(ObjCMethodCall& methodCall); static v8::Local CallFunction(CMethodCall& methodCall); + static v8::Local GetResultByType(v8::Local context, BaseDataWrapper* typeWrapper, BaseCall* call, std::shared_ptr> parentStruct = nullptr); static v8::Local GetResult(v8::Local context, const TypeEncoding* typeEncoding, BaseCall* call, bool marshalToPrimitive, std::shared_ptr> parentStruct = nullptr, bool isStructMember = false, bool ownsReturnedObject = false, bool returnsUnmanaged = false, bool isInitializer = false); static void SetStructPropertyValue(v8::Local context, StructWrapper* wrapper, StructField field, v8::Local value); static void InitializeStruct(v8::Local context, void* destBuffer, std::vector fields, v8::Local inititalizer); + static void WriteTypeValue(v8::Local context, BaseDataWrapper* typeWrapper, void* dest, v8::Local arg); static void WriteValue(v8::Local context, const TypeEncoding* typeEncoding, void* dest, v8::Local arg); static id ToObject(v8::Local context, v8::Local arg); static v8::Local GetPrimitiveReturnType(v8::Local context, BinaryTypeEncodingType type, BaseCall* call); diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index d3973128..12c7f8bc 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -182,6 +182,42 @@ inline bool isBool() { IsOfType _isObject = UNDEFINED; }; +void Interop::WriteTypeValue(Local context, BaseDataWrapper* typeWrapper, void* dest, Local arg) { + Isolate* isolate = context->GetIsolate(); + ValueCache argHelper(arg); + bool isEmptyOrUndefined = arg.IsEmpty() || arg->IsNullOrUndefined(); + + if (typeWrapper->Type() == WrapperType::StructType) { + if (isEmptyOrUndefined) { + StructTypeWrapper* structTypeWrapper = static_cast(typeWrapper); + StructInfo structInfo = structTypeWrapper->StructInfo(); + + memset(dest, 0, structInfo.FFIType()->size); + } else if (argHelper.isObject()) { + BaseDataWrapper* wrapper = tns::GetValue(isolate, arg); + if (wrapper != nullptr) { + if (wrapper->Type() == WrapperType::Struct) { + StructWrapper* structWrapper = static_cast(wrapper); + void* buffer = structWrapper->Data(); + size_t size = structWrapper->StructInfo().FFIType()->size; + memcpy(dest, buffer, size); + } else { + tns::Assert(false, isolate); + } + } else { + // Create the structure using the struct initializer syntax + StructTypeWrapper* structTypeWrapper = static_cast(typeWrapper); + StructInfo structInfo = structTypeWrapper->StructInfo(); + Interop::InitializeStruct(context, dest, structInfo.Fields(), arg.As()); + } + } else { + tns::Assert(false, isolate); + } + } else { + tns::Assert(false, isolate); + } +} + void Interop::WriteValue(Local context, const TypeEncoding* typeEncoding, void* dest, Local arg) { Isolate* isolate = context->GetIsolate(); ExecuteWriteValueDebugValidationsIfInDebug(context, typeEncoding, dest, arg); @@ -806,6 +842,21 @@ inline bool isBool() { *static_cast((void*)((uint8_t*)destBuffer + position)) = result; } +Local Interop::GetResultByType(Local context, BaseDataWrapper* typeWrapper, BaseCall* call, std::shared_ptr> parentStruct) { + Isolate* isolate = context->GetIsolate(); + + if (typeWrapper->Type() == WrapperType::StructType) { + StructTypeWrapper* structTypeWrapper = static_cast(typeWrapper); + StructInfo structInfo = structTypeWrapper->StructInfo(); + + void* result = call->ResultBuffer(); + Local value = Interop::StructToValue(context, result, structInfo, parentStruct); + return value; + } + + return Null(isolate); +} + Local Interop::GetResult(Local context, const TypeEncoding* typeEncoding, BaseCall* call, bool marshalToPrimitive, std::shared_ptr> parentStruct, bool isStructMember, bool ownsReturnedObject, bool returnsUnmanaged, bool isInitializer) { Isolate* isolate = context->GetIsolate(); diff --git a/NativeScript/runtime/Reference.cpp b/NativeScript/runtime/Reference.cpp index e0986a80..c6194ada 100644 --- a/NativeScript/runtime/Reference.cpp +++ b/NativeScript/runtime/Reference.cpp @@ -99,14 +99,20 @@ void Reference::IndexedPropertyGetCallback(uint32_t index, const PropertyCallbac Local thiz = info.This(); Local context = isolate->GetCurrentContext(); - DataPair pair = Reference::GetTypeEncodingDataPair(thiz); + DataPair pair = Reference::GetDataPair(thiz); const TypeEncoding* typeEncoding = pair.typeEncoding_; size_t size = pair.size_; void* data = pair.data_; void* ptr = (uint8_t*)data + index * size; BaseCall call((uint8_t*)ptr); - Local result = Interop::GetResult(context, typeEncoding, &call, false); + + Local result; + if (typeEncoding != nullptr) { + result = Interop::GetResult(context, typeEncoding, &call, false); + } else { + result = Interop::GetResultByType(context, pair.typeWrapper_, &call); + } info.GetReturnValue().Set(result); } @@ -115,13 +121,17 @@ void Reference::IndexedPropertySetCallback(uint32_t index, Local value, c Local context = isolate->GetCurrentContext(); Local thiz = info.This(); - DataPair pair = Reference::GetTypeEncodingDataPair(thiz); + DataPair pair = Reference::GetDataPair(thiz); const TypeEncoding* typeEncoding = pair.typeEncoding_; size_t size = pair.size_; void* data = pair.data_; - void* ptr = (uint8_t*)data + index * size; - Interop::WriteValue(context, typeEncoding, ptr, value); + + if (typeEncoding != nullptr) { + Interop::WriteValue(context, typeEncoding, ptr, value); + } else { + Interop::WriteTypeValue(context, pair.typeWrapper_, ptr, value); + } } void Reference::GetValueCallback(Local name, const PropertyCallbackInfo& info) { @@ -186,11 +196,17 @@ Local Reference::GetReferredValue(Local context, Local va } BaseDataWrapper* typeWrapper = wrapper->TypeWrapper(); - if (typeWrapper != nullptr && typeWrapper->Type() == WrapperType::Primitive && baseWrapper != nullptr && baseWrapper->Type() == WrapperType::Pointer) { - Reference::DataPair pair = GetTypeEncodingDataPair(value.As()); - if (pair.data_ != nullptr && pair.typeEncoding_ != nullptr) { + if (typeWrapper != nullptr && Reference::IsSupportedType(typeWrapper->Type()) && baseWrapper != nullptr && baseWrapper->Type() == WrapperType::Pointer) { + Reference::DataPair pair = Reference::GetDataPair(value.As()); + if (pair.data_ != nullptr) { BaseCall call((uint8_t*)pair.data_); - Local result = Interop::GetResult(context, pair.typeEncoding_, &call, false); + Local result; + + if (pair.typeEncoding_ != nullptr) { + result = Interop::GetResult(context, pair.typeEncoding_, &call, false); + } else { + result = Interop::GetResultByType(context, typeWrapper, &call); + } return result; } } @@ -203,7 +219,6 @@ void* Reference::GetWrappedPointer(Local context, Local referenc return nullptr; } - Isolate* isolate = context->GetIsolate(); BaseDataWrapper* wrapper = tns::GetValue(isolate, reference); tns::Assert(wrapper != nullptr && wrapper->Type() == WrapperType::Reference, isolate); @@ -313,7 +328,7 @@ void Reference::RegisterToStringMethod(Local context, Local pro tns::Assert(success, isolate); } -Reference::DataPair Reference::GetTypeEncodingDataPair(Local obj) { +Reference::DataPair Reference::GetDataPair(Local obj) { Local context; bool success = obj->GetCreationContext().ToLocal(&context); tns::Assert(success); @@ -327,33 +342,62 @@ Reference::DataPair Reference::GetTypeEncodingDataPair(Local obj) { // TODO: Missing type when creating the Reference instance tns::Assert(false, isolate); } - - if (typeWrapper->Type() != WrapperType::Primitive) { - // TODO: Currently only PrimitiveDataWrappers are supported as type parameters - // Objective C class classes and structures should also be handled + + size_t size = 0; + const TypeEncoding* typeEncoding = nullptr; + bool isUnknownType = false; + + if (Reference::IsSupportedType(typeWrapper->Type())) { + switch(typeWrapper->Type()) { + case WrapperType::Primitive: { + PrimitiveDataWrapper* primitiveWrapper = static_cast(typeWrapper); + + size = primitiveWrapper->Size(); + typeEncoding = primitiveWrapper->TypeEncoding(); + break; + } + case WrapperType::StructType: { + StructTypeWrapper* structTypeWrapper = static_cast(refWrapper->TypeWrapper()); + StructInfo structInfo = structTypeWrapper->StructInfo(); + + size = structInfo.FFIType()->size; + break; + } + default: { + isUnknownType = true; + break; + } + } + } else { + isUnknownType = true; + } + + if (isUnknownType) { + // TODO: Currently only PrimitiveDataWrappers and Structs are supported as type parameters + // Objective C class classes should also be handled tns::Assert(false, isolate); } - PrimitiveDataWrapper* primitiveWrapper = static_cast(typeWrapper); - Local value = refWrapper->Value()->Get(isolate); BaseDataWrapper* wrappedValue = tns::GetValue(isolate, value); if (wrappedValue != nullptr && wrappedValue->Type() == WrapperType::Pointer) { - const TypeEncoding* typeEncoding = primitiveWrapper->TypeEncoding(); PointerWrapper* pw = static_cast(wrappedValue); void* data = pw->Data(); - DataPair pair(typeEncoding, data, primitiveWrapper->Size()); + DataPair pair(typeWrapper, typeEncoding, data, size); return pair; } if (refWrapper->Encoding() != nullptr && refWrapper->Data() != nullptr) { - DataPair pair(refWrapper->Encoding(), refWrapper->Data(), primitiveWrapper->Size()); + DataPair pair(typeWrapper, refWrapper->Encoding(), refWrapper->Data(), size); return pair; } tns::Assert(false, isolate); - return DataPair(nullptr, nullptr, 0); + return DataPair(typeWrapper, nullptr, nullptr, 0); } +bool Reference::IsSupportedType(WrapperType type) { + return type == WrapperType::Primitive || type == WrapperType::StructType; +} } diff --git a/NativeScript/runtime/Reference.h b/NativeScript/runtime/Reference.h index 2d54eff4..f58706a8 100644 --- a/NativeScript/runtime/Reference.h +++ b/NativeScript/runtime/Reference.h @@ -14,9 +14,10 @@ class Reference { static void* GetWrappedPointer(v8::Local context, v8::Local reference, const TypeEncoding* typeEncoding); private: struct DataPair { - DataPair(const TypeEncoding* typeEncoding, void* data, size_t size): typeEncoding_(typeEncoding), data_(data), size_(size) { + DataPair(BaseDataWrapper* typeWrapper, const TypeEncoding* typeEncoding, void* data, size_t size): typeWrapper_(typeWrapper), typeEncoding_(typeEncoding), data_(data), size_(size) { } + BaseDataWrapper* typeWrapper_; const TypeEncoding* typeEncoding_; void* data_; size_t size_; @@ -30,7 +31,8 @@ class Reference { static void GetValueCallback(v8::Local name, const v8::PropertyCallbackInfo& info); static void SetValueCallback(v8::Local name, v8::Local value, const v8::PropertyCallbackInfo& info); static void RegisterToStringMethod(v8::Local context, v8::Local prototype); - static DataPair GetTypeEncodingDataPair(v8::Local obj); + static DataPair GetDataPair(v8::Local obj); + static bool IsSupportedType(WrapperType type); }; } diff --git a/TestRunner/app/tests/Marshalling/ReferenceTests.js b/TestRunner/app/tests/Marshalling/ReferenceTests.js index fe0f9316..62906650 100644 --- a/TestRunner/app/tests/Marshalling/ReferenceTests.js +++ b/TestRunner/app/tests/Marshalling/ReferenceTests.js @@ -346,6 +346,57 @@ describe(module.id, function () { interop.free(ptr); }); + it("Struct reference with value", function () { + const value = new TNSSimpleStruct({x: 1, y: 2}); + const ref = new interop.Reference(TNSSimpleStruct, value); + + expect(TNSSimpleStruct.equals(ref.value, value)).toBe(true); + }); + + it("Struct reference with pointer and indexed values", function () { + const structs = [ + new TNSSimpleStruct({x: 1, y: 2}), + new TNSSimpleStruct({x: 3, y: 4}), + new TNSSimpleStruct({x: 5, y: 6}) + ]; + const length = structs.length; + const ptr = interop.alloc(interop.sizeof(TNSSimpleStruct) * length); + + const ref = new interop.Reference(TNSSimpleStruct, ptr); + for (let i = 0; i < length; i++) { + ref[i] = structs[i]; + } + + // Check if values were stored into pointer + const resultRef = new interop.Reference(TNSSimpleStruct, ptr); + for (let i = 0; i < length; i++) { + expect(TNSSimpleStruct.equals(resultRef[i], structs[i])).toBe(true); + } + + interop.free(ptr); + }); + + it("Struct reference get first value as referred value", function () { + const structs = [ + new TNSSimpleStruct({x: 1, y: 2}), + new TNSSimpleStruct({x: 3, y: 4}), + new TNSSimpleStruct({x: 5, y: 6}) + ]; + const length = structs.length; + const ptr = interop.alloc(interop.sizeof(TNSSimpleStruct) * length); + + const ref = new interop.Reference(TNSSimpleStruct, ptr); + for (let i = 0; i < length; i++) { + ref[i] = structs[i]; + } + + // Check if values were stored into pointer + const resultRef = new interop.Reference(TNSSimpleStruct, ptr); + expect(TNSSimpleStruct.equals(resultRef.value, structs[0])).toBe(true); + + interop.free(ptr); + }); + it("interops string from CString with fixed length", function () { const str = "te\0st"; const ptr = interop.alloc((str.length + 1) * interop.sizeof(interop.types.uint8)); From 128d6e97907de54a48a452859ce7b7addbf0f0c9 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Sun, 26 Oct 2025 15:57:58 +0200 Subject: [PATCH 2/5] chore: Assert cleanup --- NativeScript/runtime/Interop.mm | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index 12c7f8bc..f90a9269 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -186,6 +186,7 @@ inline bool isBool() { Isolate* isolate = context->GetIsolate(); ValueCache argHelper(arg); bool isEmptyOrUndefined = arg.IsEmpty() || arg->IsNullOrUndefined(); + bool success = false; if (typeWrapper->Type() == WrapperType::StructType) { if (isEmptyOrUndefined) { @@ -193,6 +194,7 @@ inline bool isBool() { StructInfo structInfo = structTypeWrapper->StructInfo(); memset(dest, 0, structInfo.FFIType()->size); + success = true; } else if (argHelper.isObject()) { BaseDataWrapper* wrapper = tns::GetValue(isolate, arg); if (wrapper != nullptr) { @@ -201,19 +203,19 @@ inline bool isBool() { void* buffer = structWrapper->Data(); size_t size = structWrapper->StructInfo().FFIType()->size; memcpy(dest, buffer, size); - } else { - tns::Assert(false, isolate); + success = true; } } else { // Create the structure using the struct initializer syntax StructTypeWrapper* structTypeWrapper = static_cast(typeWrapper); StructInfo structInfo = structTypeWrapper->StructInfo(); Interop::InitializeStruct(context, dest, structInfo.Fields(), arg.As()); + success = true; } - } else { - tns::Assert(false, isolate); } - } else { + } + + if (!success) { tns::Assert(false, isolate); } } From a91e316226c264461c8424b0f3fd875f0245f6a1 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Mon, 27 Oct 2025 19:35:43 +0200 Subject: [PATCH 3/5] fix: Indexed values lost during pointer to reference conversion --- NativeScript/runtime/Interop.mm | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index f90a9269..7879b66f 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -1084,18 +1084,16 @@ inline bool isBool() { } const TypeEncoding* innerType = typeEncoding->details.pointer.getInnerType(); + Local pointer = Pointer::NewInstance(context, result); if (innerType->type == BinaryTypeEncodingType::VoidEncoding) { - Local instance = Pointer::NewInstance(context, result); - return instance; + return pointer; } - BaseCall c(result); - Local value = Interop::GetResult(context, innerType, &c, true); Local type = Interop::GetInteropType(context, innerType->type); std::vector> args; - args.push_back(value); + args.push_back(pointer); if (!type.IsEmpty()) { args.insert(args.begin(), type); } From 6ac67283f241c7afe2f3ff28c1efe6790434aafd Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Tue, 28 Oct 2025 16:27:10 +0200 Subject: [PATCH 4/5] chore: Added tests for array property pointers --- TestFixtures/Interfaces/TNSPointCollection.h | 13 +++++ TestFixtures/Interfaces/TNSPointCollection.m | 42 ++++++++++++++ TestFixtures/TestFixtures.h | 1 + .../app/tests/Marshalling/ReferenceTests.js | 56 +++++++++++++++++++ v8ios.xcodeproj/project.pbxproj | 6 ++ 5 files changed, 118 insertions(+) create mode 100644 TestFixtures/Interfaces/TNSPointCollection.h create mode 100644 TestFixtures/Interfaces/TNSPointCollection.m diff --git a/TestFixtures/Interfaces/TNSPointCollection.h b/TestFixtures/Interfaces/TNSPointCollection.h new file mode 100644 index 00000000..4e1e7c50 --- /dev/null +++ b/TestFixtures/Interfaces/TNSPointCollection.h @@ -0,0 +1,13 @@ + +#import + +typedef struct TNSPoint { + int x; + int y; +} TNSPoint; + +@interface TNSPointCollection : NSObject +- (instancetype)initWithPoints:(const TNSPoint *)points count:(NSUInteger)count; +@property (nonatomic, readonly) TNSPoint *points NS_RETURNS_INNER_POINTER; +@property (nonatomic, readonly) NSUInteger pointCount; +@end diff --git a/TestFixtures/Interfaces/TNSPointCollection.m b/TestFixtures/Interfaces/TNSPointCollection.m new file mode 100644 index 00000000..f1ed4f1a --- /dev/null +++ b/TestFixtures/Interfaces/TNSPointCollection.m @@ -0,0 +1,42 @@ +#import "TNSPointCollection.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation TNSPointCollection +{ + TNSPoint *_points; + NSUInteger _pointCount; +} + +- (instancetype)initWithPoints:(const TNSPoint *)points count:(NSUInteger)count +{ + self = [super init]; + if (self) + { + _pointCount = count; + if (count > 0) + { + _points = malloc(sizeof(TNSPoint) * count); + memcpy(_points, points, sizeof(TNSPoint) * count); + } + else + { + _points = NULL; + } + } + return self; +} + +- (NSUInteger)pointCount +{ + return _pointCount; +} + +- (TNSPoint *)points +{ + return _points; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/TestFixtures/TestFixtures.h b/TestFixtures/TestFixtures.h index c5271ef8..87cc28d1 100644 --- a/TestFixtures/TestFixtures.h +++ b/TestFixtures/TestFixtures.h @@ -14,6 +14,7 @@ #import "Interfaces/TNSConstructorResolution.h" #import "Interfaces/TNSInheritance.h" #import "Interfaces/TNSMethodCalls.h" +#import "Interfaces/TNSPointCollection.h" #import "Interfaces/TNSSwiftLike.h" #import "Marshalling/TNSAllocLog.h" diff --git a/TestRunner/app/tests/Marshalling/ReferenceTests.js b/TestRunner/app/tests/Marshalling/ReferenceTests.js index 62906650..e8501809 100644 --- a/TestRunner/app/tests/Marshalling/ReferenceTests.js +++ b/TestRunner/app/tests/Marshalling/ReferenceTests.js @@ -397,6 +397,62 @@ describe(module.id, function () { interop.free(ptr); }); + it("Reference access indexed values from pointer array property", function () { + const structs = [ + new TNSPoint({x: 1, y: 2}), + new TNSPoint({x: 3, y: 4}), + new TNSPoint({x: 5, y: 6}) + ]; + const length = structs.length; + const ptr = interop.alloc(interop.sizeof(TNSPoint) * length); + + const ref = new interop.Reference(TNSPoint, ptr); + for (let i = 0; i < length; i++) { + ref[i] = structs[i]; + } + + const pointCollection = TNSPointCollection.alloc().initWithPointsCount(ptr, length); + + // Runtime converts pointers into references for cases with type so use handleof to get pointer + const pointsPtr = interop.handleof(pointCollection.points); + + // Check if values were retrieved from pointer + const resultRef = new interop.Reference(TNSPoint, pointsPtr); + for (let i = 0; i < length; i++) { + expect(TNSPoint.equals(resultRef[i], structs[i])).toBe(true); + } + + interop.free(ptr); + interop.free(pointsPtr); + }); + + it("Reference access value from pointer array property", function () { + const structs = [ + new TNSPoint({x: 1, y: 2}), + new TNSPoint({x: 3, y: 4}), + new TNSPoint({x: 5, y: 6}) + ]; + const length = structs.length; + const ptr = interop.alloc(interop.sizeof(TNSPoint) * length); + + const ref = new interop.Reference(TNSPoint, ptr); + for (let i = 0; i < length; i++) { + ref[i] = structs[i]; + } + + const pointCollection = TNSPointCollection.alloc().initWithPointsCount(ptr, length); + + // Runtime converts pointers into references for cases with type so use handleof to get pointer + const pointsPtr = interop.handleof(pointCollection.points); + + // Check if values were retrieved from pointer + const resultRef = new interop.Reference(TNSPoint, pointsPtr); + expect(TNSPoint.equals(resultRef.value, structs[0])).toBe(true); + + interop.free(ptr); + interop.free(pointsPtr); + }); + it("interops string from CString with fixed length", function () { const str = "te\0st"; const ptr = interop.alloc((str.length + 1) * interop.sizeof(interop.types.uint8)); diff --git a/v8ios.xcodeproj/project.pbxproj b/v8ios.xcodeproj/project.pbxproj index 54f685c8..3b597c6c 100644 --- a/v8ios.xcodeproj/project.pbxproj +++ b/v8ios.xcodeproj/project.pbxproj @@ -46,6 +46,7 @@ 6573B9EA291FE2A700B0ED7C /* jsilib.h in Headers */ = {isa = PBXBuildFile; fileRef = 6573B9E1291FE2A700B0ED7C /* jsilib.h */; }; 6573B9ED291FE5B700B0ED7C /* JSIRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = 6573B9EB291FE5B700B0ED7C /* JSIRuntime.h */; }; 6573B9EE291FE5B700B0ED7C /* JSIRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = 6573B9EC291FE5B700B0ED7C /* JSIRuntime.m */; }; + 68E0071A2EB0F7EC00B923AE /* TNSPointCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 68E007192EB0F7EC00B923AE /* TNSPointCollection.m */; }; 9160C065291ED41F000641C0 /* SpinLock.h in Headers */ = {isa = PBXBuildFile; fileRef = 9160C064291ED41F000641C0 /* SpinLock.h */; }; 91B25A0A29DAC83D00E3CE04 /* ns-v8-tracing-agent-impl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 91B25A0829DAC83D00E3CE04 /* ns-v8-tracing-agent-impl.mm */; }; 91B25A0B29DAC83D00E3CE04 /* ns-v8-tracing-agent-impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 91B25A0929DAC83D00E3CE04 /* ns-v8-tracing-agent-impl.h */; }; @@ -479,6 +480,8 @@ 6573B9E1291FE2A700B0ED7C /* jsilib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = jsilib.h; sourceTree = ""; }; 6573B9EB291FE5B700B0ED7C /* JSIRuntime.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = JSIRuntime.h; sourceTree = ""; }; 6573B9EC291FE5B700B0ED7C /* JSIRuntime.m */ = {isa = PBXFileReference; explicitFileType = sourcecode.cpp.objcpp; path = JSIRuntime.m; sourceTree = ""; }; + 68E007192EB0F7EC00B923AE /* TNSPointCollection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TNSPointCollection.m; sourceTree = ""; }; + 68E0071B2EB0F80D00B923AE /* TNSPointCollection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TNSPointCollection.h; sourceTree = ""; }; 9160C064291ED41F000641C0 /* SpinLock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SpinLock.h; path = NativeScript/runtime/SpinLock.h; sourceTree = ""; }; 91B25A0829DAC83D00E3CE04 /* ns-v8-tracing-agent-impl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = "ns-v8-tracing-agent-impl.mm"; sourceTree = ""; }; 91B25A0929DAC83D00E3CE04 /* ns-v8-tracing-agent-impl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "ns-v8-tracing-agent-impl.h"; sourceTree = ""; }; @@ -1134,6 +1137,8 @@ C29374EF229FC1600075CB16 /* TNSClassWithPlaceholder.m */, C22EF68C2379414300D67CBF /* TNSSwiftLike.h */, C22EF68A2379412100D67CBF /* TNSSwiftLike.m */, + 68E007192EB0F7EC00B923AE /* TNSPointCollection.m */, + 68E0071B2EB0F80D00B923AE /* TNSPointCollection.h */, ); path = Interfaces; sourceTree = ""; @@ -2160,6 +2165,7 @@ C293751B229FC1600075CB16 /* TNSTestNativeCallbacks.m in Sources */, C293751D229FC1600075CB16 /* TNSTestCommon.m in Sources */, C293751F229FC1600075CB16 /* TNSBridgedTypes.m in Sources */, + 68E0071A2EB0F7EC00B923AE /* TNSPointCollection.m in Sources */, C2937528229FC1600075CB16 /* TNSObjCTypes.m in Sources */, C2937526229FC1600075CB16 /* TNSRecords.m in Sources */, C2937522229FC1600075CB16 /* TNSReturnsRetained.m in Sources */, From 625bb0eafdf7c9da432e35faebe320015264ff39 Mon Sep 17 00:00:00 2001 From: Dimitris - Rafail Katsampas Date: Tue, 28 Oct 2025 18:40:11 +0200 Subject: [PATCH 5/5] fix: Reference with encoding can accept index access --- NativeScript/runtime/Reference.cpp | 90 +++++++++---------- .../app/tests/Marshalling/ReferenceTests.js | 17 +--- 2 files changed, 49 insertions(+), 58 deletions(-) diff --git a/NativeScript/runtime/Reference.cpp b/NativeScript/runtime/Reference.cpp index c6194ada..a023de41 100644 --- a/NativeScript/runtime/Reference.cpp +++ b/NativeScript/runtime/Reference.cpp @@ -336,65 +336,65 @@ Reference::DataPair Reference::GetDataPair(Local obj) { BaseDataWrapper* wrapper = tns::GetValue(isolate, obj); tns::Assert(wrapper != nullptr && wrapper->Type() == WrapperType::Reference, isolate); ReferenceWrapper* refWrapper = static_cast(wrapper); - BaseDataWrapper* typeWrapper = refWrapper->TypeWrapper(); - if (typeWrapper == nullptr) { - // TODO: Missing type when creating the Reference instance - tns::Assert(false, isolate); - } size_t size = 0; - const TypeEncoding* typeEncoding = nullptr; - bool isUnknownType = false; - - if (Reference::IsSupportedType(typeWrapper->Type())) { - switch(typeWrapper->Type()) { - case WrapperType::Primitive: { - PrimitiveDataWrapper* primitiveWrapper = static_cast(typeWrapper); - - size = primitiveWrapper->Size(); - typeEncoding = primitiveWrapper->TypeEncoding(); - break; - } - case WrapperType::StructType: { - StructTypeWrapper* structTypeWrapper = static_cast(refWrapper->TypeWrapper()); - StructInfo structInfo = structTypeWrapper->StructInfo(); - - size = structInfo.FFIType()->size; - break; - } - default: { - isUnknownType = true; - break; + + if (typeWrapper != nullptr) { + const TypeEncoding* typeEncoding = nullptr; + + if (Reference::IsSupportedType(typeWrapper->Type())) { + switch(typeWrapper->Type()) { + case WrapperType::Primitive: { + PrimitiveDataWrapper* primitiveWrapper = static_cast(typeWrapper); + + size = primitiveWrapper->Size(); + typeEncoding = primitiveWrapper->TypeEncoding(); + break; + } + case WrapperType::StructType: { + StructTypeWrapper* structTypeWrapper = static_cast(refWrapper->TypeWrapper()); + StructInfo structInfo = structTypeWrapper->StructInfo(); + + size = structInfo.FFIType()->size; + break; + } + default: { + break; + } } + } else { + // TODO: Currently only PrimitiveDataWrappers and Structs are supported as type parameters + // Objective C class classes should also be handled + tns::Assert(false, isolate); } - } else { - isUnknownType = true; - } - - if (isUnknownType) { - // TODO: Currently only PrimitiveDataWrappers and Structs are supported as type parameters - // Objective C class classes should also be handled - tns::Assert(false, isolate); - } - Local value = refWrapper->Value()->Get(isolate); - BaseDataWrapper* wrappedValue = tns::GetValue(isolate, value); - if (wrappedValue != nullptr && wrappedValue->Type() == WrapperType::Pointer) { - PointerWrapper* pw = static_cast(wrappedValue); - void* data = pw->Data(); + Local value = refWrapper->Value()->Get(isolate); + BaseDataWrapper* wrappedValue = tns::GetValue(isolate, value); + if (wrappedValue != nullptr && wrappedValue->Type() == WrapperType::Pointer) { + PointerWrapper* pw = static_cast(wrappedValue); + void* data = pw->Data(); - DataPair pair(typeWrapper, typeEncoding, data, size); - return pair; + DataPair pair(typeWrapper, typeEncoding, data, size); + return pair; + } } if (refWrapper->Encoding() != nullptr && refWrapper->Data() != nullptr) { - DataPair pair(typeWrapper, refWrapper->Encoding(), refWrapper->Data(), size); + const TypeEncoding* typeEncoding = refWrapper->Encoding(); + + if (typeWrapper == nullptr) { + ffi_type* ffiType = FFICall::GetArgumentType(typeEncoding); + size = ffiType->size; + FFICall::DisposeFFIType(ffiType, typeEncoding); + } + + DataPair pair(typeWrapper, typeEncoding, refWrapper->Data(), size); return pair; } tns::Assert(false, isolate); - return DataPair(typeWrapper, nullptr, nullptr, 0); + return DataPair(typeWrapper, nullptr, nullptr, size); } bool Reference::IsSupportedType(WrapperType type) { diff --git a/TestRunner/app/tests/Marshalling/ReferenceTests.js b/TestRunner/app/tests/Marshalling/ReferenceTests.js index e8501809..2d8ebe0d 100644 --- a/TestRunner/app/tests/Marshalling/ReferenceTests.js +++ b/TestRunner/app/tests/Marshalling/ReferenceTests.js @@ -412,18 +412,14 @@ describe(module.id, function () { } const pointCollection = TNSPointCollection.alloc().initWithPointsCount(ptr, length); - - // Runtime converts pointers into references for cases with type so use handleof to get pointer - const pointsPtr = interop.handleof(pointCollection.points); + const pointsRef = pointCollection.points; // Check if values were retrieved from pointer - const resultRef = new interop.Reference(TNSPoint, pointsPtr); for (let i = 0; i < length; i++) { - expect(TNSPoint.equals(resultRef[i], structs[i])).toBe(true); + expect(TNSPoint.equals(pointsRef[i], structs[i])).toBe(true); } interop.free(ptr); - interop.free(pointsPtr); }); it("Reference access value from pointer array property", function () { @@ -441,16 +437,11 @@ describe(module.id, function () { } const pointCollection = TNSPointCollection.alloc().initWithPointsCount(ptr, length); - - // Runtime converts pointers into references for cases with type so use handleof to get pointer - const pointsPtr = interop.handleof(pointCollection.points); + const pointsRef = pointCollection.points; - // Check if values were retrieved from pointer - const resultRef = new interop.Reference(TNSPoint, pointsPtr); - expect(TNSPoint.equals(resultRef.value, structs[0])).toBe(true); + expect(TNSPoint.equals(pointsRef.value, structs[0])).toBe(true); interop.free(ptr); - interop.free(pointsPtr); }); it("interops string from CString with fixed length", function () {