1/*
2 * Copyright (C) 2012-2019 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 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#pragma once
30
31#include "JSScope.h"
32#include "PropertyDescriptor.h"
33#include "SymbolTable.h"
34#include "ThrowScope.h"
35#include "VariableWriteFireDetail.h"
36
37namespace JSC {
38
39class JSSymbolTableObject : public JSScope {
40public:
41 typedef JSScope Base;
42 static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetPropertyNames;
43
44 SymbolTable* symbolTable() const { return m_symbolTable.get(); }
45
46 JS_EXPORT_PRIVATE static bool deleteProperty(JSCell*, JSGlobalObject*, PropertyName);
47 JS_EXPORT_PRIVATE static void getOwnNonIndexPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArray&, EnumerationMode);
48
49 static ptrdiff_t offsetOfSymbolTable() { return OBJECT_OFFSETOF(JSSymbolTableObject, m_symbolTable); }
50
51 DECLARE_EXPORT_INFO;
52
53protected:
54 JSSymbolTableObject(VM& vm, Structure* structure, JSScope* scope)
55 : Base(vm, structure, scope)
56 {
57 }
58
59 JSSymbolTableObject(VM& vm, Structure* structure, JSScope* scope, SymbolTable* symbolTable)
60 : Base(vm, structure, scope)
61 {
62 ASSERT(symbolTable);
63 setSymbolTable(vm, symbolTable);
64 }
65
66 void setSymbolTable(VM& vm, SymbolTable* symbolTable)
67 {
68 ASSERT(!m_symbolTable);
69 symbolTable->notifyCreation(vm, this, "Allocated a scope");
70 m_symbolTable.set(vm, this, symbolTable);
71 }
72
73 static void visitChildren(JSCell*, SlotVisitor&);
74
75private:
76 WriteBarrier<SymbolTable> m_symbolTable;
77};
78
79template<typename SymbolTableObjectType>
80inline bool symbolTableGet(
81 SymbolTableObjectType* object, PropertyName propertyName, PropertySlot& slot)
82{
83 SymbolTable& symbolTable = *object->symbolTable();
84 ConcurrentJSLocker locker(symbolTable.m_lock);
85 SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid());
86 if (iter == symbolTable.end(locker))
87 return false;
88 SymbolTableEntry::Fast entry = iter->value;
89 ASSERT(!entry.isNull());
90
91 ScopeOffset offset = entry.scopeOffset();
92 // Defend against the inspector asking for a var after it has been optimized out.
93 if (!object->isValidScopeOffset(offset))
94 return false;
95
96 slot.setValue(object, entry.getAttributes() | PropertyAttribute::DontDelete, object->variableAt(offset).get());
97 return true;
98}
99
100template<typename SymbolTableObjectType>
101inline bool symbolTableGet(
102 SymbolTableObjectType* object, PropertyName propertyName, PropertyDescriptor& descriptor)
103{
104 SymbolTable& symbolTable = *object->symbolTable();
105 ConcurrentJSLocker locker(symbolTable.m_lock);
106 SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid());
107 if (iter == symbolTable.end(locker))
108 return false;
109 SymbolTableEntry::Fast entry = iter->value;
110 ASSERT(!entry.isNull());
111
112 ScopeOffset offset = entry.scopeOffset();
113 // Defend against the inspector asking for a var after it has been optimized out.
114 if (!object->isValidScopeOffset(offset))
115 return false;
116
117 descriptor.setDescriptor(object->variableAt(offset).get(), entry.getAttributes() | PropertyAttribute::DontDelete);
118 return true;
119}
120
121template<typename SymbolTableObjectType>
122inline bool symbolTableGet(
123 SymbolTableObjectType* object, PropertyName propertyName, PropertySlot& slot,
124 bool& slotIsWriteable)
125{
126 SymbolTable& symbolTable = *object->symbolTable();
127 ConcurrentJSLocker locker(symbolTable.m_lock);
128 SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid());
129 if (iter == symbolTable.end(locker))
130 return false;
131 SymbolTableEntry::Fast entry = iter->value;
132 ASSERT(!entry.isNull());
133
134 ScopeOffset offset = entry.scopeOffset();
135 // Defend against the inspector asking for a var after it has been optimized out.
136 if (!object->isValidScopeOffset(offset))
137 return false;
138
139 slot.setValue(object, entry.getAttributes() | PropertyAttribute::DontDelete, object->variableAt(offset).get());
140 slotIsWriteable = !entry.isReadOnly();
141 return true;
142}
143
144template<typename SymbolTableObjectType>
145ALWAYS_INLINE void symbolTablePutTouchWatchpointSet(VM& vm, SymbolTableObjectType* object, PropertyName propertyName, JSValue value, WriteBarrierBase<Unknown>* reg, WatchpointSet* set)
146{
147 reg->set(vm, object, value);
148 if (set)
149 VariableWriteFireDetail::touch(vm, set, object, propertyName);
150}
151
152template<typename SymbolTableObjectType>
153ALWAYS_INLINE void symbolTablePutInvalidateWatchpointSet(VM& vm, SymbolTableObjectType* object, PropertyName propertyName, JSValue value, WriteBarrierBase<Unknown>* reg, WatchpointSet* set)
154{
155 reg->set(vm, object, value);
156 if (set)
157 set->invalidate(vm, VariableWriteFireDetail(object, propertyName)); // Don't mess around - if we had found this statically, we would have invalidated it.
158}
159
160enum class SymbolTablePutMode {
161 Touch,
162 Invalidate
163};
164
165template<SymbolTablePutMode symbolTablePutMode, typename SymbolTableObjectType>
166inline bool symbolTablePut(SymbolTableObjectType* object, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, bool shouldThrowReadOnlyError, bool ignoreReadOnlyErrors, bool& putResult)
167{
168 VM& vm = getVM(globalObject);
169 auto scope = DECLARE_THROW_SCOPE(vm);
170
171 WatchpointSet* set = nullptr;
172 WriteBarrierBase<Unknown>* reg;
173 {
174 SymbolTable& symbolTable = *object->symbolTable();
175 // FIXME: This is very suspicious. We shouldn't need a GC-safe lock here.
176 // https://bugs.webkit.org/show_bug.cgi?id=134601
177 GCSafeConcurrentJSLocker locker(symbolTable.m_lock, vm.heap);
178 SymbolTable::Map::iterator iter = symbolTable.find(locker, propertyName.uid());
179 if (iter == symbolTable.end(locker))
180 return false;
181 bool wasFat;
182 SymbolTableEntry::Fast fastEntry = iter->value.getFast(wasFat);
183 ASSERT(!fastEntry.isNull());
184 if (fastEntry.isReadOnly() && !ignoreReadOnlyErrors) {
185 if (shouldThrowReadOnlyError)
186 throwTypeError(globalObject, scope, ReadonlyPropertyWriteError);
187 putResult = false;
188 return true;
189 }
190
191 ScopeOffset offset = fastEntry.scopeOffset();
192
193 // Defend against the inspector asking for a var after it has been optimized out.
194 if (!object->isValidScopeOffset(offset))
195 return false;
196
197 set = iter->value.watchpointSet();
198 reg = &object->variableAt(offset);
199 }
200 // I'd prefer we not hold lock while executing barriers, since I prefer to reserve
201 // the right for barriers to be able to trigger GC. And I don't want to hold VM
202 // locks while GC'ing.
203 if (symbolTablePutMode == SymbolTablePutMode::Invalidate)
204 symbolTablePutInvalidateWatchpointSet(vm, object, propertyName, value, reg, set);
205 else
206 symbolTablePutTouchWatchpointSet(vm, object, propertyName, value, reg, set);
207 putResult = true;
208 return true;
209}
210
211template<typename SymbolTableObjectType>
212inline bool symbolTablePutTouchWatchpointSet(
213 SymbolTableObjectType* object, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value,
214 bool shouldThrowReadOnlyError, bool ignoreReadOnlyErrors, bool& putResult)
215{
216 ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(object));
217 return symbolTablePut<SymbolTablePutMode::Touch>(object, globalObject, propertyName, value, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
218}
219
220template<typename SymbolTableObjectType>
221inline bool symbolTablePutInvalidateWatchpointSet(
222 SymbolTableObjectType* object, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value,
223 bool shouldThrowReadOnlyError, bool ignoreReadOnlyErrors, bool& putResult)
224{
225 ASSERT(!Heap::heap(value) || Heap::heap(value) == Heap::heap(object));
226 return symbolTablePut<SymbolTablePutMode::Invalidate>(object, globalObject, propertyName, value, shouldThrowReadOnlyError, ignoreReadOnlyErrors, putResult);
227}
228
229} // namespace JSC
230