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