LWNode_Release_211109_610b330
[platform/framework/web/lwnode.git] / lwnode / code / escargotshim / src / lwnode / lwnode-loader.cc
1 /*
2  * Copyright (c) 2021-present Samsung Electronics Co., Ltd
3  *
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
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 #include "lwnode-loader.h"
18
19 #include <EscargotPublic.h>
20 #include <codecvt>
21 #include <fstream>
22 #include <locale>
23 #include <string>
24 #include "api.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"
30 #include "base.h"
31
32 using namespace EscargotShim;
33 using namespace Escargot;
34 using namespace v8;
35
36 namespace LWNode {
37
38 void* allocateStringBuffer(size_t size) {
39   return malloc(size);
40 }
41
42 void freeStringBuffer(void* ptr) {
43   free(ptr);
44 }
45
46 bool convertUTF8ToUTF16le(char** buffer,
47                           size_t* bufferSize,
48                           const char* utf8Buffer,
49                           const size_t utf8BufferSize) {
50   LWNODE_CHECK_NOT_NULL(buffer);
51   LWNODE_CHECK_NOT_NULL(bufferSize);
52
53   std::wstring_convert<
54       std::codecvt_utf8_utf16<char16_t,
55                               0x10ffff,
56                               std::codecvt_mode::little_endian>,
57       char16_t>
58       convertor;
59   std::u16string utf16 = convertor.from_bytes(utf8Buffer);
60
61   if (convertor.converted() < utf8BufferSize) {
62     LWNODE_LOG_ERROR("Invalid conversion");
63     return false;
64   }
65
66   size_t utf16Size = utf16.size() * 2;
67
68   *buffer = (char*)allocateStringBuffer(utf16Size + 1);
69   memcpy(*buffer, utf16.data(), utf16Size);
70   (*buffer)[utf16Size] = '\0';
71
72   *bufferSize = utf16Size;
73   return true;
74 }
75
76 class FileScope {
77  public:
78   FileScope(const char* path, const char* mode) {
79     file_ = std::fopen(path, mode);
80   }
81   ~FileScope() { std::fclose(file_); }
82   std::FILE* file() { return file_; }
83
84  private:
85   std::FILE* file_{nullptr};
86 };
87
88 static void tryConvertUTF8ToLatin1(
89     std::basic_string<uint8_t, std::char_traits<uint8_t>>& latin1String,
90     Encoding& encoding,
91     const uint8_t* buffer,
92     const size_t bufferSize,
93     const Encoding encodingHint) {
94   bool isOneByteString = true;
95
96   if (encodingHint == Encoding::kUnknown || encodingHint == Encoding::kLatin1) {
97     if (UTF8Sequence::convertUTF8ToLatin1(
98             latin1String, buffer, buffer + bufferSize) == false) {
99       isOneByteString = false;
100     }
101   } else if (encodingHint == Encoding::kUtf16) {
102     isOneByteString = false;
103   }
104
105   encoding = Encoding::kUnknown;
106
107   if (isOneByteString) {
108     if (latin1String.length() == bufferSize) {
109       encoding = Encoding::kAscii;
110     } else {
111       encoding = Encoding::kLatin1;
112     }
113   } else {
114     encoding = Encoding::kUtf16;
115   }
116 }
117
118 FileData Loader::createFileDataForReloadableString(
119     std::string filename,
120     std::unique_ptr<void, std::function<void(void*)>> bufferHolder,
121     size_t bufferSize,
122     const Encoding encodingHint) {
123   std::basic_string<uint8_t, std::char_traits<uint8_t>> latin1String;
124   Encoding encoding = Encoding::kUnknown;
125
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,
132                            encoding,
133                            (uint8_t*)bufferHolder.get(),
134                            bufferSize,
135                            encodingHint);
136   } else {
137     LWNODE_CHECK(encodingHint == Encoding::kUnknown);
138     tryConvertUTF8ToLatin1(latin1String,
139                            encoding,
140                            (uint8_t*)bufferHolder.get(),
141                            bufferSize,
142                            encodingHint);
143   }
144
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.",
149                       filename.c_str());
150     }
151
152     char* newStringBuffer = nullptr;
153     size_t newStringBufferSize = 0;
154
155     bool isConverted = convertUTF8ToUTF16le(&newStringBuffer,
156                                             &newStringBufferSize,
157                                             (const char*)bufferHolder.get(),
158                                             bufferSize);
159     if (isConverted == false) {
160       return FileData();
161     }
162
163     bufferHolder.reset(newStringBuffer);
164     bufferSize = newStringBufferSize;
165   } else {
166     if (encoding == Encoding::kLatin1) {
167       if (encodingHint == Encoding::kUnknown) {
168         LWNODE_LOG_INFO("%s contains Latin1 characters.", filename.c_str());
169       }
170
171       bufferSize = latin1String.length();
172       bufferHolder.reset(allocateStringBuffer(bufferSize + 1));
173       ((uint8_t*)bufferHolder.get())[bufferSize] = '\0';
174
175       memcpy(bufferHolder.get(), latin1String.data(), bufferSize);
176     }
177   }
178
179   return FileData(bufferHolder.release(), bufferSize, encoding, filename);
180 }
181
182 SourceReader* SourceReader::getInstance() {
183   static SourceReader s_singleton;
184   return &s_singleton;
185 }
186
187 FileData SourceReader::read(std::string filename, const Encoding encodingHint) {
188   FileScope fileScope(filename.c_str(), "rb");
189
190   std::FILE* file = fileScope.file();
191
192   if (!file) {
193     return FileData();
194   }
195
196   std::fseek(file, 0, SEEK_END);
197   long pos = std::ftell(file);
198   std::rewind(file);
199
200   LWNODE_CHECK(pos >= 0);
201
202   size_t bufferSize = (size_t)pos;
203   uint8_t* buffer = (uint8_t*)allocateStringBuffer(bufferSize + 1);
204   buffer[bufferSize] = '\0';
205
206   std::unique_ptr<void, std::function<void(void*)>> bufferHolder(
207       buffer, freeStringBuffer);
208
209   if (std::fread(buffer, sizeof(uint8_t), bufferSize, file) == 0) {
210     return FileData();
211   }
212
213   return Loader::createFileDataForReloadableString(
214       filename, std::move(bufferHolder), bufferSize, encodingHint);
215 }
216
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();
222
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';
227
228   data->preloadedData = fileData.buffer;
229   data->preloadedDataLength_ = fileData.size;
230   data->encoding_ = fileData.encoding;
231   data->sourceReader_ = sourceReader;
232
233   return data;
234 }
235
236 struct Stat {
237   int loaded{0};
238   int reloaded{0};
239 };
240 static Stat s_stat;
241
242 ValueRef* Loader::CreateReloadableSourceFromFile(ExecutionStateRef* state,
243                                                  std::string fileName) {
244   auto lwContext = ContextWrap::fromEscargot(state->context());
245   auto isolate = lwContext->GetIsolate()->toV8();
246
247   auto sourceReader = SourceReader::getInstance();
248   FileData fileData = sourceReader->read(fileName, Encoding::kUnknown);
249
250   if (fileData.buffer == nullptr) {
251     return ValueRef::createUndefined();
252   }
253
254   HandleScope handleScope(isolate);
255
256   v8::Local<v8::String> source =
257       NewReloadableString(isolate,
258                           ReloadableSourceData::create(fileData, sourceReader))
259           .ToLocalChecked();
260
261   return CVAL(*source)->value()->asString();
262 }
263
264 MaybeLocal<String> Loader::NewReloadableString(Isolate* isolate,
265                                                ReloadableSourceData* data,
266                                                LoadCallback loadCallback,
267                                                UnloadCallback unloadCallback) {
268   MaybeLocal<String> result;
269
270   if (data->stringLength() == 0) {
271     result = String::Empty(isolate);
272   } else if (data->stringLength() > v8::String::kMaxLength) {
273     result = MaybeLocal<String>();
274   } else {
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);
279
280         LWNODE_LOG_INFO("  Load: %d (%d) %p %s (+%.2f kB)",
281                         ++s_stat.loaded,
282                         s_stat.reloaded,
283                         data->preloadedData,
284                         data->path(),
285                         (float)data->preloadedDataLength() / 1024);
286
287         if (data->preloadedData) {
288           auto buffer = data->preloadedData;
289           data->preloadedData = nullptr;
290           return buffer;  // move memory ownership to js engine
291         }
292         s_stat.reloaded++;
293
294         FileData fileData =
295             data->sourceReader()->read(data->path(), data->encoding());
296
297         LWNODE_CHECK_NOT_NULL(fileData.buffer);
298         return fileData.buffer;
299       };
300
301       unloadCallback = [](void* preloadedData, void* userData) -> void {
302         auto data = reinterpret_cast<Loader::ReloadableSourceData*>(userData);
303
304         LWNODE_LOG_INFO(" Unload: %d (%d) %p %s (-%.2f kB)",
305                         --s_stat.loaded,
306                         s_stat.reloaded,
307                         preloadedData,
308                         data->path(),
309                         (float)data->preloadedDataLength() / 1024);
310
311         if (data->preloadedData) {
312           freeStringBuffer(data->preloadedData);
313           data->preloadedData = nullptr;
314         }
315         freeStringBuffer(preloadedData);
316       };
317     }
318
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.
325             loadCallback,
326             unloadCallback);
327
328     result = v8::Utils::NewLocal<String>(isolate, reloadableString);
329   }
330
331   return result;
332 }
333
334 }  // namespace LWNode