| 1 | /* |
| 2 | * Copyright (C) 2015-2017 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 | #include "config.h" |
| 27 | #include "AbstractModuleRecord.h" |
| 28 | |
| 29 | #include "Error.h" |
| 30 | #include "Interpreter.h" |
| 31 | #include "JSCInlines.h" |
| 32 | #include "JSMap.h" |
| 33 | #include "JSModuleEnvironment.h" |
| 34 | #include "JSModuleNamespaceObject.h" |
| 35 | #include "JSModuleRecord.h" |
| 36 | #include "UnlinkedModuleProgramCodeBlock.h" |
| 37 | #include "WebAssemblyModuleRecord.h" |
| 38 | #include <wtf/Optional.h> |
| 39 | |
| 40 | namespace JSC { |
| 41 | namespace AbstractModuleRecordInternal { |
| 42 | static const bool verbose = false; |
| 43 | } // namespace AbstractModuleRecordInternal |
| 44 | |
| 45 | const ClassInfo AbstractModuleRecord::s_info = { "AbstractModuleRecord" , &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(AbstractModuleRecord) }; |
| 46 | |
| 47 | AbstractModuleRecord::AbstractModuleRecord(VM& vm, Structure* structure, const Identifier& moduleKey) |
| 48 | : Base(vm, structure) |
| 49 | , m_moduleKey(moduleKey) |
| 50 | { |
| 51 | } |
| 52 | |
| 53 | void AbstractModuleRecord::destroy(JSCell* cell) |
| 54 | { |
| 55 | AbstractModuleRecord* thisObject = static_cast<AbstractModuleRecord*>(cell); |
| 56 | thisObject->AbstractModuleRecord::~AbstractModuleRecord(); |
| 57 | } |
| 58 | |
| 59 | void AbstractModuleRecord::finishCreation(ExecState* exec, VM& vm) |
| 60 | { |
| 61 | Base::finishCreation(vm); |
| 62 | ASSERT(inherits(vm, info())); |
| 63 | |
| 64 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 65 | JSMap* map = JSMap::create(exec, vm, globalObject(vm)->mapStructure()); |
| 66 | scope.releaseAssertNoException(); |
| 67 | m_dependenciesMap.set(vm, this, map); |
| 68 | putDirect(vm, Identifier::fromString(&vm, "dependenciesMap"_s ), m_dependenciesMap.get()); |
| 69 | } |
| 70 | |
| 71 | void AbstractModuleRecord::visitChildren(JSCell* cell, SlotVisitor& visitor) |
| 72 | { |
| 73 | AbstractModuleRecord* thisObject = jsCast<AbstractModuleRecord*>(cell); |
| 74 | Base::visitChildren(thisObject, visitor); |
| 75 | visitor.append(thisObject->m_moduleEnvironment); |
| 76 | visitor.append(thisObject->m_moduleNamespaceObject); |
| 77 | visitor.append(thisObject->m_dependenciesMap); |
| 78 | } |
| 79 | |
| 80 | void AbstractModuleRecord::appendRequestedModule(const Identifier& moduleName) |
| 81 | { |
| 82 | m_requestedModules.add(moduleName.impl()); |
| 83 | } |
| 84 | |
| 85 | void AbstractModuleRecord::addStarExportEntry(const Identifier& moduleName) |
| 86 | { |
| 87 | m_starExportEntries.add(moduleName.impl()); |
| 88 | } |
| 89 | |
| 90 | void AbstractModuleRecord::addImportEntry(const ImportEntry& entry) |
| 91 | { |
| 92 | bool isNewEntry = m_importEntries.add(entry.localName.impl(), entry).isNewEntry; |
| 93 | ASSERT_UNUSED(isNewEntry, isNewEntry); // This is guaranteed by the parser. |
| 94 | } |
| 95 | |
| 96 | void AbstractModuleRecord::addExportEntry(const ExportEntry& entry) |
| 97 | { |
| 98 | bool isNewEntry = m_exportEntries.add(entry.exportName.impl(), entry).isNewEntry; |
| 99 | ASSERT_UNUSED(isNewEntry, isNewEntry); // This is guaranteed by the parser. |
| 100 | } |
| 101 | |
| 102 | auto AbstractModuleRecord::tryGetImportEntry(UniquedStringImpl* localName) -> Optional<ImportEntry> |
| 103 | { |
| 104 | const auto iterator = m_importEntries.find(localName); |
| 105 | if (iterator == m_importEntries.end()) |
| 106 | return WTF::nullopt; |
| 107 | return Optional<ImportEntry>(iterator->value); |
| 108 | } |
| 109 | |
| 110 | auto AbstractModuleRecord::tryGetExportEntry(UniquedStringImpl* exportName) -> Optional<ExportEntry> |
| 111 | { |
| 112 | const auto iterator = m_exportEntries.find(exportName); |
| 113 | if (iterator == m_exportEntries.end()) |
| 114 | return WTF::nullopt; |
| 115 | return Optional<ExportEntry>(iterator->value); |
| 116 | } |
| 117 | |
| 118 | auto AbstractModuleRecord::ExportEntry::createLocal(const Identifier& exportName, const Identifier& localName) -> ExportEntry |
| 119 | { |
| 120 | return ExportEntry { Type::Local, exportName, Identifier(), Identifier(), localName }; |
| 121 | } |
| 122 | |
| 123 | auto AbstractModuleRecord::ExportEntry::createIndirect(const Identifier& exportName, const Identifier& importName, const Identifier& moduleName) -> ExportEntry |
| 124 | { |
| 125 | return ExportEntry { Type::Indirect, exportName, moduleName, importName, Identifier() }; |
| 126 | } |
| 127 | |
| 128 | auto AbstractModuleRecord::Resolution::notFound() -> Resolution |
| 129 | { |
| 130 | return Resolution { Type::NotFound, nullptr, Identifier() }; |
| 131 | } |
| 132 | |
| 133 | auto AbstractModuleRecord::Resolution::error() -> Resolution |
| 134 | { |
| 135 | return Resolution { Type::Error, nullptr, Identifier() }; |
| 136 | } |
| 137 | |
| 138 | auto AbstractModuleRecord::Resolution::ambiguous() -> Resolution |
| 139 | { |
| 140 | return Resolution { Type::Ambiguous, nullptr, Identifier() }; |
| 141 | } |
| 142 | |
| 143 | AbstractModuleRecord* AbstractModuleRecord::hostResolveImportedModule(ExecState* exec, const Identifier& moduleName) |
| 144 | { |
| 145 | VM& vm = exec->vm(); |
| 146 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 147 | JSValue moduleNameValue = identifierToJSValue(vm, moduleName); |
| 148 | JSValue entry = m_dependenciesMap->JSMap::get(exec, moduleNameValue); |
| 149 | RETURN_IF_EXCEPTION(scope, nullptr); |
| 150 | RELEASE_AND_RETURN(scope, jsCast<AbstractModuleRecord*>(entry.get(exec, Identifier::fromString(exec, "module" )))); |
| 151 | } |
| 152 | |
| 153 | auto AbstractModuleRecord::resolveImport(ExecState* exec, const Identifier& localName) -> Resolution |
| 154 | { |
| 155 | VM& vm = exec->vm(); |
| 156 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 157 | |
| 158 | Optional<ImportEntry> optionalImportEntry = tryGetImportEntry(localName.impl()); |
| 159 | if (!optionalImportEntry) |
| 160 | return Resolution::notFound(); |
| 161 | |
| 162 | const ImportEntry& importEntry = *optionalImportEntry; |
| 163 | if (importEntry.type == AbstractModuleRecord::ImportEntryType::Namespace) |
| 164 | return Resolution::notFound(); |
| 165 | |
| 166 | AbstractModuleRecord* importedModule = hostResolveImportedModule(exec, importEntry.moduleRequest); |
| 167 | RETURN_IF_EXCEPTION(scope, Resolution::error()); |
| 168 | return importedModule->resolveExport(exec, importEntry.importName); |
| 169 | } |
| 170 | |
| 171 | struct AbstractModuleRecord::ResolveQuery { |
| 172 | struct Hash { |
| 173 | static unsigned hash(const ResolveQuery&); |
| 174 | static bool equal(const ResolveQuery&, const ResolveQuery&); |
| 175 | static const bool safeToCompareToEmptyOrDeleted = true; |
| 176 | }; |
| 177 | using HashTraits = WTF::CustomHashTraits<ResolveQuery>; |
| 178 | |
| 179 | ResolveQuery(AbstractModuleRecord* moduleRecord, UniquedStringImpl* exportName) |
| 180 | : moduleRecord(moduleRecord) |
| 181 | , exportName(exportName) |
| 182 | { |
| 183 | } |
| 184 | |
| 185 | ResolveQuery(AbstractModuleRecord* moduleRecord, const Identifier& exportName) |
| 186 | : ResolveQuery(moduleRecord, exportName.impl()) |
| 187 | { |
| 188 | } |
| 189 | |
| 190 | enum EmptyValueTag { EmptyValue }; |
| 191 | ResolveQuery(EmptyValueTag) |
| 192 | { |
| 193 | } |
| 194 | |
| 195 | enum DeletedValueTag { DeletedValue }; |
| 196 | ResolveQuery(DeletedValueTag) |
| 197 | : moduleRecord(nullptr) |
| 198 | , exportName(WTF::HashTableDeletedValue) |
| 199 | { |
| 200 | } |
| 201 | |
| 202 | bool isEmptyValue() const |
| 203 | { |
| 204 | return !exportName; |
| 205 | } |
| 206 | |
| 207 | bool isDeletedValue() const |
| 208 | { |
| 209 | return exportName.isHashTableDeletedValue(); |
| 210 | } |
| 211 | |
| 212 | void dump(PrintStream& out) const |
| 213 | { |
| 214 | if (!moduleRecord) { |
| 215 | out.print("<empty>" ); |
| 216 | return; |
| 217 | } |
| 218 | out.print(moduleRecord->moduleKey(), " \"" , exportName.get(), "\"" ); |
| 219 | } |
| 220 | |
| 221 | // The module record is not marked from the GC. But these records are reachable from the JSGlobalObject. |
| 222 | // So we don't care the reachability to this record. |
| 223 | AbstractModuleRecord* moduleRecord; |
| 224 | RefPtr<UniquedStringImpl> exportName; |
| 225 | }; |
| 226 | |
| 227 | inline unsigned AbstractModuleRecord::ResolveQuery::Hash::hash(const ResolveQuery& query) |
| 228 | { |
| 229 | return WTF::PtrHash<AbstractModuleRecord*>::hash(query.moduleRecord) + IdentifierRepHash::hash(query.exportName); |
| 230 | } |
| 231 | |
| 232 | inline bool AbstractModuleRecord::ResolveQuery::Hash::equal(const ResolveQuery& lhs, const ResolveQuery& rhs) |
| 233 | { |
| 234 | return lhs.moduleRecord == rhs.moduleRecord && lhs.exportName == rhs.exportName; |
| 235 | } |
| 236 | |
| 237 | auto AbstractModuleRecord::tryGetCachedResolution(UniquedStringImpl* exportName) -> Optional<Resolution> |
| 238 | { |
| 239 | const auto iterator = m_resolutionCache.find(exportName); |
| 240 | if (iterator == m_resolutionCache.end()) |
| 241 | return WTF::nullopt; |
| 242 | return Optional<Resolution>(iterator->value); |
| 243 | } |
| 244 | |
| 245 | void AbstractModuleRecord::cacheResolution(UniquedStringImpl* exportName, const Resolution& resolution) |
| 246 | { |
| 247 | m_resolutionCache.add(exportName, resolution); |
| 248 | } |
| 249 | |
| 250 | auto AbstractModuleRecord::resolveExportImpl(ExecState* exec, const ResolveQuery& root) -> Resolution |
| 251 | { |
| 252 | VM& vm = exec->vm(); |
| 253 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 254 | |
| 255 | if (AbstractModuleRecordInternal::verbose) |
| 256 | dataLog("Resolving " , root, "\n" ); |
| 257 | |
| 258 | // https://tc39.github.io/ecma262/#sec-resolveexport |
| 259 | |
| 260 | // How to avoid C++ recursion in this function: |
| 261 | // This function avoids C++ recursion of the naive ResolveExport implementation. |
| 262 | // Flatten the recursion to the loop with the task queue and frames. |
| 263 | // |
| 264 | // 1. pendingTasks |
| 265 | // We enqueue the recursive resolveExport call to this queue to avoid recursive calls in C++. |
| 266 | // The task has 3 types. (1) Query, (2) IndirectFallback and (3) GatherStars. |
| 267 | // (1) Query |
| 268 | // Querying the resolution to the current module. |
| 269 | // (2) IndirectFallback |
| 270 | // Examine the result of the indirect export resolution. Only when the indirect export resolution fails, |
| 271 | // we look into the star exports. (step 5-a-vi). |
| 272 | // (3) GatherStars |
| 273 | // Examine the result of the star export resolutions. |
| 274 | // |
| 275 | // 2. frames |
| 276 | // When the spec calls the resolveExport recursively, instead we append the frame |
| 277 | // (that holds the result resolution) to the frames and enqueue the task to the pendingTasks. |
| 278 | // The entry in the frames means the *local* resolution result of the specific recursive resolveExport. |
| 279 | // |
| 280 | // We should maintain the local resolution result instead of holding the global resolution result only. |
| 281 | // For example, |
| 282 | // |
| 283 | // star |
| 284 | // (1) ---> (2) "Resolve" |
| 285 | // | |
| 286 | // | |
| 287 | // +-> (3) "NotFound" |
| 288 | // | |
| 289 | // | star |
| 290 | // +-> (4) ---> (5) "Resolve" [here] |
| 291 | // | |
| 292 | // | |
| 293 | // +-> (6) "Error" |
| 294 | // |
| 295 | // Consider the above graph. The numbers represents the modules. Now we are [here]. |
| 296 | // If we only hold the global resolution result during the resolveExport operation, [here], |
| 297 | // we decide the entire result of resolveExport is "Ambiguous", because there are multiple |
| 298 | // "Resolve" (in module (2) and (5)). However, this should become "Error" because (6) will |
| 299 | // propagate "Error" state to the (4), (4) will become "Error" and then, (1) will become |
| 300 | // "Error". We should aggregate the results at the star exports point ((4) and (1)). |
| 301 | // |
| 302 | // Usually, both "Error" and "Ambiguous" states will throw the syntax error. So except for the content of the |
| 303 | // error message, there are no difference. (And if we fix the (6) that raises "Error", next, it will produce |
| 304 | // the "Ambiguous" error due to (5). Anyway, user need to fix the both. So which error should be raised at first |
| 305 | // doesn't matter so much. |
| 306 | // |
| 307 | // However, this may become the problem under the module namespace creation. |
| 308 | // http://www.ecma-international.org/ecma-262/6.0/#sec-getmodulenamespace |
| 309 | // section 15.2.1.18, step 3-d-ii |
| 310 | // Here, we distinguish "Ambiguous" and "Error". When "Error" state is produced, we need to throw the propagated error. |
| 311 | // But if "Ambiguous" state comes, we just ignore the result. |
| 312 | // To follow the requirement strictly, in this implementation, we keep the local resolution result to produce the |
| 313 | // correct result under the above complex cases. |
| 314 | |
| 315 | // Caching strategy: |
| 316 | // The resolveExport operation is frequently called. So caching results is important. |
| 317 | // We observe the following aspects and based on them construct the caching strategy. |
| 318 | // Here, we attempt to cache the resolution by constructing the map in module records. |
| 319 | // That means Module -> ExportName -> Maybe<Resolution>. |
| 320 | // Technically, all the AbstractModuleRecords have the Map<ExportName, Resolution> for caching. |
| 321 | // |
| 322 | // The important observations are that, |
| 323 | // |
| 324 | // - *cacheable* means that traversing to this node from a path will produce the same results as starting from this node. |
| 325 | // |
| 326 | // Here, we define the resovling route. We represent [?] as the module that has the local binding. |
| 327 | // And (?) as the module without the local binding. |
| 328 | // |
| 329 | // @ -> (A) -> (B) -> [C] |
| 330 | // |
| 331 | // We list the resolving route for each node. |
| 332 | // |
| 333 | // (A): (A) -> (B) -> [C] |
| 334 | // (B): (B) -> [C] |
| 335 | // [C]: [C] |
| 336 | // |
| 337 | // In this case, if we start the tracing from (B), the resolving route becomes (B) -> [C]. |
| 338 | // So this is the same. At that time, we can say (B) is cacheable in the first tracing. |
| 339 | // |
| 340 | // - The cache ability of a node depends on the resolving route from this node. |
| 341 | // |
| 342 | // 1. The starting point is always cacheable. |
| 343 | // |
| 344 | // 2. A module that has resolved a local binding is always cacheable. |
| 345 | // |
| 346 | // @ -> (A) -> [B] |
| 347 | // |
| 348 | // In the above case, we can see the [B] as cacheable. |
| 349 | // This is because when starting from [B] node, we immediately resolve with the local binding. |
| 350 | // So the resolving route from [B] does not depend on the starting point. |
| 351 | // |
| 352 | // 3. If we don't follow any star links during the resolution, we can see all the traced nodes are cacheable. |
| 353 | // |
| 354 | // If there are non star links, it means that there is *no branch* in the module dependency graph. |
| 355 | // This *no branch* feature makes all the modules cachable. |
| 356 | // |
| 357 | // I.e, if we traverse one star link (even if we successfully resolve that star link), |
| 358 | // we must still traverse all other star links. I would also explain we don't run into |
| 359 | // this when resolving a local/indirect link. When resolving a local/indirect link, |
| 360 | // we won't traverse any star links. |
| 361 | // And since the module can hold only one local/indirect link for the specific export name (if there |
| 362 | // are multiple local/indirect links that has the same export name, it should be syntax error in the |
| 363 | // parsing phase.), there is no multiple outgoing links from a module. |
| 364 | // |
| 365 | // @ -> (A) --> (B) -> [C] -> (D) -> (E) -+ |
| 366 | // ^ | |
| 367 | // | | |
| 368 | // +------------------------+ |
| 369 | // |
| 370 | // When starting from @, [C] will be found as the module resolving the given binding. |
| 371 | // In this case, (B) can cache this resolution. Since the resolving route is the same to the one when |
| 372 | // starting from (B). After caching the above result, we attempt to resolve the same binding from (D). |
| 373 | // |
| 374 | // @ |
| 375 | // | |
| 376 | // v |
| 377 | // @ -> (A) --> (B) -> [C] -> (D) -> (E) -+ |
| 378 | // ^ | |
| 379 | // | | |
| 380 | // +------------------------+ |
| 381 | // |
| 382 | // In this case, we can use the (B)'s cached result. And (E) can be cached. |
| 383 | // |
| 384 | // (E): The resolving route is now (E) -> (B) -> [C]. That is the same when starting from (E). |
| 385 | // |
| 386 | // No branching makes that the problematic *once-visited* node cannot be seen. |
| 387 | // The *once-visited* node makes the resolving route changed since when we see the *once-visited* node, |
| 388 | // we stop tracing this. |
| 389 | // |
| 390 | // If there is no star links and if we look *once-visited* node under no branching graph, *once-visited* |
| 391 | // node cannot resolve the requested binding. If the *once-visited* node can resolve the binding, we |
| 392 | // should have already finished the resolution before reaching this *once-visited* node. |
| 393 | // |
| 394 | // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache. |
| 395 | // |
| 396 | // Star links are only the way to introduce branch. |
| 397 | // Once we follow the star links during the resolution, we cannot cache naively. |
| 398 | // This is because the cacheability depends on the resolving route. And branching produces the problematic *once-visited* |
| 399 | // nodes. Since we don't follow the *once-visited* node, the resolving route from the node becomes different from |
| 400 | // the resolving route when starting from this node. |
| 401 | // |
| 402 | // The following example explains when we should not retrieve the cache and cache the result. |
| 403 | // |
| 404 | // +----> (D) ------+ |
| 405 | // | | |
| 406 | // | v |
| 407 | // (A) *----+----> (B) ---> [C] |
| 408 | // ^ |
| 409 | // | |
| 410 | // @ |
| 411 | // |
| 412 | // When starting from (B), we find [C]. In this resolving route, we don't find any star link. |
| 413 | // And by definition, (B) and [C] are cachable. (B) is the starting point. And [C] has the local binding. |
| 414 | // |
| 415 | // +----> (D) ------+ |
| 416 | // | | |
| 417 | // | v |
| 418 | // @-> (A) *----+----> (B) ---> [C] |
| 419 | // |
| 420 | // But when starting from (A), we should not get the value from the cache. Because, |
| 421 | // |
| 422 | // 1. When looking (D), we reach [C] and make both resolved. |
| 423 | // 2. When looking (B), if we retrieved the last cache from (B), (B) becomes resolved. |
| 424 | // 3. But actually, (B) is not-found in this trial because (C) is already *once-visited*. |
| 425 | // 4. If we accidentally make (B) resolved, (A) becomes ambiguous. But the correct answer is resolved. |
| 426 | // |
| 427 | // Why is this problem caused? This is because the *once-visited* node makes the result not-found. |
| 428 | // In the second trial, (B) -> [C] result is changed from resolved to not-found. |
| 429 | // |
| 430 | // When does this become a problem? If the status of the *once-visited* node group is resolved, |
| 431 | // changing the result to not-found makes the result changed. |
| 432 | // |
| 433 | // This problem does not happen when we don't see any star link yet. Now, consider the minimum case. |
| 434 | // |
| 435 | // @-> (A) -> [ some graph ] |
| 436 | // ^ | |
| 437 | // | | |
| 438 | // +------------+ |
| 439 | // |
| 440 | // In (A), we don't see any star link yet. So we can say that all the visited nodes does not have any local |
| 441 | // resolution. Because if they had a local/indirect resolution, we should have already finished the tracing. |
| 442 | // |
| 443 | // And even if the some graph will see the *once-visited* node (in this case, (A)), that does not affect the |
| 444 | // result of the resolution. Because even if we follow the link to (A) or not follow the link to (A), the status |
| 445 | // of the link is always not-found since (A) does not have any local resolution. |
| 446 | // In the above case, we can use the result of the [some graph]. |
| 447 | // |
| 448 | // 5. Once we see star links, even if we have not yet traversed that star link path, we should disable caching. |
| 449 | // |
| 450 | // Here is the reason why: |
| 451 | // |
| 452 | // +-------------+ |
| 453 | // | | |
| 454 | // v | |
| 455 | // (A) -> (B) -> (C) *-> [E] |
| 456 | // * ^ |
| 457 | // | | |
| 458 | // v @ |
| 459 | // [D] |
| 460 | // |
| 461 | // In the above case, (C) will be resolved with [D]. |
| 462 | // (C) will see (A) and (A) gives up in (A) -> (B) -> (C) route. So, (A) will fallback to [D]. |
| 463 | // |
| 464 | // +-------------+ |
| 465 | // | | |
| 466 | // v | |
| 467 | // @-> (A) -> (B) -> (C) *-> [E] |
| 468 | // * |
| 469 | // | |
| 470 | // v |
| 471 | // [D] |
| 472 | // |
| 473 | // But in this case, (A) will be resolved with [E] (not [D]). |
| 474 | // (C) will attempt to follow the link to (A), but it fails. |
| 475 | // So (C) will fallback to the star link and found [E]. In this senario, |
| 476 | // (C) is now resolved with [E]'s result. |
| 477 | // |
| 478 | // The cause of this problem is also the same to 4. |
| 479 | // In the latter case, when looking (C), we cannot use the cached result in (C). |
| 480 | // Because the cached result of (C) depends on the *once-visited* node (A) and |
| 481 | // (A) has the fallback system with the star link. |
| 482 | // In the latter trial, we now assume that (A)'s status is not-found. |
| 483 | // But, actually, in the former trial, (A)'s status becomes resolved due to the fallback to the [D]. |
| 484 | // |
| 485 | // To summarize the observations. |
| 486 | // |
| 487 | // 1. The starting point is always cacheable. |
| 488 | // 2. A module that has resolved a local binding is always cacheable. |
| 489 | // 3. If we don't follow any star links during the resolution, we can see all the traced nodes are cacheable. |
| 490 | // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result. |
| 491 | // 5. Once we see star links, even if we have not yet traversed that star link path, we should disable caching. |
| 492 | |
| 493 | using ResolveSet = WTF::HashSet<ResolveQuery, ResolveQuery::Hash, ResolveQuery::HashTraits>; |
| 494 | enum class Type { Query, IndirectFallback, GatherStars }; |
| 495 | struct Task { |
| 496 | ResolveQuery query; |
| 497 | Type type; |
| 498 | }; |
| 499 | |
| 500 | auto typeString = [] (Type type) -> const char* { |
| 501 | switch (type) { |
| 502 | case Type::Query: |
| 503 | return "Query" ; |
| 504 | case Type::IndirectFallback: |
| 505 | return "IndirectFallback" ; |
| 506 | case Type::GatherStars: |
| 507 | return "GatherStars" ; |
| 508 | } |
| 509 | RELEASE_ASSERT_NOT_REACHED(); |
| 510 | return nullptr; |
| 511 | }; |
| 512 | |
| 513 | Vector<Task, 8> pendingTasks; |
| 514 | ResolveSet resolveSet; |
| 515 | |
| 516 | Vector<Resolution, 8> frames; |
| 517 | |
| 518 | bool foundStarLinks = false; |
| 519 | |
| 520 | frames.append(Resolution::notFound()); |
| 521 | |
| 522 | // Call when the query is not resolved in the current module. |
| 523 | // It will enqueue the star resolution requests. Return "false" if the error occurs. |
| 524 | auto resolveNonLocal = [&](const ResolveQuery& query) -> bool { |
| 525 | // https://tc39.github.io/ecma262/#sec-resolveexport |
| 526 | // section 15.2.1.16.3, step 6 |
| 527 | // If the "default" name is not resolved in the current module, we need to throw an error and stop resolution immediately, |
| 528 | // Rationale to this error: A default export cannot be provided by an export *. |
| 529 | VM& vm = exec->vm(); |
| 530 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 531 | if (query.exportName == vm.propertyNames->defaultKeyword.impl()) |
| 532 | return false; |
| 533 | |
| 534 | // Enqueue the task to gather the results of the stars. |
| 535 | // And append the new Resolution frame to gather the local result of the stars. |
| 536 | pendingTasks.append(Task { query, Type::GatherStars }); |
| 537 | foundStarLinks = true; |
| 538 | frames.append(Resolution::notFound()); |
| 539 | |
| 540 | // Enqueue the tasks in reverse order. |
| 541 | for (auto iterator = query.moduleRecord->starExportEntries().rbegin(), end = query.moduleRecord->starExportEntries().rend(); iterator != end; ++iterator) { |
| 542 | const RefPtr<UniquedStringImpl>& starModuleName = *iterator; |
| 543 | AbstractModuleRecord* importedModuleRecord = query.moduleRecord->hostResolveImportedModule(exec, Identifier::fromUid(exec, starModuleName.get())); |
| 544 | RETURN_IF_EXCEPTION(scope, false); |
| 545 | pendingTasks.append(Task { ResolveQuery(importedModuleRecord, query.exportName.get()), Type::Query }); |
| 546 | } |
| 547 | return true; |
| 548 | }; |
| 549 | |
| 550 | // Return the current resolution value of the top frame. |
| 551 | auto currentTop = [&] () -> Resolution& { |
| 552 | ASSERT(!frames.isEmpty()); |
| 553 | return frames.last(); |
| 554 | }; |
| 555 | |
| 556 | // Merge the given resolution to the current resolution value of the top frame. |
| 557 | // If there is ambiguity, return "false". When the "false" is returned, we should make the result "ambiguous". |
| 558 | auto mergeToCurrentTop = [&] (const Resolution& resolution) -> bool { |
| 559 | if (resolution.type == Resolution::Type::NotFound) |
| 560 | return true; |
| 561 | |
| 562 | if (currentTop().type == Resolution::Type::NotFound) { |
| 563 | currentTop() = resolution; |
| 564 | return true; |
| 565 | } |
| 566 | |
| 567 | if (currentTop().moduleRecord != resolution.moduleRecord || currentTop().localName != resolution.localName) |
| 568 | return false; |
| 569 | |
| 570 | return true; |
| 571 | }; |
| 572 | |
| 573 | auto cacheResolutionForQuery = [] (const ResolveQuery& query, const Resolution& resolution) { |
| 574 | ASSERT(resolution.type == Resolution::Type::Resolved); |
| 575 | query.moduleRecord->cacheResolution(query.exportName.get(), resolution); |
| 576 | }; |
| 577 | |
| 578 | pendingTasks.append(Task { root, Type::Query }); |
| 579 | while (!pendingTasks.isEmpty()) { |
| 580 | const Task task = pendingTasks.takeLast(); |
| 581 | const ResolveQuery& query = task.query; |
| 582 | |
| 583 | if (AbstractModuleRecordInternal::verbose) |
| 584 | dataLog(" " , typeString(task.type), " " , task.query, "\n" ); |
| 585 | |
| 586 | switch (task.type) { |
| 587 | case Type::Query: { |
| 588 | AbstractModuleRecord* moduleRecord = query.moduleRecord; |
| 589 | |
| 590 | if (!resolveSet.add(task.query).isNewEntry) |
| 591 | continue; |
| 592 | |
| 593 | // 5. Once we see star links, even if we have not yet traversed that star link path, we should disable caching. |
| 594 | if (!moduleRecord->starExportEntries().isEmpty()) |
| 595 | foundStarLinks = true; |
| 596 | |
| 597 | // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result. |
| 598 | if (!foundStarLinks) { |
| 599 | if (Optional<Resolution> cachedResolution = moduleRecord->tryGetCachedResolution(query.exportName.get())) { |
| 600 | if (!mergeToCurrentTop(*cachedResolution)) |
| 601 | return Resolution::ambiguous(); |
| 602 | continue; |
| 603 | } |
| 604 | } |
| 605 | |
| 606 | const Optional<ExportEntry> optionalExportEntry = moduleRecord->tryGetExportEntry(query.exportName.get()); |
| 607 | if (!optionalExportEntry) { |
| 608 | // If there is no matched exported binding in the current module, |
| 609 | // we need to look into the stars. |
| 610 | bool success = resolveNonLocal(task.query); |
| 611 | EXCEPTION_ASSERT(!scope.exception() || !success); |
| 612 | if (!success) |
| 613 | return Resolution::error(); |
| 614 | continue; |
| 615 | } |
| 616 | |
| 617 | const ExportEntry& exportEntry = *optionalExportEntry; |
| 618 | switch (exportEntry.type) { |
| 619 | case ExportEntry::Type::Local: { |
| 620 | ASSERT(!exportEntry.localName.isNull()); |
| 621 | Resolution resolution { Resolution::Type::Resolved, moduleRecord, exportEntry.localName }; |
| 622 | // 2. A module that has resolved a local binding is always cacheable. |
| 623 | cacheResolutionForQuery(query, resolution); |
| 624 | if (!mergeToCurrentTop(resolution)) |
| 625 | return Resolution::ambiguous(); |
| 626 | continue; |
| 627 | } |
| 628 | |
| 629 | case ExportEntry::Type::Indirect: { |
| 630 | AbstractModuleRecord* importedModuleRecord = moduleRecord->hostResolveImportedModule(exec, exportEntry.moduleName); |
| 631 | RETURN_IF_EXCEPTION(scope, Resolution::error()); |
| 632 | |
| 633 | // When the imported module does not produce any resolved binding, we need to look into the stars in the *current* |
| 634 | // module. To do this, we append the `IndirectFallback` task to the task queue. |
| 635 | pendingTasks.append(Task { query, Type::IndirectFallback }); |
| 636 | // And append the new Resolution frame to check the indirect export will be resolved or not. |
| 637 | frames.append(Resolution::notFound()); |
| 638 | pendingTasks.append(Task { ResolveQuery(importedModuleRecord, exportEntry.importName), Type::Query }); |
| 639 | continue; |
| 640 | } |
| 641 | } |
| 642 | break; |
| 643 | } |
| 644 | |
| 645 | case Type::IndirectFallback: { |
| 646 | Resolution resolution = frames.takeLast(); |
| 647 | |
| 648 | if (resolution.type == Resolution::Type::NotFound) { |
| 649 | // Indirect export entry does not produce any resolved binding. |
| 650 | // So we will investigate the stars. |
| 651 | bool success = resolveNonLocal(task.query); |
| 652 | EXCEPTION_ASSERT(!scope.exception() || !success); |
| 653 | if (!success) |
| 654 | return Resolution::error(); |
| 655 | continue; |
| 656 | } |
| 657 | |
| 658 | ASSERT_WITH_MESSAGE(resolution.type == Resolution::Type::Resolved, "When we see Error and Ambiguous, we immediately return from this loop. So here, only Resolved comes." ); |
| 659 | |
| 660 | // 3. If we don't follow any star links during the resolution, we can see all the traced nodes are cacheable. |
| 661 | // 4. Once we follow star links, we should not retrieve the result from the cache and should not cache the result. |
| 662 | if (!foundStarLinks) |
| 663 | cacheResolutionForQuery(query, resolution); |
| 664 | |
| 665 | // If indirect export entry produces Resolved, we should merge it to the upper frame. |
| 666 | // And do not investigate the stars of the current module. |
| 667 | if (!mergeToCurrentTop(resolution)) |
| 668 | return Resolution::ambiguous(); |
| 669 | break; |
| 670 | } |
| 671 | |
| 672 | case Type::GatherStars: { |
| 673 | Resolution resolution = frames.takeLast(); |
| 674 | ASSERT_WITH_MESSAGE(resolution.type == Resolution::Type::Resolved || resolution.type == Resolution::Type::NotFound, "When we see Error and Ambiguous, we immediately return from this loop. So here, only Resolved and NotFound comes." ); |
| 675 | |
| 676 | // Merge the star resolution to the upper frame. |
| 677 | if (!mergeToCurrentTop(resolution)) |
| 678 | return Resolution::ambiguous(); |
| 679 | break; |
| 680 | } |
| 681 | } |
| 682 | } |
| 683 | |
| 684 | ASSERT(frames.size() == 1); |
| 685 | // 1. The starting point is always cacheable. |
| 686 | if (frames[0].type == Resolution::Type::Resolved) |
| 687 | cacheResolutionForQuery(root, frames[0]); |
| 688 | return frames[0]; |
| 689 | } |
| 690 | |
| 691 | auto AbstractModuleRecord::resolveExport(ExecState* exec, const Identifier& exportName) -> Resolution |
| 692 | { |
| 693 | // Look up the cached resolution first before entering the resolving loop, since the loop setup takes some cost. |
| 694 | if (Optional<Resolution> cachedResolution = tryGetCachedResolution(exportName.impl())) |
| 695 | return *cachedResolution; |
| 696 | return resolveExportImpl(exec, ResolveQuery(this, exportName.impl())); |
| 697 | } |
| 698 | |
| 699 | static void getExportedNames(ExecState* exec, AbstractModuleRecord* root, IdentifierSet& exportedNames) |
| 700 | { |
| 701 | VM& vm = exec->vm(); |
| 702 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 703 | |
| 704 | HashSet<AbstractModuleRecord*> exportStarSet; |
| 705 | Vector<AbstractModuleRecord*, 8> pendingModules; |
| 706 | |
| 707 | pendingModules.append(root); |
| 708 | |
| 709 | while (!pendingModules.isEmpty()) { |
| 710 | AbstractModuleRecord* moduleRecord = pendingModules.takeLast(); |
| 711 | if (exportStarSet.contains(moduleRecord)) |
| 712 | continue; |
| 713 | exportStarSet.add(moduleRecord); |
| 714 | |
| 715 | for (const auto& pair : moduleRecord->exportEntries()) { |
| 716 | const AbstractModuleRecord::ExportEntry& exportEntry = pair.value; |
| 717 | if (moduleRecord == root || vm.propertyNames->defaultKeyword != exportEntry.exportName) |
| 718 | exportedNames.add(exportEntry.exportName.impl()); |
| 719 | } |
| 720 | |
| 721 | for (const auto& starModuleName : moduleRecord->starExportEntries()) { |
| 722 | AbstractModuleRecord* requestedModuleRecord = moduleRecord->hostResolveImportedModule(exec, Identifier::fromUid(exec, starModuleName.get())); |
| 723 | RETURN_IF_EXCEPTION(scope, void()); |
| 724 | pendingModules.append(requestedModuleRecord); |
| 725 | } |
| 726 | } |
| 727 | } |
| 728 | |
| 729 | JSModuleNamespaceObject* AbstractModuleRecord::getModuleNamespace(ExecState* exec) |
| 730 | { |
| 731 | VM& vm = exec->vm(); |
| 732 | auto scope = DECLARE_THROW_SCOPE(vm); |
| 733 | |
| 734 | // http://www.ecma-international.org/ecma-262/6.0/#sec-getmodulenamespace |
| 735 | if (m_moduleNamespaceObject) |
| 736 | return m_moduleNamespaceObject.get(); |
| 737 | |
| 738 | JSGlobalObject* globalObject = exec->lexicalGlobalObject(); |
| 739 | IdentifierSet exportedNames; |
| 740 | getExportedNames(exec, this, exportedNames); |
| 741 | RETURN_IF_EXCEPTION(scope, nullptr); |
| 742 | |
| 743 | Vector<std::pair<Identifier, Resolution>> resolutions; |
| 744 | for (auto& name : exportedNames) { |
| 745 | Identifier ident = Identifier::fromUid(exec, name.get()); |
| 746 | const Resolution resolution = resolveExport(exec, ident); |
| 747 | RETURN_IF_EXCEPTION(scope, nullptr); |
| 748 | switch (resolution.type) { |
| 749 | case Resolution::Type::NotFound: |
| 750 | throwSyntaxError(exec, scope, makeString("Exported binding name '" , String(name.get()), "' is not found." )); |
| 751 | return nullptr; |
| 752 | |
| 753 | case Resolution::Type::Error: |
| 754 | throwSyntaxError(exec, scope, makeString("Exported binding name 'default' cannot be resolved by star export entries." )); |
| 755 | return nullptr; |
| 756 | |
| 757 | case Resolution::Type::Ambiguous: |
| 758 | break; |
| 759 | |
| 760 | case Resolution::Type::Resolved: |
| 761 | resolutions.append({ WTFMove(ident), resolution }); |
| 762 | break; |
| 763 | } |
| 764 | } |
| 765 | |
| 766 | auto* moduleNamespaceObject = JSModuleNamespaceObject::create(exec, globalObject, globalObject->moduleNamespaceObjectStructure(), this, WTFMove(resolutions)); |
| 767 | RETURN_IF_EXCEPTION(scope, nullptr); |
| 768 | m_moduleNamespaceObject.set(vm, this, moduleNamespaceObject); |
| 769 | return moduleNamespaceObject; |
| 770 | } |
| 771 | |
| 772 | void AbstractModuleRecord::link(ExecState* exec, JSValue scriptFetcher) |
| 773 | { |
| 774 | VM& vm = exec->vm(); |
| 775 | if (auto* jsModuleRecord = jsDynamicCast<JSModuleRecord*>(vm, this)) |
| 776 | return jsModuleRecord->link(exec, scriptFetcher); |
| 777 | #if ENABLE(WEBASSEMBLY) |
| 778 | if (auto* wasmModuleRecord = jsDynamicCast<WebAssemblyModuleRecord*>(vm, this)) |
| 779 | return wasmModuleRecord->link(exec, scriptFetcher, nullptr, Wasm::CreationMode::FromModuleLoader); |
| 780 | #endif |
| 781 | RELEASE_ASSERT_NOT_REACHED(); |
| 782 | } |
| 783 | |
| 784 | JS_EXPORT_PRIVATE JSValue AbstractModuleRecord::evaluate(ExecState* exec) |
| 785 | { |
| 786 | VM& vm = exec->vm(); |
| 787 | if (auto* jsModuleRecord = jsDynamicCast<JSModuleRecord*>(vm, this)) |
| 788 | return jsModuleRecord->evaluate(exec); |
| 789 | #if ENABLE(WEBASSEMBLY) |
| 790 | if (auto* wasmModuleRecord = jsDynamicCast<WebAssemblyModuleRecord*>(vm, this)) |
| 791 | return wasmModuleRecord->evaluate(exec); |
| 792 | #endif |
| 793 | RELEASE_ASSERT_NOT_REACHED(); |
| 794 | return jsUndefined(); |
| 795 | } |
| 796 | |
| 797 | static String printableName(const RefPtr<UniquedStringImpl>& uid) |
| 798 | { |
| 799 | if (uid->isSymbol()) |
| 800 | return uid.get(); |
| 801 | return WTF::makeString("'" , String(uid.get()), "'" ); |
| 802 | } |
| 803 | |
| 804 | static String printableName(const Identifier& ident) |
| 805 | { |
| 806 | return printableName(ident.impl()); |
| 807 | } |
| 808 | |
| 809 | void AbstractModuleRecord::dump() |
| 810 | { |
| 811 | dataLog("\nAnalyzing ModuleRecord key(" , printableName(m_moduleKey), ")\n" ); |
| 812 | |
| 813 | dataLog(" Dependencies: " , m_requestedModules.size(), " modules\n" ); |
| 814 | for (const auto& moduleName : m_requestedModules) |
| 815 | dataLog(" module(" , printableName(moduleName), ")\n" ); |
| 816 | |
| 817 | dataLog(" Import: " , m_importEntries.size(), " entries\n" ); |
| 818 | for (const auto& pair : m_importEntries) { |
| 819 | const ImportEntry& importEntry = pair.value; |
| 820 | dataLog(" import(" , printableName(importEntry.importName), "), local(" , printableName(importEntry.localName), "), module(" , printableName(importEntry.moduleRequest), ")\n" ); |
| 821 | } |
| 822 | |
| 823 | dataLog(" Export: " , m_exportEntries.size(), " entries\n" ); |
| 824 | for (const auto& pair : m_exportEntries) { |
| 825 | const ExportEntry& exportEntry = pair.value; |
| 826 | switch (exportEntry.type) { |
| 827 | case ExportEntry::Type::Local: |
| 828 | dataLog(" [Local] " , "export(" , printableName(exportEntry.exportName), "), local(" , printableName(exportEntry.localName), ")\n" ); |
| 829 | break; |
| 830 | |
| 831 | case ExportEntry::Type::Indirect: |
| 832 | dataLog(" [Indirect] " , "export(" , printableName(exportEntry.exportName), "), import(" , printableName(exportEntry.importName), "), module(" , printableName(exportEntry.moduleName), ")\n" ); |
| 833 | break; |
| 834 | } |
| 835 | } |
| 836 | for (const auto& moduleName : m_starExportEntries) |
| 837 | dataLog(" [Star] module(" , printableName(moduleName.get()), ")\n" ); |
| 838 | } |
| 839 | |
| 840 | } // namespace JSC |
| 841 | |