bd19e698573ce4a7383367b6e7d2828328807db2
[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 void Loader::tryConvertUTF8ToLatin1(U8String& latin1String,
89                                     Encoding& encoding,
90                                     const uint8_t* buffer,
91                                     const size_t bufferSize,
92                                     const Encoding encodingHint) {
93   bool isOneByteString = true;
94
95   if (encodingHint == UNKNOWN || encodingHint == ONE_BYTE_LATIN1) {
96     if (UTF8Sequence::convertUTF8ToLatin1(
97             latin1String, buffer, buffer + bufferSize) == false) {
98       isOneByteString = false;
99     }
100   } else if (encodingHint == TWO_BYTE) {
101     isOneByteString = false;
102   }
103
104   encoding = UNKNOWN;
105
106   if (isOneByteString) {
107     if (latin1String.length() == bufferSize) {
108       encoding = ONE_BYTE;
109     } else {
110       encoding = ONE_BYTE_LATIN1;
111     }
112   } else {
113     encoding = TWO_BYTE;
114   }
115 }
116
117 FileData Loader::readFile(std::string filename, const Encoding encodingHint) {
118   FileScope fileScope(filename.c_str(), "rb");
119
120   std::FILE* file = fileScope.file();
121
122   if (!file) {
123     return FileData();
124   }
125
126   std::fseek(file, 0, SEEK_END);
127   long pos = std::ftell(file);
128   std::rewind(file);
129
130   LWNODE_CHECK(pos >= 0);
131
132   size_t bufferSize = (size_t)pos;
133   uint8_t* buffer = (uint8_t*)allocateStringBuffer(bufferSize + 1);
134   buffer[bufferSize] = '\0';
135
136   std::unique_ptr<void, std::function<void(void*)>> bufferHolder(
137       buffer, freeStringBuffer);
138
139   if (std::fread(buffer, sizeof(uint8_t), bufferSize, file) == 0) {
140     return FileData();
141   }
142
143   Loader::U8String latin1String;
144   Encoding encoding = UNKNOWN;
145
146   if (encodingHint == ONE_BYTE) {
147     encoding = ONE_BYTE;
148   } else if (encodingHint == TWO_BYTE) {
149     encoding = TWO_BYTE;
150   } else if (encodingHint == ONE_BYTE_LATIN1) {
151     Loader::tryConvertUTF8ToLatin1(
152         latin1String, encoding, buffer, bufferSize, encodingHint);
153   } else {
154     LWNODE_CHECK(encodingHint == UNKNOWN);
155     Loader::tryConvertUTF8ToLatin1(
156         latin1String, encoding, buffer, bufferSize, encodingHint);
157   }
158
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.",
163                       filename.c_str());
164     }
165
166     char* newStringBuffer = nullptr;
167     size_t newStringBufferSize = 0;
168
169     bool isConverted = convertUTF8ToUTF16le(&newStringBuffer,
170                                             &newStringBufferSize,
171                                             (const char*)bufferHolder.get(),
172                                             bufferSize);
173     if (isConverted == false) {
174       return FileData();
175     }
176
177     bufferHolder.reset(newStringBuffer);
178     bufferSize = newStringBufferSize;
179   } else {
180     if (encoding == ONE_BYTE_LATIN1) {
181       if (encodingHint == UNKNOWN) {
182         LWNODE_LOG_INFO("%s contains Latin1 characters.", filename.c_str());
183       }
184
185       bufferSize = latin1String.length();
186       bufferHolder.reset(allocateStringBuffer(bufferSize + 1));
187       ((uint8_t*)bufferHolder.get())[bufferSize] = '\0';
188
189       memcpy(bufferHolder.get(), latin1String.data(), bufferSize);
190     }
191   }
192
193   return FileData(bufferHolder.release(), bufferSize, encoding);
194 }
195
196 Loader::ReloadableSourceData* Loader::ReloadableSourceData::create(
197     std::string sourcePath,
198     void* preloadedData,
199     size_t preloadedDataLength,
200     Encoding encoding) {
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';
207
208   data->preloadedData = preloadedData;
209   data->preloadedDataLength_ = preloadedDataLength;
210   data->encoding_ = encoding;
211
212   return data;
213 }
214
215 struct Stat {
216   int loaded{0};
217   int reloaded{0};
218 };
219 static Stat s_stat;
220
221 ValueRef* Loader::CreateReloadableSourceFromFile(ExecutionStateRef* state,
222                                                  std::string fileName) {
223   auto lwContext = ContextWrap::fromEscargot(state->context());
224   auto isolate = lwContext->GetIsolate()->toV8();
225
226   FileData dest = Loader::readFile(fileName, UNKNOWN);
227
228   if (dest.buffer) {
229     auto data = Loader::ReloadableSourceData::create(
230         fileName, dest.buffer, dest.size, dest.encoding);
231
232     HandleScope handleScope(isolate);
233
234     v8::Local<v8::String> source =
235         Loader::NewReloadableString(
236             isolate,
237             data,
238             // Load-ReloadableSource
239             [](void* userData) -> void* {
240               auto data =
241                   reinterpret_cast<Loader::ReloadableSourceData*>(userData);
242
243               LWNODE_LOG_INFO("  * Load: %d (%d) %p %s (+%.2f kB)",
244                               ++s_stat.loaded,
245                               s_stat.reloaded,
246                               data->preloadedData,
247                               data->path(),
248                               (float)data->preloadedDataLength() / 1024);
249
250               if (data->preloadedData) {
251                 auto buffer = data->preloadedData;
252                 data->preloadedData = nullptr;
253                 return buffer;  // move memory ownership to js engine
254               }
255               s_stat.reloaded++;
256
257               FileData dest = Loader::readFile(data->path(), data->encoding());
258               LWNODE_CHECK_NOT_NULL(dest.buffer);
259               return dest.buffer;
260             },
261             // Unload-ReloadableSource
262             [](void* preloadedData, void* userData) -> void {
263               auto data =
264                   reinterpret_cast<Loader::ReloadableSourceData*>(userData);
265
266               LWNODE_LOG_INFO("* Unload: %d (%d) %p %s (-%.2f kB)",
267                               --s_stat.loaded,
268                               s_stat.reloaded,
269                               preloadedData,
270                               data->path(),
271                               (float)data->preloadedDataLength() / 1024);
272
273               if (data->preloadedData) {
274                 freeStringBuffer(data->preloadedData);
275                 data->preloadedData = nullptr;
276               }
277               freeStringBuffer(preloadedData);
278             })
279             .ToLocalChecked();
280
281     return CVAL(*source)->value()->asString();
282   }
283
284   return ValueRef::createUndefined();
285 }
286
287 MaybeLocal<String> Loader::NewReloadableString(Isolate* isolate,
288                                                ReloadableSourceData* data,
289                                                LoadCallback loadCallback,
290                                                UnloadCallback unloadCallback) {
291   MaybeLocal<String> result;
292
293   if (data->stringLength() == 0) {
294     result = String::Empty(isolate);
295   } else if (data->stringLength() > v8::String::kMaxLength) {
296     result = MaybeLocal<String>();
297   } else {
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.
304             loadCallback,
305             unloadCallback);
306     result = v8::Utils::NewLocal<String>(isolate, reloadableString);
307   }
308
309   return result;
310 }
311
312 }  // namespace LWNode