01f0dea239301c8d5586d94ebd04d2fa9c5f353e
[WebKit-https.git] / Source / JavaScriptCore / API / JSScript.mm
1 /*
2  * Copyright (C) 2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27 #import "JSScriptInternal.h"
28
29 #import "APICast.h"
30 #import "CachedTypes.h"
31 #import "CodeCache.h"
32 #import "Identifier.h"
33 #import "JSContextInternal.h"
34 #import "JSScriptSourceProvider.h"
35 #import "JSSourceCode.h"
36 #import "JSValuePrivate.h"
37 #import "JSVirtualMachineInternal.h"
38 #import "ParserError.h"
39 #import "Symbol.h"
40 #include <sys/stat.h>
41 #include <wtf/FileSystem.h>
42
43 #if JSC_OBJC_API_ENABLED
44
45 @implementation JSScript {
46     __weak JSVirtualMachine* m_virtualMachine;
47     JSScriptType m_type;
48     FileSystem::MappedFileData m_mappedSource;
49     String m_source;
50     RetainPtr<NSURL> m_sourceURL;
51     RetainPtr<NSURL> m_cachePath;
52     JSC::CachedBytecode m_cachedBytecode;
53     JSC::Strong<JSC::JSSourceCode> m_jsSourceCode;
54     int m_cacheFileDescriptor;
55 }
56
57 + (instancetype)scriptWithSource:(NSString *)source inVirtualMachine:(JSVirtualMachine *)vm
58 {
59     JSScript *result = [[[JSScript alloc] init] autorelease];
60     result->m_source = source;
61     result->m_virtualMachine = vm;
62     result->m_type = kJSScriptTypeModule;
63     return result;
64 }
65
66 template<typename Vector>
67 static bool fillBufferWithContentsOfFile(FILE* file, Vector& buffer)
68 {
69     // We might have injected "use strict"; at the top.
70     size_t initialSize = buffer.size();
71     if (fseek(file, 0, SEEK_END) == -1)
72         return false;
73     long bufferCapacity = ftell(file);
74     if (bufferCapacity == -1)
75         return false;
76     if (fseek(file, 0, SEEK_SET) == -1)
77         return false;
78     buffer.resize(bufferCapacity + initialSize);
79     size_t readSize = fread(buffer.data() + initialSize, 1, buffer.size(), file);
80     return readSize == buffer.size() - initialSize;
81 }
82
83 static bool fillBufferWithContentsOfFile(const String& fileName, Vector<LChar>& buffer)
84 {
85     FILE* f = fopen(fileName.utf8().data(), "rb");
86     if (!f) {
87         fprintf(stderr, "Could not open file: %s\n", fileName.utf8().data());
88         return false;
89     }
90
91     bool result = fillBufferWithContentsOfFile(f, buffer);
92     fclose(f);
93
94     return result;
95 }
96
97
98 + (instancetype)scriptFromASCIIFile:(NSURL *)filePath inVirtualMachine:(JSVirtualMachine *)vm withCodeSigning:(NSURL *)codeSigningPath andBytecodeCache:(NSURL *)cachePath
99 {
100     UNUSED_PARAM(codeSigningPath);
101     UNUSED_PARAM(cachePath);
102
103     URL filePathURL([filePath absoluteURL]);
104     if (!filePathURL.isLocalFile())
105         return nil;
106
107     Vector<LChar> buffer;
108     if (!fillBufferWithContentsOfFile(filePathURL.fileSystemPath(), buffer))
109         return nil;
110
111     if (!charactersAreAllASCII(buffer.data(), buffer.size()))
112         return nil;
113
114     JSScript *result = [[[JSScript alloc] init] autorelease];
115     result->m_virtualMachine = vm;
116     result->m_source = String::fromUTF8WithLatin1Fallback(buffer.data(), buffer.size());
117     result->m_type = kJSScriptTypeModule;
118     return result;
119 }
120
121 + (instancetype)scriptFromUTF8File:(NSURL *)filePath inVirtualMachine:(JSVirtualMachine *)vm withCodeSigning:(NSURL *)codeSigningPath andBytecodeCache:(NSURL *)cachePath
122 {
123     return [JSScript scriptFromASCIIFile:filePath inVirtualMachine:vm withCodeSigning:codeSigningPath andBytecodeCache:cachePath];
124 }
125
126 static JSScript *createError(NSString *message, NSError** error)
127 {
128     if (error)
129         *error = [NSError errorWithDomain:@"JSScriptErrorDomain" code:1 userInfo:@{ @"message": message }];
130     return nil;
131 }
132
133 + (instancetype)scriptOfType:(JSScriptType)type withSource:(NSString *)source andSourceURL:(NSURL *)sourceURL andBytecodeCache:(NSURL *)cachePath inVirtualMachine:(JSVirtualMachine *)vm error:(out NSError **)error
134 {
135     UNUSED_PARAM(error);
136     JSScript *result = [[[JSScript alloc] init] autorelease];
137     result->m_virtualMachine = vm;
138     result->m_type = type;
139     result->m_source = source;
140     result->m_sourceURL = sourceURL;
141     result->m_cachePath = cachePath;
142     [result readCache];
143     return result;
144 }
145
146 + (instancetype)scriptOfType:(JSScriptType)type memoryMappedFromASCIIFile:(NSURL *)filePath withSourceURL:(NSURL *)sourceURL andBytecodeCache:(NSURL *)cachePath inVirtualMachine:(JSVirtualMachine *)vm error:(out NSError **)error
147 {
148     UNUSED_PARAM(error);
149     URL filePathURL([filePath absoluteURL]);
150     if (!filePathURL.isLocalFile())
151         return createError([NSString stringWithFormat:@"File path %@ is not a local file", static_cast<NSString *>(filePathURL)], error);
152
153     bool success = false;
154     String systemPath = filePathURL.fileSystemPath();
155     FileSystem::MappedFileData fileData(systemPath, success);
156     if (!success)
157         return createError([NSString stringWithFormat:@"File at path %@ could not be mapped.", static_cast<NSString *>(systemPath)], error);
158
159     if (!charactersAreAllASCII(reinterpret_cast<const LChar*>(fileData.data()), fileData.size()))
160         return createError([NSString stringWithFormat:@"Not all characters in file at %@ are ASCII.", static_cast<NSString *>(systemPath)], error);
161
162     JSScript *result = [[[JSScript alloc] init] autorelease];
163     result->m_virtualMachine = vm;
164     result->m_type = type;
165     result->m_source = String(StringImpl::createWithoutCopying(bitwise_cast<const LChar*>(fileData.data()), fileData.size()));
166     result->m_mappedSource = WTFMove(fileData);
167     result->m_sourceURL = sourceURL;
168     result->m_cachePath = cachePath;
169     [result readCache];
170     return result;
171 }
172
173 - (void)dealloc
174 {
175     if (m_cachedBytecode.size() && !m_cachedBytecode.owned())
176         munmap(const_cast<void*>(m_cachedBytecode.data()), m_cachedBytecode.size());
177
178     if (m_cacheFileDescriptor != -1)
179         close(m_cacheFileDescriptor);
180
181     [super dealloc];
182 }
183
184 - (void)readCache
185 {
186     if (!m_cachePath)
187         return;
188
189     m_cacheFileDescriptor = open([m_cachePath path].UTF8String, O_CREAT | O_RDWR | O_EXLOCK | O_NONBLOCK, 0666);
190     if (m_cacheFileDescriptor == -1)
191         return;
192
193     struct stat sb;
194     int res = fstat(m_cacheFileDescriptor, &sb);
195     size_t size = static_cast<size_t>(sb.st_size);
196     if (res || !size)
197         return;
198
199     void* buffer = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, m_cacheFileDescriptor, 0);
200
201     JSC::CachedBytecode cachedBytecode { buffer, size };
202
203     JSC::VM& vm = m_virtualMachine.vm;
204     const JSC::SourceCode& sourceCode = [self jsSourceCode]->sourceCode();
205     JSC::SourceCodeKey key = m_type == kJSScriptTypeProgram ? sourceCodeKeyForSerializedProgram(vm, sourceCode) : sourceCodeKeyForSerializedModule(vm, sourceCode);
206     if (isCachedBytecodeStillValid(vm, cachedBytecode, key, m_type == kJSScriptTypeProgram ? JSC::SourceCodeType::ProgramType : JSC::SourceCodeType::ModuleType))
207         m_cachedBytecode = WTFMove(cachedBytecode);
208     else
209         ftruncate(m_cacheFileDescriptor, 0);
210 }
211
212 - (BOOL)cacheBytecodeWithError:(NSError **)error
213 {
214     String errorString { };
215     [self writeCache:errorString];
216     if (!errorString.isNull()) {
217         createError(errorString, error);
218         return NO;
219     }
220
221     return YES;
222 }
223
224 - (NSURL *)sourceURL
225 {
226     return m_sourceURL.get();
227 }
228
229 - (JSScriptType)type
230 {
231     return m_type;
232 }
233
234 @end
235
236 @implementation JSScript(Internal)
237
238 - (instancetype)init
239 {
240     self = [super init];
241     if (!self)
242         return nil;
243
244     m_cacheFileDescriptor = -1;
245     return self;
246 }
247
248 - (unsigned)hash
249 {
250     return m_source.hash();
251 }
252
253 - (const String&)source
254 {
255     return m_source;
256 }
257
258 - (const JSC::CachedBytecode*)cachedBytecode
259 {
260     return &m_cachedBytecode;
261 }
262
263 - (JSC::JSSourceCode*)jsSourceCode
264 {
265     if (m_jsSourceCode)
266         return m_jsSourceCode.get();
267
268     return [self forceRecreateJSSourceCode];
269 }
270
271 - (BOOL)writeCache:(String&)error
272 {
273     if (m_cachedBytecode.size()) {
274         error = "Cache for JSScript is already non-empty. Can not override it."_s;
275         return NO;
276     }
277
278     if (m_cacheFileDescriptor == -1) {
279         if (!m_cachePath)
280             error = "No cache was path provided during construction of this JSScript."_s;
281         else
282             error = "Could not lock the bytecode cache file. It's likely another VM or process is already using it."_s;
283         return NO;
284     }
285
286     JSC::ParserError parserError;
287     switch (m_type) {
288     case kJSScriptTypeModule:
289         m_cachedBytecode = JSC::generateModuleBytecode(m_virtualMachine.vm, [self jsSourceCode]->sourceCode(), parserError);
290         break;
291     case kJSScriptTypeProgram:
292         m_cachedBytecode = JSC::generateProgramBytecode(m_virtualMachine.vm, [self jsSourceCode]->sourceCode(), parserError);
293         break;
294     }
295
296     if (parserError.isValid()) {
297         m_cachedBytecode = { };
298         error = makeString("Unable to generate bytecode for this JSScript because of a parser error: ", parserError.message());
299         return NO;
300     }
301
302     ssize_t bytesWritten = write(m_cacheFileDescriptor, m_cachedBytecode.data(), m_cachedBytecode.size());
303     if (bytesWritten == -1) {
304         error = makeString("Could not write cache file to disk: ", strerror(errno));
305         return NO;
306     }
307
308     if (static_cast<size_t>(bytesWritten) != m_cachedBytecode.size()) {
309         ftruncate(m_cacheFileDescriptor, 0);
310         error = makeString("Could not write the full cache file to disk. Only wrote ", String::number(bytesWritten), " of the expected ", String::number(m_cachedBytecode.size()), " bytes.");
311         return NO;
312     }
313
314     return YES;
315 }
316
317 - (void)setSourceURL:(NSURL *)url
318 {
319     m_sourceURL = url;
320 }
321
322 - (JSC::JSSourceCode*)forceRecreateJSSourceCode
323 {
324     JSC::VM& vm = m_virtualMachine.vm;
325     JSC::JSLockHolder locker(vm);
326
327     TextPosition startPosition { };
328
329     String url = String { [[self sourceURL] absoluteString] };
330     auto type = m_type == kJSScriptTypeModule ? JSC::SourceProviderSourceType::Module : JSC::SourceProviderSourceType::Program;
331     Ref<JSScriptSourceProvider> sourceProvider = JSScriptSourceProvider::create(self, JSC::SourceOrigin(url), URL({ }, url), startPosition, type);
332     JSC::SourceCode sourceCode(WTFMove(sourceProvider), startPosition.m_line.oneBasedInt(), startPosition.m_column.oneBasedInt());
333     JSC::JSSourceCode* jsSourceCode = JSC::JSSourceCode::create(vm, WTFMove(sourceCode));
334     m_jsSourceCode.set(vm, jsSourceCode);
335     return jsSourceCode;
336 }
337
338 @end
339
340 #endif