We should support CreateThis in the FTL
[WebKit-https.git] / Source / JavaScriptCore / runtime / JSScope.cpp
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
38 namespace JSC {
39
40 STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(JSScope);
41
42 const ClassInfo JSScope::s_info = { "Scope", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSScope) };
43
44 void 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.
53 static 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
194 JSObject* 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.
204 static 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
221 template<typename ReturnPredicateFunctor, typename SkipPredicateFunctor>
222 ALWAYS_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
266 JSValue 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
292 JSObject* 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
303 ResolveOp 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
325 void 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
345 bool JSScope::isVarScope()
346 {
347     if (type() != LexicalEnvironmentType)
348         return false;
349     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::VarScope;
350 }
351
352 bool JSScope::isLexicalScope()
353 {
354     if (!isJSLexicalEnvironment())
355         return false;
356     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::LexicalScope;
357 }
358
359 bool JSScope::isModuleScope()
360 {
361     return type() == ModuleEnvironmentType;
362 }
363
364 bool JSScope::isCatchScope()
365 {
366     if (type() != LexicalEnvironmentType)
367         return false;
368     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::CatchScope;
369 }
370
371 bool JSScope::isFunctionNameScopeObject()
372 {
373     if (type() != LexicalEnvironmentType)
374         return false;
375     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->scopeType() == SymbolTable::ScopeType::FunctionNameScope;
376 }
377
378 bool JSScope::isNestedLexicalScope()
379 {
380     if (!isJSLexicalEnvironment())
381         return false;
382     return jsCast<JSLexicalEnvironment*>(this)->symbolTable()->isNestedLexicalScope();
383 }
384
385 JSScope* 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
404 SymbolTable* JSScope::symbolTable(VM& vm)
405 {
406     if (JSSymbolTableObject* symbolTableObject = jsDynamicCast<JSSymbolTableObject*>(vm, this))
407         return symbolTableObject->symbolTable();
408
409     return nullptr;
410 }
411
412 JSValue JSScope::toThis(JSCell*, ExecState* exec, ECMAMode ecmaMode)
413 {
414     if (ecmaMode == StrictMode)
415         return jsUndefined();
416     return exec->globalThisValue();
417 }
418
419 } // namespace JSC