2 * Copyright (c) 2021-present Samsung Electronics Co., Ltd
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 #include "lwnode-loader.h"
19 #include <EscargotPublic.h>
25 #include "api/context.h"
26 #include "api/es-helper.h"
27 #include "api/isolate.h"
28 #include "api/utils/misc.h"
29 #include "api/utils/string-util.h"
32 using namespace EscargotShim;
33 using namespace Escargot;
38 void* allocateStringBuffer(size_t size) {
42 void freeStringBuffer(void* ptr) {
46 bool convertUTF8ToUTF16le(char** buffer,
48 const char* utf8Buffer,
49 const size_t utf8BufferSize) {
50 LWNODE_CHECK_NOT_NULL(buffer);
51 LWNODE_CHECK_NOT_NULL(bufferSize);
54 std::codecvt_utf8_utf16<char16_t,
56 std::codecvt_mode::little_endian>,
59 std::u16string utf16 = convertor.from_bytes(utf8Buffer);
61 if (convertor.converted() < utf8BufferSize) {
62 LWNODE_LOG_ERROR("Invalid conversion");
66 size_t utf16Size = utf16.size() * 2;
68 *buffer = (char*)allocateStringBuffer(utf16Size + 1);
69 memcpy(*buffer, utf16.data(), utf16Size);
70 (*buffer)[utf16Size] = '\0';
72 *bufferSize = utf16Size;
78 FileScope(const char* path, const char* mode) {
79 file_ = std::fopen(path, mode);
81 ~FileScope() { std::fclose(file_); }
82 std::FILE* file() { return file_; }
85 std::FILE* file_{nullptr};
88 void Loader::tryConvertUTF8ToLatin1(U8String& latin1String,
90 const uint8_t* buffer,
91 const size_t bufferSize,
92 const Encoding encodingHint) {
93 bool isOneByteString = true;
95 if (encodingHint == UNKNOWN || encodingHint == ONE_BYTE_LATIN1) {
96 if (UTF8Sequence::convertUTF8ToLatin1(
97 latin1String, buffer, buffer + bufferSize) == false) {
98 isOneByteString = false;
100 } else if (encodingHint == TWO_BYTE) {
101 isOneByteString = false;
106 if (isOneByteString) {
107 if (latin1String.length() == bufferSize) {
110 encoding = ONE_BYTE_LATIN1;
117 FileData Loader::readFile(std::string filename, const Encoding encodingHint) {
118 FileScope fileScope(filename.c_str(), "rb");
120 std::FILE* file = fileScope.file();
126 std::fseek(file, 0, SEEK_END);
127 long pos = std::ftell(file);
130 LWNODE_CHECK(pos >= 0);
132 size_t bufferSize = (size_t)pos;
133 uint8_t* buffer = (uint8_t*)allocateStringBuffer(bufferSize + 1);
134 buffer[bufferSize] = '\0';
136 std::unique_ptr<void, std::function<void(void*)>> bufferHolder(
137 buffer, freeStringBuffer);
139 if (std::fread(buffer, sizeof(uint8_t), bufferSize, file) == 0) {
143 Loader::U8String latin1String;
144 Encoding encoding = UNKNOWN;
146 if (encodingHint == ONE_BYTE) {
148 } else if (encodingHint == TWO_BYTE) {
150 } else if (encodingHint == ONE_BYTE_LATIN1) {
151 Loader::tryConvertUTF8ToLatin1(
152 latin1String, encoding, buffer, bufferSize, encodingHint);
154 LWNODE_CHECK(encodingHint == UNKNOWN);
155 Loader::tryConvertUTF8ToLatin1(
156 latin1String, encoding, buffer, bufferSize, encodingHint);
159 if (encoding == TWO_BYTE) {
160 // Treat non-latin1 as UTF-8 and encode it as UTF-16 Little Endian.
161 if (encodingHint == UNKNOWN) {
162 LWNODE_LOG_INFO("%s contains characters outside of the Latin1 range.",
166 char* newStringBuffer = nullptr;
167 size_t newStringBufferSize = 0;
169 bool isConverted = convertUTF8ToUTF16le(&newStringBuffer,
170 &newStringBufferSize,
171 (const char*)bufferHolder.get(),
173 if (isConverted == false) {
177 bufferHolder.reset(newStringBuffer);
178 bufferSize = newStringBufferSize;
180 if (encoding == ONE_BYTE_LATIN1) {
181 if (encodingHint == UNKNOWN) {
182 LWNODE_LOG_INFO("%s contains Latin1 characters.", filename.c_str());
185 bufferSize = latin1String.length();
186 bufferHolder.reset(allocateStringBuffer(bufferSize + 1));
187 ((uint8_t*)bufferHolder.get())[bufferSize] = '\0';
189 memcpy(bufferHolder.get(), latin1String.data(), bufferSize);
193 return FileData(bufferHolder.release(), bufferSize, encoding);
196 Loader::ReloadableSourceData* Loader::ReloadableSourceData::create(
197 std::string sourcePath,
199 size_t preloadedDataLength,
201 // NOTE: data and data->path should be managed by gc
202 auto data = new (Memory::gcMalloc(sizeof(ReloadableSourceData)))
203 ReloadableSourceData();
204 data->path_ = (char*)Memory::gcMallocAtomic(sourcePath.size() + 1);
205 std::copy(sourcePath.begin(), sourcePath.end(), data->path_);
206 data->path_[sourcePath.size()] = '\0';
208 data->preloadedData = preloadedData;
209 data->preloadedDataLength_ = preloadedDataLength;
210 data->encoding_ = encoding;
221 ValueRef* Loader::CreateReloadableSourceFromFile(ExecutionStateRef* state,
222 std::string fileName) {
223 auto lwContext = ContextWrap::fromEscargot(state->context());
224 auto isolate = lwContext->GetIsolate()->toV8();
226 FileData dest = Loader::readFile(fileName, UNKNOWN);
229 auto data = Loader::ReloadableSourceData::create(
230 fileName, dest.buffer, dest.size, dest.encoding);
232 HandleScope handleScope(isolate);
234 v8::Local<v8::String> source =
235 Loader::NewReloadableString(
238 // Load-ReloadableSource
239 [](void* userData) -> void* {
241 reinterpret_cast<Loader::ReloadableSourceData*>(userData);
243 LWNODE_LOG_INFO(" * Load: %d (%d) %p %s (+%.2f kB)",
248 (float)data->preloadedDataLength() / 1024);
250 if (data->preloadedData) {
251 auto buffer = data->preloadedData;
252 data->preloadedData = nullptr;
253 return buffer; // move memory ownership to js engine
257 FileData dest = Loader::readFile(data->path(), data->encoding());
258 LWNODE_CHECK_NOT_NULL(dest.buffer);
261 // Unload-ReloadableSource
262 [](void* preloadedData, void* userData) -> void {
264 reinterpret_cast<Loader::ReloadableSourceData*>(userData);
266 LWNODE_LOG_INFO("* Unload: %d (%d) %p %s (-%.2f kB)",
271 (float)data->preloadedDataLength() / 1024);
273 if (data->preloadedData) {
274 freeStringBuffer(data->preloadedData);
275 data->preloadedData = nullptr;
277 freeStringBuffer(preloadedData);
281 return CVAL(*source)->value()->asString();
284 return ValueRef::createUndefined();
287 MaybeLocal<String> Loader::NewReloadableString(Isolate* isolate,
288 ReloadableSourceData* data,
289 LoadCallback loadCallback,
290 UnloadCallback unloadCallback) {
291 MaybeLocal<String> result;
293 if (data->stringLength() == 0) {
294 result = String::Empty(isolate);
295 } else if (data->stringLength() > v8::String::kMaxLength) {
296 result = MaybeLocal<String>();
298 Escargot::StringRef* reloadableString =
299 Escargot::StringRef::createReloadableString(
300 IsolateWrap::fromV8(isolate)->vmInstance(),
301 data->isOneByteString(),
302 data->stringLength(),
303 data, // data should be gc-managed.
306 result = v8::Utils::NewLocal<String>(isolate, reloadableString);
312 } // namespace LWNode