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