FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

fix(runtime): free struct buffers marshalled from object literals when the call completes by edusperoni · Pull Request #449 · NativeScript/ios · GitHub

Repository navigation

Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension .h  (3) .js  (1) .m  (1) .mm  (1) All 4 file types selected
Viewed files
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Unified
Split
Hide whitespace
Diff view
Unified
Split
Hide whitespace
11 changes: 11 additions & 0 deletions NativeScript/runtime/FFICall.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@

#include <malloc/malloc.h>

#include <cstdlib>
#include <map>
#include <vector>

#include "DataWrapper.h"
#include "Metadata.h"
Expand Down Expand Up @@ -92,11 +94,19 @@ class FFICall : public BaseCall {
}

~FFICall() {
for (void* buffer : this->ownedBuffers_) {
std::free(buffer);
}

if (this->useDynamicBuffer_) {
free(this->buffer_);
}
}

// Ties a malloc'd argument buffer to this call: it stays valid until after
// ffi_call returns and the result has been read.
inline void OwnBuffer(void* buffer) { this->ownedBuffers_.push_back(buffer); }

/**
When calling this, always make another call to DisposeFFIType with the same
parameters
Expand All @@ -122,6 +132,7 @@ class FFICall : public BaseCall {
static SpinMutex structInfosCacheMutex_;
void** argsArray_;
bool useDynamicBuffer_;
std::vector<void*> ownedBuffers_;
uint8_t staticBuffer[512];
};

Expand Down
3 changes: 2 additions & 1 deletion NativeScript/runtime/Interop.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,8 @@ class Interop {
v8::Local<v8::Value> arg);
static void WriteValue(v8::Local<v8::Context> context,
const TypeEncoding* typeEncoding, void* dest,
v8::Local<v8::Value> arg);
v8::Local<v8::Value> arg,
FFICall* callOwner = nullptr);
static id ToObject(v8::Local<v8::Context> context, v8::Local<v8::Value> arg);
static v8::Local<v8::Value> GetPrimitiveReturnType(
v8::Local<v8::Context> context, BinaryTypeEncodingType type,
Expand Down
20 changes: 9 additions & 11 deletions NativeScript/runtime/Interop.mm
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@
enc = enc->next();
Local<Value> arg = args[i - initialParameterIndex];
void* argBuffer = call->ArgumentBuffer(i);
Interop::WriteValue(context, enc, argBuffer, arg);
Interop::WriteValue(context, enc, argBuffer, arg, call);
}
}

Expand Down Expand Up @@ -256,7 +256,7 @@ inline bool isBool() {
}

void Interop::WriteValue(Local<Context> context, const TypeEncoding* typeEncoding, void* dest,
Local<Value> arg) {
Local<Value> arg, FFICall* callOwner) {
Isolate* isolate = v8::Isolate::GetCurrent();
ExecuteWriteValueDebugValidationsIfInDebug(context, typeEncoding, dest, arg);
ValueCache argHelper(arg);
Expand Down Expand Up @@ -433,17 +433,15 @@ inline bool isBool() {
tns::Assert(meta != nullptr && meta->type() == MetaType::Struct, isolate);
const StructMeta* structMeta = static_cast<const StructMeta*>(meta);
StructInfo structInfo = FFICall::GetStructInfo(structMeta);
// TODO: How to free this?
// this is used when you have js obj and wants to pass the data as a struct ponter
// (MyStruct*) we create a new MyStruct with a snapshot of the jsObject and pass that in but
// when should we delete it? currently it's up to the function called to delete it we could
// delete after the function call, but if the fuction stores that then it's a memory leak we
// could also just store it as a wrapper in the object, binding it to the object lifecycle
// but that also means refactoring a lot of "if(wrapper == nullptr)" because essentially the
// wrapper should be treated as a nullptr, except when deating with
// StructDeclarationReference
// A plain JS object written into a MyStruct* slot is snapshotted into a fresh
// buffer. With an owner the callee only borrows it for the duration of the call.
// Without one (writes into an interop.Reference slot) the pointer is stored in
// memory that outlives any call, so the buffer must stay allocated.
data = malloc(structInfo.FFIType()->size);
Interop::InitializeStruct(context, data, structInfo.Fields(), arg);
if (callOwner != nullptr) {
callOwner->OwnBuffer(data);
}
} else {
if (wrapper == nullptr) {
bool isArrayBuffer = false;
Expand Down
4 changes: 4 additions & 0 deletions TestFixtures/TNSTestNativeCallbacks.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@

+ (void)recordsPointer:(TNSSimpleStruct*)object;

// Reads the pointee back out so callers can assert the marshalled values
// without going through the shared log buffer.
+ (TNSSimpleStruct)recordsPointerEcho:(TNSSimpleStruct*)object;

+ (void)apiNSMutableArrayMethods:(NSMutableArray*)object;

+ (void)apiSwizzle:(TNSSwizzleKlass*)object;
Expand Down
4 changes: 4 additions & 0 deletions TestFixtures/TNSTestNativeCallbacks.m
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -330,6 +330,10 @@ + (void)recordsPointer:(TNSSimpleStruct*)object {
TNSLog([NSString stringWithFormat:@"%d %d", object->x, object->y]);
}

+ (TNSSimpleStruct)recordsPointerEcho:(TNSSimpleStruct*)object {
return *object;
}

+ (void)apiNSMutableArrayMethods:(NSMutableArray*)object {
[object addObject:@"b"];
[object addObject:@"x"];
Expand Down
47 changes: 47 additions & 0 deletions TestRunner/app/tests/Marshalling/RecordTests.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode characters
Original file line number Diff line number Diff line change
Expand Up @@ -358,4 +358,51 @@ describe(module.id, function () {
TNSTestNativeCallbacks.recordsPointer(obj);
expect(TNSGetOutput()).toBe("1 2");
});

it("Marshalling struct pointers from object literals repeatedly", () => {
for (let i = 0; i < 1000; i++) {
const echoed = TNSTestNativeCallbacks.recordsPointerEcho({ x: i, y: i + 1 });
expect(echoed instanceof TNSSimpleStruct).toBe(true);
expect(echoed.x).toBe(i);
expect(echoed.y).toBe(i + 1);
}
});

it("Marshalling struct pointers from a wrapped struct leaves the wrapper's buffer intact", () => {
const record = new TNSSimpleStruct();
record.x = 3;
record.y = 4;

for (let i = 0; i < 100; i++) {
const echoed = TNSTestNativeCallbacks.recordsPointerEcho(record);
expect(echoed.x).toBe(3);
expect(echoed.y).toBe(4);
}

expect(record.x).toBe(3);
expect(record.y).toBe(4);
});

it("Marshalling struct pointers from an interop.Reference leaves the reference readable", () => {
const record = new TNSSimpleStruct();
record.x = 5;
record.y = 6;

const reference = new interop.Reference(record);

const echoed = TNSTestNativeCallbacks.recordsPointerEcho(reference);
expect(echoed.x).toBe(5);
expect(echoed.y).toBe(6);

expect(reference.value.x).toBe(5);
expect(reference.value.y).toBe(6);
});

it("Marshalling structs by value from object literals repeatedly", () => {
for (let i = 0; i < 1000; i++) {
const echoed = TNSTestNativeCallbacks.recordsSimpleStruct({ x: i, y: i + 1 });
expect(echoed.x).toBe(i);
expect(echoed.y).toBe(i + 1);
}
});
});
Loading

Back | FazBrowse Home | New Git URL