1/*
2 * Copyright (C) 2008-2009, 2014, 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 "DebuggerScope.h"
28
29#include "JSLexicalEnvironment.h"
30#include "JSCInlines.h"
31#include "JSWithScope.h"
32
33namespace JSC {
34
35STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(DebuggerScope);
36
37const ClassInfo DebuggerScope::s_info = { "DebuggerScope", &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DebuggerScope) };
38
39DebuggerScope* DebuggerScope::create(VM& vm, JSScope* scope)
40{
41 Structure* structure = scope->globalObject(vm)->debuggerScopeStructure();
42 DebuggerScope* debuggerScope = new (NotNull, allocateCell<DebuggerScope>(vm.heap)) DebuggerScope(vm, structure, scope);
43 debuggerScope->finishCreation(vm);
44 return debuggerScope;
45}
46
47DebuggerScope::DebuggerScope(VM& vm, Structure* structure, JSScope* scope)
48 : JSNonFinalObject(vm, structure)
49{
50 ASSERT(scope);
51 m_scope.set(vm, this, scope);
52}
53
54void DebuggerScope::finishCreation(VM& vm)
55{
56 Base::finishCreation(vm);
57}
58
59void DebuggerScope::visitChildren(JSCell* cell, SlotVisitor& visitor)
60{
61 DebuggerScope* thisObject = jsCast<DebuggerScope*>(cell);
62 ASSERT_GC_OBJECT_INHERITS(thisObject, info());
63 Base::visitChildren(cell, visitor);
64
65 visitor.append(thisObject->m_scope);
66 visitor.append(thisObject->m_next);
67}
68
69String DebuggerScope::className(const JSObject* object, VM& vm)
70{
71 const DebuggerScope* scope = jsCast<const DebuggerScope*>(object);
72 // We cannot assert that scope->isValid() because the TypeProfiler may encounter an invalidated
73 // DebuggerScope in its log entries. We just need to handle it appropriately as below.
74 if (!scope->isValid())
75 return String();
76 JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
77 return thisObject->methodTable(vm)->className(thisObject, vm);
78}
79
80String DebuggerScope::toStringName(const JSObject* object, ExecState* exec)
81{
82 const DebuggerScope* scope = jsCast<const DebuggerScope*>(object);
83 // We cannot assert that scope->isValid() because the TypeProfiler may encounter an invalidated
84 // DebuggerScope in its log entries. We just need to handle it appropriately as below.
85 if (!scope->isValid())
86 return String();
87 JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
88 return thisObject->methodTable(exec->vm())->toStringName(thisObject, exec);
89}
90
91bool DebuggerScope::getOwnPropertySlot(JSObject* object, ExecState* exec, PropertyName propertyName, PropertySlot& slot)
92{
93 DebuggerScope* scope = jsCast<DebuggerScope*>(object);
94 if (!scope->isValid())
95 return false;
96 JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
97 slot.setThisValue(JSValue(thisObject));
98
99 // By default, JSObject::getPropertySlot() will look in the DebuggerScope's prototype
100 // chain and not the wrapped scope, and JSObject::getPropertySlot() cannot be overridden
101 // to behave differently for the DebuggerScope.
102 //
103 // Instead, we'll treat all properties in the wrapped scope and its prototype chain as
104 // the own properties of the DebuggerScope. This is fine because the WebInspector
105 // does not presently need to distinguish between what's owned at each level in the
106 // prototype chain. Hence, we'll invoke getPropertySlot() on the wrapped scope here
107 // instead of getOwnPropertySlot().
108 bool result = thisObject->getPropertySlot(exec, propertyName, slot);
109 if (result && slot.isValue() && slot.getValue(exec, propertyName) == jsTDZValue()) {
110 // FIXME:
111 // We hit a scope property that has the TDZ empty value.
112 // Currently, we just lie to the inspector and claim that this property is undefined.
113 // This is not ideal and we should fix it.
114 // https://bugs.webkit.org/show_bug.cgi?id=144977
115 slot.setValue(slot.slotBase(), static_cast<unsigned>(PropertyAttribute::DontEnum), jsUndefined());
116 return true;
117 }
118 return result;
119}
120
121bool DebuggerScope::put(JSCell* cell, ExecState* exec, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
122{
123 DebuggerScope* scope = jsCast<DebuggerScope*>(cell);
124 ASSERT(scope->isValid());
125 if (!scope->isValid())
126 return false;
127 JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
128 slot.setThisValue(JSValue(thisObject));
129 return thisObject->methodTable(exec->vm())->put(thisObject, exec, propertyName, value, slot);
130}
131
132bool DebuggerScope::deleteProperty(JSCell* cell, ExecState* exec, PropertyName propertyName)
133{
134 DebuggerScope* scope = jsCast<DebuggerScope*>(cell);
135 ASSERT(scope->isValid());
136 if (!scope->isValid())
137 return false;
138 JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
139 return thisObject->methodTable(exec->vm())->deleteProperty(thisObject, exec, propertyName);
140}
141
142void DebuggerScope::getOwnPropertyNames(JSObject* object, ExecState* exec, PropertyNameArray& propertyNames, EnumerationMode mode)
143{
144 DebuggerScope* scope = jsCast<DebuggerScope*>(object);
145 ASSERT(scope->isValid());
146 if (!scope->isValid())
147 return;
148 JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
149 thisObject->methodTable(exec->vm())->getPropertyNames(thisObject, exec, propertyNames, mode);
150}
151
152bool DebuggerScope::defineOwnProperty(JSObject* object, ExecState* exec, PropertyName propertyName, const PropertyDescriptor& descriptor, bool shouldThrow)
153{
154 DebuggerScope* scope = jsCast<DebuggerScope*>(object);
155 ASSERT(scope->isValid());
156 if (!scope->isValid())
157 return false;
158 JSObject* thisObject = JSScope::objectAtScope(scope->jsScope());
159 return thisObject->methodTable(exec->vm())->defineOwnProperty(thisObject, exec, propertyName, descriptor, shouldThrow);
160}
161
162DebuggerScope* DebuggerScope::next()
163{
164 ASSERT(isValid());
165 if (!m_next && m_scope->next()) {
166 VM& vm = *m_scope->vm();
167 DebuggerScope* nextScope = create(vm, m_scope->next());
168 m_next.set(vm, this, nextScope);
169 }
170 return m_next.get();
171}
172
173void DebuggerScope::invalidateChain()
174{
175 if (!isValid())
176 return;
177
178 DebuggerScope* scope = this;
179 while (scope) {
180 DebuggerScope* nextScope = scope->m_next.get();
181 scope->m_next.clear();
182 scope->m_scope.clear(); // This also marks this scope as invalid.
183 scope = nextScope;
184 }
185}
186
187bool DebuggerScope::isCatchScope() const
188{
189 return m_scope->isCatchScope();
190}
191
192bool DebuggerScope::isFunctionNameScope() const
193{
194 return m_scope->isFunctionNameScopeObject();
195}
196
197bool DebuggerScope::isWithScope() const
198{
199 return m_scope->isWithScope();
200}
201
202bool DebuggerScope::isGlobalScope() const
203{
204 return m_scope->isGlobalObject();
205}
206
207bool DebuggerScope::isGlobalLexicalEnvironment() const
208{
209 return m_scope->isGlobalLexicalEnvironment();
210}
211
212bool DebuggerScope::isClosureScope() const
213{
214 // In the current debugger implementation, every function or eval will create an
215 // lexical environment object. Hence, a lexical environment object implies a
216 // function or eval scope.
217 return m_scope->isVarScope() || m_scope->isLexicalScope();
218}
219
220bool DebuggerScope::isNestedLexicalScope() const
221{
222 return m_scope->isNestedLexicalScope();
223}
224
225String DebuggerScope::name() const
226{
227 SymbolTable* symbolTable = m_scope->symbolTable(*vm());
228 if (!symbolTable)
229 return String();
230
231 CodeBlock* codeBlock = symbolTable->rareDataCodeBlock();
232 if (!codeBlock)
233 return String();
234
235 return String::fromUTF8(codeBlock->inferredName());
236}
237
238DebuggerLocation DebuggerScope::location() const
239{
240 SymbolTable* symbolTable = m_scope->symbolTable(*vm());
241 if (!symbolTable)
242 return DebuggerLocation();
243
244 CodeBlock* codeBlock = symbolTable->rareDataCodeBlock();
245 if (!codeBlock)
246 return DebuggerLocation();
247
248 ScriptExecutable* executable = codeBlock->ownerExecutable();
249 return DebuggerLocation(executable);
250}
251
252JSValue DebuggerScope::caughtValue(ExecState* exec) const
253{
254 ASSERT(isCatchScope());
255 JSLexicalEnvironment* catchEnvironment = jsCast<JSLexicalEnvironment*>(m_scope.get());
256 SymbolTable* catchSymbolTable = catchEnvironment->symbolTable();
257 RELEASE_ASSERT(catchSymbolTable->size() == 1);
258 PropertyName errorName(catchSymbolTable->begin(catchSymbolTable->m_lock)->key.get());
259 PropertySlot slot(m_scope.get(), PropertySlot::InternalMethodType::Get);
260 bool success = catchEnvironment->getOwnPropertySlot(catchEnvironment, exec, errorName, slot);
261 RELEASE_ASSERT(success && slot.isValue());
262 return slot.getValue(exec, errorName);
263}
264
265} // namespace JSC
266