diff --git a/NativeScript/runtime/Interop.mm b/NativeScript/runtime/Interop.mm index ac140efa..19671455 100644 --- a/NativeScript/runtime/Interop.mm +++ b/NativeScript/runtime/Interop.mm @@ -365,16 +365,13 @@ static bool SnapshotArrayLike(Isolate* isolate, Local object, Local()); + // The external string only ties the copy's lifetime to the GC; it is + // never read as a string. bool success = v8::String::NewExternalOneByte(isolate, resource).ToLocal(&arg); tns::Assert(success, isolate); - Interop::SetValue(dest, value); + Interop::SetValue(dest, resource->data()); } else { BaseDataWrapper* wrapper = tns::GetValue(isolate, arg); if (wrapper == nullptr) { diff --git a/NativeScript/runtime/OneByteStringResource.cpp b/NativeScript/runtime/OneByteStringResource.cpp index e10086ca..b8d724e3 100644 --- a/NativeScript/runtime/OneByteStringResource.cpp +++ b/NativeScript/runtime/OneByteStringResource.cpp @@ -1,22 +1,35 @@ #include "OneByteStringResource.h" #include +#include using namespace v8; namespace tns { -OneByteStringResource::OneByteStringResource(const char* data, size_t length): - data_(data), length_(length) { +OneByteStringResource* OneByteStringResource::NewUtf8Copy(Isolate* isolate, + Local str) { + size_t byteLength = str->Utf8LengthV2(isolate); + void* block = std::malloc(sizeof(OneByteStringResource) + byteLength + 1); + OneByteStringResource* resource = + new (block) OneByteStringResource(byteLength + 1); + // Same flags as String::Utf8Value. + str->WriteUtf8V2(isolate, reinterpret_cast(resource + 1), + byteLength + 1, + String::WriteFlags::kNullTerminate | + String::WriteFlags::kReplaceInvalidUtf8); + return resource; } -OneByteStringResource::~OneByteStringResource() { - // data_ comes from strdup (see Interop::WriteValue's CStringEncoding path). - std::free(const_cast(this->data_)); +OneByteStringResource::OneByteStringResource(size_t length) : length_(length) {} + +void OneByteStringResource::Dispose() { + this->~OneByteStringResource(); + std::free(this); } const char* OneByteStringResource::data() const { - return this->data_; + return reinterpret_cast(this + 1); } size_t OneByteStringResource::length() const { diff --git a/NativeScript/runtime/OneByteStringResource.h b/NativeScript/runtime/OneByteStringResource.h index 0cf0a8fe..e49a572d 100644 --- a/NativeScript/runtime/OneByteStringResource.h +++ b/NativeScript/runtime/OneByteStringResource.h @@ -5,17 +5,28 @@ namespace tns { -class OneByteStringResource : public v8::String::ExternalOneByteStringResource { -public: - OneByteStringResource(const char* data, size_t length); - ~OneByteStringResource() override; - const char* data() const override; - size_t length() const override; -private: - const char* data_; - size_t length_; -}; +// A NUL-terminated UTF-8 copy of a JS string, stored in the same allocation as +// the resource. V8 releases it through Dispose(), never through delete. +class OneByteStringResource final + : public v8::String::ExternalOneByteStringResource { + public: + static OneByteStringResource* NewUtf8Copy(v8::Isolate* isolate, + v8::Local str); + + const char* data() const override; + // The UTF-8 byte count plus the NUL, so it is never zero: V8 disposes a + // zero-length resource as soon as it is handed one. + size_t length() const override; + + protected: + void Dispose() override; + private: + explicit OneByteStringResource(size_t length); + ~OneByteStringResource() override = default; + + size_t length_; +}; } #endif /* OneByteStringResource_h */