/* * Copyright (C) 2019 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #import "config.h" #import "JSScriptInternal.h" #import "APICast.h" #import "BytecodeCacheError.h" #import "CachedTypes.h" #import "CodeCache.h" #import "Identifier.h" #import "JSContextInternal.h" #import "JSScriptSourceProvider.h" #import "JSSourceCode.h" #import "JSValuePrivate.h" #import "JSVirtualMachineInternal.h" #import "Symbol.h" #import #import #import #import #import #import #ifdef DARLING_NONUNIFIED_BUILD #include "runtime/Completion.h" #endif #if JSC_OBJC_API_ENABLED @implementation JSScript { WeakObjCPtr m_virtualMachine; JSScriptType m_type; FileSystem::MappedFileData m_mappedSource; String m_source; RetainPtr m_sourceURL; RetainPtr m_cachePath; RefPtr m_cachedBytecode; } static JSScript *createError(NSString *message, NSError** error) { if (error) *error = [NSError errorWithDomain:@"JSScriptErrorDomain" code:1 userInfo:@{ @"message": message }]; return nil; } static bool validateBytecodeCachePath(NSURL* cachePath, NSError** error) { if (!cachePath) return true; URL cachePathURL([cachePath absoluteURL]); if (!cachePathURL.isLocalFile()) { createError([NSString stringWithFormat:@"Cache path `%@` is not a local file", static_cast(cachePathURL)], error); return false; } String systemPath = cachePathURL.fileSystemPath(); if (auto metadata = FileSystem::fileMetadata(systemPath)) { if (metadata->type != FileMetadata::Type::File) { createError([NSString stringWithFormat:@"Cache path `%@` already exists and is not a file", static_cast(systemPath)], error); return false; } } String directory = FileSystem::directoryName(systemPath); if (directory.isNull()) { createError([NSString stringWithFormat:@"Cache path `%@` does not contain in a valid directory", static_cast(systemPath)], error); return false; } if (!FileSystem::fileIsDirectory(directory, FileSystem::ShouldFollowSymbolicLinks::No)) { createError([NSString stringWithFormat:@"Cache directory `%@` is not a directory or does not exist", static_cast(directory)], error); return false; } #if USE(APPLE_INTERNAL_SDK) if (rootless_check_datavault_flag(FileSystem::fileSystemRepresentation(directory).data(), nullptr)) { createError([NSString stringWithFormat:@"Cache directory `%@` is not a data vault", static_cast(directory)], error); return false; } #endif return true; } + (instancetype)scriptOfType:(JSScriptType)type withSource:(NSString *)source andSourceURL:(NSURL *)sourceURL andBytecodeCache:(NSURL *)cachePath inVirtualMachine:(JSVirtualMachine *)vm error:(out NSError **)error { if (!validateBytecodeCachePath(cachePath, error)) return nil; JSScript *result = [[[JSScript alloc] init] autorelease]; result->m_virtualMachine = vm; result->m_type = type; result->m_source = source; result->m_sourceURL = sourceURL; result->m_cachePath = cachePath; [result readCache]; return result; } + (instancetype)scriptOfType:(JSScriptType)type memoryMappedFromASCIIFile:(NSURL *)filePath withSourceURL:(NSURL *)sourceURL andBytecodeCache:(NSURL *)cachePath inVirtualMachine:(JSVirtualMachine *)vm error:(out NSError **)error { if (!validateBytecodeCachePath(cachePath, error)) return nil; URL filePathURL([filePath absoluteURL]); if (!filePathURL.isLocalFile()) return createError([NSString stringWithFormat:@"File path %@ is not a local file", static_cast(filePathURL)], error); bool success = false; String systemPath = filePathURL.fileSystemPath(); FileSystem::MappedFileData fileData(systemPath, success); if (!success) return createError([NSString stringWithFormat:@"File at path %@ could not be mapped.", static_cast(systemPath)], error); if (!charactersAreAllASCII(reinterpret_cast(fileData.data()), fileData.size())) return createError([NSString stringWithFormat:@"Not all characters in file at %@ are ASCII.", static_cast(systemPath)], error); JSScript *result = [[[JSScript alloc] init] autorelease]; result->m_virtualMachine = vm; result->m_type = type; result->m_source = String(StringImpl::createWithoutCopying(bitwise_cast(fileData.data()), fileData.size())); result->m_mappedSource = WTFMove(fileData); result->m_sourceURL = sourceURL; result->m_cachePath = cachePath; [result readCache]; return result; } - (void)readCache { if (!m_cachePath) return; int fd = open([m_cachePath path].UTF8String, O_RDONLY | O_EXLOCK | O_NONBLOCK, 0666); if (fd == -1) return; auto closeFD = makeScopeExit([&] { close(fd); }); struct stat sb; int res = fstat(fd, &sb); size_t size = static_cast(sb.st_size); if (res || !size) return; void* buffer = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0); Ref cachedBytecode = JSC::CachedBytecode::create(buffer, size); JSC::VM& vm = [m_virtualMachine vm]; JSC::SourceCode sourceCode = [self sourceCode]; JSC::SourceCodeKey key = m_type == kJSScriptTypeProgram ? sourceCodeKeyForSerializedProgram(vm, sourceCode) : sourceCodeKeyForSerializedModule(vm, sourceCode); if (isCachedBytecodeStillValid(vm, cachedBytecode.copyRef(), key, m_type == kJSScriptTypeProgram ? JSC::SourceCodeType::ProgramType : JSC::SourceCodeType::ModuleType)) m_cachedBytecode = WTFMove(cachedBytecode); else ftruncate(fd, 0); } - (BOOL)cacheBytecodeWithError:(NSError **)error { String errorString { }; [self writeCache:errorString]; if (!errorString.isNull()) { createError(errorString, error); return NO; } return YES; } - (BOOL)isUsingBytecodeCache { return !!m_cachedBytecode->size(); } - (NSURL *)sourceURL { return m_sourceURL.get(); } - (JSScriptType)type { return m_type; } @end @implementation JSScript(Internal) - (instancetype)init { self = [super init]; if (!self) return nil; self->m_cachedBytecode = JSC::CachedBytecode::create(); return self; } - (unsigned)hash { return m_source.hash(); } - (const String&)source { return m_source; } - (RefPtr)cachedBytecode { return m_cachedBytecode; } - (JSC::SourceCode)sourceCode { JSC::VM& vm = [m_virtualMachine vm]; JSC::JSLockHolder locker(vm); TextPosition startPosition { }; String url = String { [[self sourceURL] absoluteString] }; auto type = m_type == kJSScriptTypeModule ? JSC::SourceProviderSourceType::Module : JSC::SourceProviderSourceType::Program; Ref sourceProvider = JSScriptSourceProvider::create(self, JSC::SourceOrigin(url), URL({ }, url), startPosition, type); JSC::SourceCode sourceCode(WTFMove(sourceProvider), startPosition.m_line.oneBasedInt(), startPosition.m_column.oneBasedInt()); return sourceCode; } - (JSC::JSSourceCode*)jsSourceCode { JSC::VM& vm = [m_virtualMachine vm]; JSC::JSLockHolder locker(vm); JSC::JSSourceCode* jsSourceCode = JSC::JSSourceCode::create(vm, [self sourceCode]); return jsSourceCode; } - (BOOL)writeCache:(String&)error { if (self.isUsingBytecodeCache) { error = "Cache for JSScript is already non-empty. Can not override it."_s; return NO; } if (!m_cachePath) { error = "No cache path was provided during construction of this JSScript."_s; return NO; } int fd = open([m_cachePath path].UTF8String, O_CREAT | O_RDWR | O_EXLOCK | O_NONBLOCK, 0666); if (fd == -1) { error = makeString("Could not open or lock the bytecode cache file. It's likely another VM or process is already using it. Error: ", strerror(errno)); return NO; } auto closeFD = makeScopeExit([&] { close(fd); }); JSC::BytecodeCacheError cacheError; JSC::SourceCode sourceCode = [self sourceCode]; switch (m_type) { case kJSScriptTypeModule: m_cachedBytecode = JSC::generateModuleBytecode([m_virtualMachine vm], sourceCode, fd, cacheError); break; case kJSScriptTypeProgram: m_cachedBytecode = JSC::generateProgramBytecode([m_virtualMachine vm], sourceCode, fd, cacheError); break; } if (cacheError.isValid()) { m_cachedBytecode = JSC::CachedBytecode::create(); ftruncate(fd, 0); error = makeString("Unable to generate bytecode for this JSScript because: ", cacheError.message()); return NO; } return YES; } @end #endif