/ runtime / JSScope.cpp
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