JSScope.cpp
1 /* 2 * Copyright (C) 2012-2020 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 "JSScope.h" 28 29 #include "AbstractModuleRecord.h" 30 #include "JSCInlines.h" 31 #include "JSLexicalEnvironment.h" 32 #include "JSModuleEnvironment.h" 33 #include "JSWithScope.h" 34 #include "VariableEnvironment.h" 35 36 namespace JSC { 37 38 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSScope); 39 40 const ClassInfo JSScope::s_info = { "Scope", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSScope) }; 41 42 void JSScope::visitChildren(JSCell* cell, SlotVisitor& visitor) 43 { 44 JSScope* thisObject = jsCast<JSScope*>(cell); 45 ASSERT_GC_OBJECT_INHERITS(thisObject, info()); 46 Base::visitChildren(thisObject, visitor); 47 visitor.append(thisObject->m_next); 48 } 49 50 // Returns true if we found enough information to terminate optimization. 51 static inline bool abstractAccess(JSGlobalObject* globalObject, JSScope* scope, const Identifier& ident, GetOrPut getOrPut, size_t depth, bool& needsVarInjectionChecks, ResolveOp& op, InitializationMode initializationMode) 52 { 53 VM& vm = globalObject->vm(); 54 auto throwScope = DECLARE_THROW_SCOPE(vm); 55 56 if (scope->isJSLexicalEnvironment()) { 57 JSLexicalEnvironment* lexicalEnvironment = jsCast<JSLexicalEnvironment*>(scope); 58 59 SymbolTable* symbolTable = lexicalEnvironment->symbolTable(); 60 { 61 ConcurrentJSLocker locker(symbolTable->m_lock); 62 auto iter = symbolTable->find(locker, ident.impl()); 63 if (iter != symbolTable->end(locker)) { 64 SymbolTableEntry& entry = iter->value; 65 ASSERT(!entry.isNull()); 66 if (entry.isReadOnly() && getOrPut == Put) { 67 // We know the property will be at this lexical environment scope, but we don't know how to cache it. 68 op = ResolveOp(Dynamic, 0, nullptr, nullptr, nullptr, 0); 69 return true; 70 } 71 72 op = ResolveOp(makeType(ClosureVar, needsVarInjectionChecks), depth, nullptr, lexicalEnvironment, entry.watchpointSet(), entry.scopeOffset().offset()); 73 return true; 74 } 75 } 76 77 if (scope->type() == ModuleEnvironmentType) { 78 JSModuleEnvironment* moduleEnvironment = jsCast<JSModuleEnvironment*>(scope); 79 AbstractModuleRecord* moduleRecord = moduleEnvironment->moduleRecord(); 80 AbstractModuleRecord::Resolution resolution = moduleRecord->resolveImport(globalObject, ident); 81 RETURN_IF_EXCEPTION(throwScope, false); 82 if (resolution.type == AbstractModuleRecord::Resolution::Type::Resolved) { 83 AbstractModuleRecord* importedRecord = resolution.moduleRecord; 84 JSModuleEnvironment* importedEnvironment = importedRecord->moduleEnvironment(); 85 SymbolTable* symbolTable = importedEnvironment->symbolTable(); 86 ConcurrentJSLocker locker(symbolTable->m_lock); 87 auto iter = symbolTable->find(locker, resolution.localName.impl()); 88 ASSERT(iter != symbolTable->end(locker)); 89 SymbolTableEntry& entry = iter->value; 90 ASSERT(!entry.isNull()); 91 op = ResolveOp(makeType(ModuleVar, needsVarInjectionChecks), depth, nullptr, importedEnvironment, entry.watchpointSet(), entry.scopeOffset().offset(), resolution.localName.impl()); 92 return true; 93 } 94 } 95 96 if (symbolTable->usesNonStrictEval()) 97 needsVarInjectionChecks = true; 98 return false; 99 } 100 101 if (scope->isGlobalLexicalEnvironment()) { 102 JSGlobalLexicalEnvironment* globalLexicalEnvironment = jsCast<JSGlobalLexicalEnvironment*>(scope); 103 SymbolTable* symbolTable = globalLexicalEnvironment->symbolTable(); 104 ConcurrentJSLocker locker(symbolTable->m_lock); 105 auto iter = symbolTable->find(locker, ident.impl()); 106 if (iter != symbolTable->end(locker)) { 107 SymbolTableEntry& entry = iter->value; 108 ASSERT(!entry.isNull()); 109 if (getOrPut == Put && entry.isReadOnly() && !isInitialization(initializationMode)) { 110 // We know the property will be at global lexical environment, but we don't know how to cache it. 111 op = ResolveOp(Dynamic, 0, nullptr, nullptr, nullptr, 0); 112 return true; 113 } 114 115 // We can force const Initialization to always go down the fast path. It is provably impossible to construct 116 // a program that needs a var injection check here. You can convince yourself of this as follows: 117 // Any other let/const/class would be a duplicate of this in the global scope, so we would never get here in that situation. 118 // Also, if we had an eval in the global scope that defined a const, it would also be a duplicate of this const, and so it would 119 // also throw an error. Therefore, we're *the only* thing that can assign to this "const" slot for the first (and only) time. Also, 120 // we will never have a Dynamic ResolveType here because if we were inside a "with" statement, that would mean the "const" definition 121 // isn't a global, it would be a local to the "with" block. 122 // We still need to make the slow path correct for when we need to fire a watchpoint. 123 ResolveType resolveType = initializationMode == InitializationMode::ConstInitialization ? GlobalLexicalVar : makeType(GlobalLexicalVar, needsVarInjectionChecks); 124 op = ResolveOp( 125 resolveType, depth, nullptr, nullptr, entry.watchpointSet(), 126 reinterpret_cast<uintptr_t>(globalLexicalEnvironment->variableAt(entry.scopeOffset()).slot())); 127 return true; 128 } 129 130 return false; 131 } 132 133 if (scope->isGlobalObject()) { 134 JSGlobalObject* globalObject = jsCast<JSGlobalObject*>(scope); 135 { 136 SymbolTable* symbolTable = globalObject->symbolTable(); 137 ConcurrentJSLocker locker(symbolTable->m_lock); 138 auto iter = symbolTable->find(locker, ident.impl()); 139 if (iter != symbolTable->end(locker)) { 140 SymbolTableEntry& entry = iter->value; 141 ASSERT(!entry.isNull()); 142 if (getOrPut == Put && entry.isReadOnly()) { 143 // We know the property will be at global scope, but we don't know how to cache it. 144 op = ResolveOp(Dynamic, 0, nullptr, nullptr, nullptr, 0); 145 return true; 146 } 147 148 op = ResolveOp( 149 makeType(GlobalVar, needsVarInjectionChecks), depth, nullptr, nullptr, entry.watchpointSet(), 150 reinterpret_cast<uintptr_t>(globalObject->variableAt(entry.scopeOffset()).slot())); 151 return true; 152 } 153 } 154 155 PropertySlot slot(globalObject, PropertySlot::InternalMethodType::VMInquiry, &vm); 156 bool hasOwnProperty = globalObject->getOwnPropertySlot(globalObject, globalObject, ident, slot); 157 slot.disallowVMEntry.reset(); 158 if (!hasOwnProperty) { 159 op = ResolveOp(makeType(UnresolvedProperty, needsVarInjectionChecks), 0, nullptr, nullptr, nullptr, 0); 160 return true; 161 } 162 163 Structure* structure = globalObject->structure(vm); 164 if (!slot.isCacheableValue() 165 || !structure->propertyAccessesAreCacheable() 166 || (structure->hasReadOnlyOrGetterSetterPropertiesExcludingProto() && getOrPut == Put)) { 167 // We know the property will be at global scope, but we don't know how to cache it. 168 ASSERT(!scope->next()); 169 op = ResolveOp(makeType(GlobalProperty, needsVarInjectionChecks), 0, nullptr, nullptr, nullptr, 0); 170 return true; 171 } 172 173 174 WatchpointState state = structure->ensurePropertyReplacementWatchpointSet(vm, slot.cachedOffset())->state(); 175 if (state == IsWatched && getOrPut == Put) { 176 // The field exists, but because the replacement watchpoint is still intact. This is 177 // kind of dangerous. We have two options: 178 // 1) Invalidate the watchpoint set. That would work, but it's possible that this code 179 // path never executes - in which case this would be unwise. 180 // 2) Have the invalidation happen at run-time. All we have to do is leave the code 181 // uncached. The only downside is slightly more work when this does execute. 182 // We go with option (2) here because it seems less evil. 183 op = ResolveOp(makeType(GlobalProperty, needsVarInjectionChecks), depth, nullptr, nullptr, nullptr, 0); 184 } else 185 op = ResolveOp(makeType(GlobalProperty, needsVarInjectionChecks), depth, structure, nullptr, nullptr, slot.cachedOffset()); 186 return true; 187 } 188 189 op = ResolveOp(Dynamic, 0, nullptr, nullptr, nullptr, 0); 190 return true; 191 } 192 193 JSObject* JSScope::objectAtScope(JSScope* scope) 194 { 195 JSObject* object = scope; 196 if (object->type() == WithScopeType) 197 return jsCast<JSWithScope*>(object)->object(); 198 199 return object; 200 } 201 202 // When an exception occurs, the result of isUnscopable becomes false. 203 static inline bool isUnscopable(JSGlobalObject* globalObject, JSScope* scope, JSObject* object, const Identifier& ident) 204 { 205 VM& vm = globalObject->vm(); 206 auto throwScope = DECLARE_THROW_SCOPE(vm); 207 if (scope->type() != WithScopeType) 208 return false; 209 210 JSValue unscopables = object->get(globalObject, vm.propertyNames->unscopablesSymbol); 211 RETURN_IF_EXCEPTION(throwScope, false); 212 if (!unscopables.isObject()) 213 return false; 214 JSValue blocked = jsCast<JSObject*>(unscopables)->get(globalObject, ident); 215 RETURN_IF_EXCEPTION(throwScope, false); 216 217 return blocked.toBoolean(globalObject); 218 } 219 220 template<typename ReturnPredicateFunctor, typename SkipPredicateFunctor> 221 ALWAYS_INLINE JSObject* JSScope::resolve(JSGlobalObject* globalObject, JSScope* scope, const Identifier& ident, ReturnPredicateFunctor returnPredicate, SkipPredicateFunctor skipPredicate) 222 { 223 VM& vm = globalObject->vm(); 224 auto throwScope = DECLARE_THROW_SCOPE(vm); 225 ScopeChainIterator end = scope->end(); 226 ScopeChainIterator it = scope->begin(); 227 while (1) { 228 JSScope* scope = it.scope(); 229 JSObject* object = it.get(); 230 231 // Global scope. 232 if (++it == end) { 233 JSScope* globalScopeExtension = scope->globalObject(vm)->globalScopeExtension(); 234 if (UNLIKELY(globalScopeExtension)) { 235 bool hasProperty = object->hasProperty(globalObject, ident); 236 RETURN_IF_EXCEPTION(throwScope, nullptr); 237 if (hasProperty) 238 return object; 239 JSObject* extensionScopeObject = JSScope::objectAtScope(globalScopeExtension); 240 hasProperty = extensionScopeObject->hasProperty(globalObject, ident); 241 RETURN_IF_EXCEPTION(throwScope, nullptr); 242 if (hasProperty) 243 return extensionScopeObject; 244 } 245 return object; 246 } 247 248 if (skipPredicate(scope)) 249 continue; 250 251 bool hasProperty = object->hasProperty(globalObject, ident); 252 RETURN_IF_EXCEPTION(throwScope, nullptr); 253 if (hasProperty) { 254 bool unscopable = isUnscopable(globalObject, scope, object, ident); 255 EXCEPTION_ASSERT(!throwScope.exception() || !unscopable); 256 if (!unscopable) 257 return object; 258 } 259 260 if (returnPredicate(scope)) 261 return object; 262 } 263 } 264 265 JSValue JSScope::resolveScopeForHoistingFuncDeclInEval(JSGlobalObject* globalObject, JSScope* scope, const Identifier& ident) 266 { 267 VM& vm = globalObject->vm(); 268 auto throwScope = DECLARE_THROW_SCOPE(vm); 269 270 auto returnPredicate = [&] (JSScope* scope) -> bool { 271 return scope->isVarScope(); 272 }; 273 auto skipPredicate = [&] (JSScope* scope) -> bool { 274 return scope->isWithScope(); 275 }; 276 JSObject* object = resolve(globalObject, scope, ident, returnPredicate, skipPredicate); 277 RETURN_IF_EXCEPTION(throwScope, { }); 278 279 bool result = false; 280 if (JSScope* scope = jsDynamicCast<JSScope*>(vm, object)) { 281 if (SymbolTable* scopeSymbolTable = scope->symbolTable(vm)) { 282 result = scope->isGlobalObject() 283 ? JSObject::isExtensible(object, globalObject) 284 : scopeSymbolTable->scopeType() == SymbolTable::ScopeType::VarScope; 285 } 286 } 287 288 return result ? JSValue(object) : jsUndefined(); 289 } 290 291 JSObject* JSScope::resolve(JSGlobalObject* globalObject, JSScope* scope, const Identifier& ident) 292 { 293 auto predicate1 = [&] (JSScope*) -> bool { 294 return false; 295 }; 296 auto predicate2 = [&] (JSScope*) -> bool { 297 return false; 298 }; 299 return resolve(globalObject, scope, ident, predicate1, predicate2); 300 } 301 302 ResolveOp JSScope::abstractResolve(JSGlobalObject* globalObject, size_t depthOffset, JSScope* scope, const Identifier& ident, GetOrPut getOrPut, ResolveType unlinkedType, InitializationMode initializationMode) 303 { 304 VM& vm = globalObject->vm(); 305 auto throwScope = DECLARE_THROW_SCOPE(vm); 306 307 ResolveOp op(Dynamic, 0, nullptr, nullptr, nullptr, 0); 308 if (unlinkedType == Dynamic) 309 return op; 310 311 bool needsVarInjectionChecks = JSC::needsVarInjectionChecks(unlinkedType); 312 size_t depth = depthOffset; 313 for (; scope; scope = scope->next()) { 314 bool success = abstractAccess(globalObject, scope, ident, getOrPut, depth, needsVarInjectionChecks, op, initializationMode); 315 RETURN_IF_EXCEPTION(throwScope, ResolveOp(Dynamic, 0, nullptr, nullptr, nullptr, 0)); 316 if (success) 317 break; 318 ++depth; 319 } 320 321 return op; 322 } 323 324 void JSScope::collectClosureVariablesUnderTDZ(JSScope* scope, TDZEnvironment& result, VariableEnvironment& privateNames) 325 { 326 for (; scope; scope = scope->next()) { 327 if (!scope->isLexicalScope() && !scope->isCatchScope()) 328 continue; 329 330 if (scope->isModuleScope()) { 331 AbstractModuleRecord* moduleRecord = jsCast<JSModuleEnvironment*>(scope)->moduleRecord(); 332 for (const auto& pair : moduleRecord->importEntries()) 333 result.add(pair.key); 334 } 335 336 SymbolTable* symbolTable = jsCast<JSSymbolTableObject*>(scope)->symbolTable(); 337 ASSERT(symbolTable->scopeType() == SymbolTable::ScopeType::LexicalScope || symbolTable->scopeType() == SymbolTable::ScopeType::CatchScope); 338 ConcurrentJSLocker locker(symbolTable->m_lock); 339 for (auto end = symbolTable->end(locker), iter = symbolTable->begin(locker); iter != end; ++iter) 340 result.add(iter->key); 341 342 if (symbolTable->hasPrivateNames()) { 343 for (auto name : symbolTable->privateNames()) 344 privateNames.usePrivateName(name); 345 } 346 } 347 } 348 349 bool JSScope::isVarScope() 350 { 351 if (type() != LexicalEnvironmentType) 352 return false; 353 return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::VarScope; 354 } 355 356 bool JSScope::isLexicalScope() 357 { 358 if (!isJSLexicalEnvironment()) 359 return false; 360 return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::LexicalScope; 361 } 362 363 bool JSScope::isModuleScope() 364 { 365 return type() == ModuleEnvironmentType; 366 } 367 368 bool JSScope::isCatchScope() 369 { 370 if (type() != LexicalEnvironmentType) 371 return false; 372 return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::CatchScope; 373 } 374 375 bool JSScope::isFunctionNameScopeObject() 376 { 377 if (type() != LexicalEnvironmentType) 378 return false; 379 return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::FunctionNameScope; 380 } 381 382 bool JSScope::isNestedLexicalScope() 383 { 384 if (!isJSLexicalEnvironment()) 385 return false; 386 return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->isNestedLexicalScope(); 387 } 388 389 JSScope* JSScope::constantScopeForCodeBlock(ResolveType type, CodeBlock* codeBlock) 390 { 391 switch (type) { 392 case GlobalProperty: 393 case GlobalVar: 394 case GlobalPropertyWithVarInjectionChecks: 395 case GlobalVarWithVarInjectionChecks: 396 return codeBlock->globalObject(); 397 case GlobalLexicalVarWithVarInjectionChecks: 398 case GlobalLexicalVar: 399 return codeBlock->globalObject()->globalLexicalEnvironment(); 400 default: 401 return nullptr; 402 } 403 404 RELEASE_ASSERT_NOT_REACHED(); 405 return nullptr; 406 } 407 408 SymbolTable* JSScope::symbolTable(VM& vm) 409 { 410 if (JSSymbolTableObject* symbolTableObject = jsDynamicCast<JSSymbolTableObject*>(vm, this)) 411 return symbolTableObject->symbolTable(); 412 413 return nullptr; 414 } 415 416 JSValue JSScope::toThis(JSCell*, JSGlobalObject* globalObject, ECMAMode ecmaMode) 417 { 418 if (ecmaMode.isStrict()) 419 return jsUndefined(); 420 return globalObject->globalThis(); 421 } 422 423 } // namespace JSC