Unreviewed, rolling out r215476.
[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 JSObject* JSScope::resolve(ExecState* exec, JSScope* scope, const Identifier& ident)
215 {
216     VM& vm = exec->vm();
217     auto throwScope = DECLARE_THROW_SCOPE(vm);
218     ScopeChainIterator end = scope->end();
219     ScopeChainIterator it = scope->begin();
220     while (1) {
221         JSScope* scope = it.scope();
222         JSObject* object = it.get();
223
224         // Global scope.
225         if (++it == end) {
226             JSScope* globalScopeExtension = scope->globalObject(vm)->globalScopeExtension();
227             if (UNLIKELY(globalScopeExtension)) {
228                 bool hasProperty = object->hasProperty(exec, ident);
229                 RETURN_IF_EXCEPTION(throwScope, nullptr);
230                 if (hasProperty)
231                     return object;
232                 JSObject* extensionScopeObject = JSScope::objectAtScope(globalScopeExtension);
233                 hasProperty = extensionScopeObject->hasProperty(exec, ident);
234                 RETURN_IF_EXCEPTION(throwScope, nullptr);
235                 if (hasProperty)
236                     return extensionScopeObject;
237             }
238             return object;
239         }
240
241         bool hasProperty = object->hasProperty(exec, ident);
242         RETURN_IF_EXCEPTION(throwScope, nullptr);
243         if (hasProperty) {
244             bool unscopable = isUnscopable(exec, scope, object, ident);
245             ASSERT(!throwScope.exception() || !unscopable);
246             if (!unscopable)
247                 return object;
248         }
249     }
250 }
251
252 ResolveOp JSScope::abstractResolve(ExecState* exec, size_t depthOffset, JSScope* scope, const Identifier& ident, GetOrPut getOrPut, ResolveType unlinkedType, InitializationMode initializationMode)
253 {
254     ResolveOp op(Dynamic, 0, 0, 0, 0, 0);
255     if (unlinkedType == Dynamic)
256         return op;
257
258     bool needsVarInjectionChecks = JSC::needsVarInjectionChecks(unlinkedType);
259     size_t depth = depthOffset;
260     for (; scope; scope = scope->next()) {
261         if (abstractAccess(exec, scope, ident, getOrPut, depth, needsVarInjectionChecks, op, initializationMode))
262             break;
263         ++depth;
264     }
265
266     return op;
267 }
268
269 void JSScope::collectClosureVariablesUnderTDZ(JSScope* scope, VariableEnvironment& result)
270 {
271     for (; scope; scope = scope->next()) {
272         if (!scope->isLexicalScope() && !scope->isCatchScope())
273             continue;
274
275         if (scope->isModuleScope()) {
276             AbstractModuleRecord* moduleRecord = jsCast<JSModuleEnvironment*>(scope)->moduleRecord();
277             for (const auto& pair : moduleRecord->importEntries())
278                 result.add(pair.key);
279         }
280
281         SymbolTable* symbolTable = jsCast<JSSymbolTableObject*>(scope)->symbolTable();
282         ASSERT(symbolTable->scopeType() == SymbolTable::ScopeType::LexicalScope || symbolTable->scopeType() == SymbolTable::ScopeType::CatchScope);
283         ConcurrentJSLocker locker(symbolTable->m_lock);
284         for (auto end = symbolTable->end(locker), iter = symbolTable->begin(locker); iter != end; ++iter)
285             result.add(iter->key);
286     }
287 }
288
289 bool JSScope::isVarScope()
290 {
291     if (type() != LexicalEnvironmentType)
292         return false;
293     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::VarScope;
294 }
295
296 bool JSScope::isLexicalScope()
297 {
298     if (!isJSLexicalEnvironment())
299         return false;
300     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::LexicalScope;
301 }
302
303 bool JSScope::isModuleScope()
304 {
305     return type() == ModuleEnvironmentType;
306 }
307
308 bool JSScope::isCatchScope()
309 {
310     if (type() != LexicalEnvironmentType)
311         return false;
312     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::CatchScope;
313 }
314
315 bool JSScope::isFunctionNameScopeObject()
316 {
317     if (type() != LexicalEnvironmentType)
318         return false;
319     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::FunctionNameScope;
320 }
321
322 bool JSScope::isNestedLexicalScope()
323 {
324     if (!isJSLexicalEnvironment())
325         return false;
326     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->isNestedLexicalScope();
327 }
328
329 JSScope* JSScope::constantScopeForCodeBlock(ResolveType type, CodeBlock* codeBlock)
330 {
331     switch (type) {
332     case GlobalProperty:
333     case GlobalVar:
334     case GlobalPropertyWithVarInjectionChecks:
335     case GlobalVarWithVarInjectionChecks:
336         return codeBlock->globalObject();
337     case GlobalLexicalVarWithVarInjectionChecks:
338     case GlobalLexicalVar:
339         return codeBlock->globalObject()->globalLexicalEnvironment();
340     default:
341         return nullptr;
342     }
343
344     RELEASE_ASSERT_NOT_REACHED();
345     return nullptr;
346 }
347
348 SymbolTable* JSScope::symbolTable(VM& vm)
349 {
350     if (JSSymbolTableObject* symbolTableObject = jsDynamicCast<JSSymbolTableObject*>(vm, this))
351         return symbolTableObject->symbolTable();
352
353     return nullptr;
354 }
355
356 } // namespace JSC