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 | |
33 | namespace JSC { |
34 | |
35 | STATIC_ASSERT_IS_TRIVIALLY_DESTRUCTIBLE(DebuggerScope); |
36 | |
37 | const ClassInfo DebuggerScope::s_info = { "DebuggerScope" , &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(DebuggerScope) }; |
38 | |
39 | DebuggerScope* 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 | |
47 | DebuggerScope::DebuggerScope(VM& vm, Structure* structure, JSScope* scope) |
48 | : JSNonFinalObject(vm, structure) |
49 | { |
50 | ASSERT(scope); |
51 | m_scope.set(vm, this, scope); |
52 | } |
53 | |
54 | void DebuggerScope::finishCreation(VM& vm) |
55 | { |
56 | Base::finishCreation(vm); |
57 | } |
58 | |
59 | void 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 | |
69 | String 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 | |
80 | String 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 | |
91 | bool 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 | |
121 | bool 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 | |
132 | bool 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 | |
142 | void 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 | |
152 | bool 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 | |
162 | DebuggerScope* 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 | |
173 | void 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 | |
187 | bool DebuggerScope::isCatchScope() const |
188 | { |
189 | return m_scope->isCatchScope(); |
190 | } |
191 | |
192 | bool DebuggerScope::isFunctionNameScope() const |
193 | { |
194 | return m_scope->isFunctionNameScopeObject(); |
195 | } |
196 | |
197 | bool DebuggerScope::isWithScope() const |
198 | { |
199 | return m_scope->isWithScope(); |
200 | } |
201 | |
202 | bool DebuggerScope::isGlobalScope() const |
203 | { |
204 | return m_scope->isGlobalObject(); |
205 | } |
206 | |
207 | bool DebuggerScope::isGlobalLexicalEnvironment() const |
208 | { |
209 | return m_scope->isGlobalLexicalEnvironment(); |
210 | } |
211 | |
212 | bool 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 | |
220 | bool DebuggerScope::isNestedLexicalScope() const |
221 | { |
222 | return m_scope->isNestedLexicalScope(); |
223 | } |
224 | |
225 | String 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 | |
238 | DebuggerLocation 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 | |
252 | JSValue 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 | |