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 static void tryConvertUTF8ToLatin1(
89 std::basic_string<uint8_t, std::char_traits<uint8_t>>& latin1String,
91 const uint8_t* buffer,
92 const size_t bufferSize,
93 const Encoding encodingHint) {
94 bool isOneByteString = true;
96 if (encodingHint == Encoding::kUnknown || encodingHint == Encoding::kLatin1) {
97 if (UTF8Sequence::convertUTF8ToLatin1(
98 latin1String, buffer, buffer + bufferSize) == false) {
99 isOneByteString = false;
101 } else if (encodingHint == Encoding::kUtf16) {
102 isOneByteString = false;
105 encoding = Encoding::kUnknown;
107 if (isOneByteString) {
108 if (latin1String.length() == bufferSize) {
109 encoding = Encoding::kAscii;
111 encoding = Encoding::kLatin1;
114 encoding = Encoding::kUtf16;
118 FileData Loader::createFileDataForReloadableString(
119 std::string filename,
120 std::unique_ptr<void, std::function<void(void*)>> bufferHolder,
122 const Encoding encodingHint) {
123 std::basic_string<uint8_t, std::char_traits<uint8_t>> latin1String;
124 Encoding encoding = Encoding::kUnknown;
126 if (encodingHint == Encoding::kAscii) {
127 encoding = Encoding::kAscii;
128 } else if (encodingHint == Encoding::kUtf16) {
129 encoding = Encoding::kUtf16;
130 } else if (encodingHint == Encoding::kLatin1) {
131 tryConvertUTF8ToLatin1(latin1String,
133 (uint8_t*)bufferHolder.get(),
137 LWNODE_CHECK(encodingHint == Encoding::kUnknown);
138 tryConvertUTF8ToLatin1(latin1String,
140 (uint8_t*)bufferHolder.get(),
145 if (encoding == Encoding::kUtf16) {
146 // Treat non-latin1 as UTF-8 and encode it as UTF-16 Little Endian.
147 if (encodingHint == Encoding::kUnknown) {
148 LWNODE_LOG_INFO("%s contains characters outside of the Latin1 range.",
152 char* newStringBuffer = nullptr;
153 size_t newStringBufferSize = 0;
155 bool isConverted = convertUTF8ToUTF16le(&newStringBuffer,
156 &newStringBufferSize,
157 (const char*)bufferHolder.get(),
159 if (isConverted == false) {
163 bufferHolder.reset(newStringBuffer);
164 bufferSize = newStringBufferSize;
166 if (encoding == Encoding::kLatin1) {
167 if (encodingHint == Encoding::kUnknown) {
168 LWNODE_LOG_INFO("%s contains Latin1 characters.", filename.c_str());
171 bufferSize = latin1String.length();
172 bufferHolder.reset(allocateStringBuffer(bufferSize + 1));
173 ((uint8_t*)bufferHolder.get())[bufferSize] = '\0';
175 memcpy(bufferHolder.get(), latin1String.data(), bufferSize);
179 return FileData(bufferHolder.release(), bufferSize, encoding, filename);
182 SourceReader* SourceReader::getInstance() {
183 static SourceReader s_singleton;
187 FileData SourceReader::read(std::string filename, const Encoding encodingHint) {
188 FileScope fileScope(filename.c_str(), "rb");
190 std::FILE* file = fileScope.file();
196 std::fseek(file, 0, SEEK_END);
197 long pos = std::ftell(file);
200 LWNODE_CHECK(pos >= 0);
202 size_t bufferSize = (size_t)pos;
203 uint8_t* buffer = (uint8_t*)allocateStringBuffer(bufferSize + 1);
204 buffer[bufferSize] = '\0';
206 std::unique_ptr<void, std::function<void(void*)>> bufferHolder(
207 buffer, freeStringBuffer);
209 if (std::fread(buffer, sizeof(uint8_t), bufferSize, file) == 0) {
213 return Loader::createFileDataForReloadableString(
214 filename, std::move(bufferHolder), bufferSize, encodingHint);
217 Loader::ReloadableSourceData* Loader::ReloadableSourceData::create(
218 const FileData fileData, SourceReaderInterface* sourceReader) {
219 // NOTE: data and data->path should be managed by gc
220 auto data = new (Memory::gcMalloc(sizeof(ReloadableSourceData)))
221 ReloadableSourceData();
223 auto& sourcePath = fileData.filePath;
224 data->path_ = (char*)Memory::gcMallocAtomic(sourcePath.size() + 1);
225 std::copy(sourcePath.begin(), sourcePath.end(), data->path_);
226 data->path_[sourcePath.size()] = '\0';
228 data->preloadedData = fileData.buffer;
229 data->preloadedDataLength_ = fileData.size;
230 data->encoding_ = fileData.encoding;
231 data->sourceReader_ = sourceReader;
242 ValueRef* Loader::CreateReloadableSourceFromFile(ExecutionStateRef* state,
243 std::string fileName) {
244 auto lwContext = ContextWrap::fromEscargot(state->context());
245 auto isolate = lwContext->GetIsolate()->toV8();
247 auto sourceReader = SourceReader::getInstance();
248 FileData fileData = sourceReader->read(fileName, Encoding::kUnknown);
250 if (fileData.buffer == nullptr) {
251 return ValueRef::createUndefined();
254 HandleScope handleScope(isolate);
256 v8::Local<v8::String> source =
257 NewReloadableString(isolate,
258 ReloadableSourceData::create(fileData, sourceReader))
261 return CVAL(*source)->value()->asString();
264 MaybeLocal<String> Loader::NewReloadableString(Isolate* isolate,
265 ReloadableSourceData* data,
266 LoadCallback loadCallback,
267 UnloadCallback unloadCallback) {
268 MaybeLocal<String> result;
270 if (data->stringLength() == 0) {
271 result = String::Empty(isolate);
272 } else if (data->stringLength() > v8::String::kMaxLength) {
273 result = MaybeLocal<String>();
275 if (loadCallback == nullptr && unloadCallback == nullptr) {
276 // set default load/unload callbacks
277 loadCallback = [](void* userData) -> void* {
278 auto data = reinterpret_cast<Loader::ReloadableSourceData*>(userData);
280 LWNODE_LOG_INFO(" Load: %d (%d) %p %s (+%.2f kB)",
285 (float)data->preloadedDataLength() / 1024);
287 if (data->preloadedData) {
288 auto buffer = data->preloadedData;
289 data->preloadedData = nullptr;
290 return buffer; // move memory ownership to js engine
295 data->sourceReader()->read(data->path(), data->encoding());
297 LWNODE_CHECK_NOT_NULL(fileData.buffer);
298 return fileData.buffer;
301 unloadCallback = [](void* preloadedData, void* userData) -> void {
302 auto data = reinterpret_cast<Loader::ReloadableSourceData*>(userData);
304 LWNODE_LOG_INFO(" Unload: %d (%d) %p %s (-%.2f kB)",
309 (float)data->preloadedDataLength() / 1024);
311 if (data->preloadedData) {
312 freeStringBuffer(data->preloadedData);
313 data->preloadedData = nullptr;
315 freeStringBuffer(preloadedData);
319 Escargot::StringRef* reloadableString =
320 Escargot::StringRef::createReloadableString(
321 IsolateWrap::fromV8(isolate)->vmInstance(),
322 data->isOneByteString(),
323 data->stringLength(),
324 data, // data should be gc-managed.
328 result = v8::Utils::NewLocal<String>(isolate, reloadableString);
334 } // namespace LWNode