1/*
2 * Copyright (C) 1999-2000 Harri Porten ([email protected])
3 * Copyright (C) 2003-2019 Apple Inc. All rights reserved.
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library; if not, write to the Free Software
17 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
18 *
19 */
20
21#pragma once
22
23#include "BatchedTransitionOptimizer.h"
24#include "CallFrame.h"
25#include "CustomGetterSetter.h"
26#include "DOMJITGetterSetter.h"
27#include "DOMJITSignature.h"
28#include "Identifier.h"
29#include "IdentifierInlines.h"
30#include "Intrinsic.h"
31#include "JSFunction.h"
32#include "JSGlobalObject.h"
33#include "LazyProperty.h"
34#include "PropertySlot.h"
35#include "PutPropertySlot.h"
36#include "TypeError.h"
37#include <wtf/Assertions.h>
38
39namespace JSC {
40
41struct CompactHashIndex {
42 const int16_t value;
43 const int16_t next;
44};
45
46// FIXME: There is no reason this get function can't be simpler.
47// ie. typedef JSValue (*GetFunction)(JSGlobalObject*, JSObject* baseObject)
48typedef PropertySlot::GetValueFunc GetFunction;
49typedef PutPropertySlot::PutValueFunc PutFunction;
50typedef FunctionExecutable* (*BuiltinGenerator)(VM&);
51typedef JSValue (*LazyPropertyCallback)(VM&, JSObject*);
52
53// Hash table generated by the create_hash_table script.
54struct HashTableValue {
55 const char* m_key; // property name
56 unsigned m_attributes; // JSObject attributes
57 Intrinsic m_intrinsic;
58 union ValueStorage {
59 constexpr ValueStorage(intptr_t value1, intptr_t value2)
60 : value1(value1)
61 , value2(value2)
62 { }
63 constexpr ValueStorage(long long constant)
64 : constant(constant)
65 { }
66
67 struct {
68 intptr_t value1;
69 intptr_t value2;
70 };
71 long long constant;
72 } m_values;
73
74 unsigned attributes() const { return m_attributes; }
75
76 Intrinsic intrinsic() const { ASSERT(m_attributes & PropertyAttribute::Function); return m_intrinsic; }
77 BuiltinGenerator builtinGenerator() const { ASSERT(m_attributes & PropertyAttribute::Builtin); return reinterpret_cast<BuiltinGenerator>(m_values.value1); }
78 NativeFunction function() const { ASSERT(m_attributes & PropertyAttribute::Function); return NativeFunction(m_values.value1); }
79 unsigned char functionLength() const
80 {
81 ASSERT(m_attributes & PropertyAttribute::Function);
82 if (m_attributes & PropertyAttribute::DOMJITFunction)
83 return signature()->argumentCount;
84 return static_cast<unsigned char>(m_values.value2);
85 }
86
87 GetFunction propertyGetter() const { ASSERT(!(m_attributes & PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyPropertyOrConstant)); return reinterpret_cast<GetFunction>(m_values.value1); }
88 PutFunction propertyPutter() const { ASSERT(!(m_attributes & PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyPropertyOrConstant)); return reinterpret_cast<PutFunction>(m_values.value2); }
89
90 const DOMJIT::GetterSetter* domJIT() const { ASSERT(m_attributes & PropertyAttribute::DOMJITAttribute); return reinterpret_cast<const DOMJIT::GetterSetter*>(m_values.value1); }
91 const DOMJIT::Signature* signature() const { ASSERT(m_attributes & PropertyAttribute::DOMJITFunction); return reinterpret_cast<const DOMJIT::Signature*>(m_values.value2); }
92
93 NativeFunction accessorGetter() const { ASSERT(m_attributes & PropertyAttribute::Accessor); return NativeFunction(m_values.value1); }
94 NativeFunction accessorSetter() const { ASSERT(m_attributes & PropertyAttribute::Accessor); return NativeFunction(m_values.value2); }
95 BuiltinGenerator builtinAccessorGetterGenerator() const;
96 BuiltinGenerator builtinAccessorSetterGenerator() const;
97
98 long long constantInteger() const { ASSERT(m_attributes & PropertyAttribute::ConstantInteger); return m_values.constant; }
99
100 intptr_t lexerValue() const { ASSERT(!m_attributes); return m_values.value1; }
101
102 ptrdiff_t lazyCellPropertyOffset() const { ASSERT(m_attributes & PropertyAttribute::CellProperty); return m_values.value1; }
103 ptrdiff_t lazyClassStructureOffset() const { ASSERT(m_attributes & PropertyAttribute::ClassStructure); return m_values.value1; }
104 LazyPropertyCallback lazyPropertyCallback() const { ASSERT(m_attributes & PropertyAttribute::PropertyCallback); return reinterpret_cast<LazyPropertyCallback>(m_values.value1); }
105};
106
107struct HashTable {
108 int numberOfValues;
109 int indexMask;
110 bool hasSetterOrReadonlyProperties;
111 const ClassInfo* classForThis; // Used by DOMAttribute. Attribute accessors perform type check against this classInfo.
112
113 const HashTableValue* values; // Fixed values generated by script.
114 const CompactHashIndex* index;
115
116 // Find an entry in the table, and return the entry.
117 ALWAYS_INLINE const HashTableValue* entry(PropertyName propertyName) const
118 {
119 if (propertyName.isSymbol())
120 return nullptr;
121
122 auto uid = propertyName.uid();
123 if (!uid)
124 return nullptr;
125
126 int indexEntry = IdentifierRepHash::hash(uid) & indexMask;
127 int valueIndex = index[indexEntry].value;
128 if (valueIndex == -1)
129 return nullptr;
130
131 while (true) {
132 if (WTF::equal(uid, values[valueIndex].m_key))
133 return &values[valueIndex];
134
135 indexEntry = index[indexEntry].next;
136 if (indexEntry == -1)
137 return nullptr;
138 valueIndex = index[indexEntry].value;
139 ASSERT(valueIndex != -1);
140 };
141 }
142
143 class ConstIterator {
144 public:
145 ConstIterator(const HashTable* table, int position)
146 : m_table(table)
147 , m_position(position)
148 {
149 skipInvalidKeys();
150 }
151
152 const HashTableValue* value() const
153 {
154 return &m_table->values[m_position];
155 }
156
157 const HashTableValue& operator*() const { return *value(); }
158
159 const char* key() const
160 {
161 return m_table->values[m_position].m_key;
162 }
163
164 const HashTableValue* operator->() const
165 {
166 return value();
167 }
168
169 bool operator!=(const ConstIterator& other) const
170 {
171 ASSERT(m_table == other.m_table);
172 return m_position != other.m_position;
173 }
174
175 ConstIterator& operator++()
176 {
177 ASSERT(m_position < m_table->numberOfValues);
178 ++m_position;
179 skipInvalidKeys();
180 return *this;
181 }
182
183 private:
184 void skipInvalidKeys()
185 {
186 ASSERT(m_position <= m_table->numberOfValues);
187 while (m_position < m_table->numberOfValues && !m_table->values[m_position].m_key)
188 ++m_position;
189 ASSERT(m_position <= m_table->numberOfValues);
190 }
191
192 const HashTable* m_table;
193 int m_position;
194 };
195
196 ConstIterator begin() const
197 {
198 return ConstIterator(this, 0);
199 }
200 ConstIterator end() const
201 {
202 return ConstIterator(this, numberOfValues);
203 }
204};
205
206JS_EXPORT_PRIVATE bool setUpStaticFunctionSlot(VM&, const ClassInfo*, const HashTableValue*, JSObject* thisObject, PropertyName, PropertySlot&);
207JS_EXPORT_PRIVATE void reifyStaticAccessor(VM&, const HashTableValue&, JSObject& thisObject, PropertyName);
208
209inline BuiltinGenerator HashTableValue::builtinAccessorGetterGenerator() const
210{
211 ASSERT(m_attributes & PropertyAttribute::Accessor);
212 ASSERT(m_attributes & PropertyAttribute::Builtin);
213 return reinterpret_cast<BuiltinGenerator>(m_values.value1);
214}
215
216inline BuiltinGenerator HashTableValue::builtinAccessorSetterGenerator() const
217{
218 ASSERT(m_attributes & PropertyAttribute::Accessor);
219 ASSERT(m_attributes & PropertyAttribute::Builtin);
220 return reinterpret_cast<BuiltinGenerator>(m_values.value2);
221}
222
223inline bool getStaticPropertySlotFromTable(VM& vm, const ClassInfo* classInfo, const HashTable& table, JSObject* thisObject, PropertyName propertyName, PropertySlot& slot)
224{
225 if (thisObject->staticPropertiesReified(vm))
226 return false;
227
228 auto* entry = table.entry(propertyName);
229 if (!entry)
230 return false;
231
232 if (entry->attributes() & PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyProperty)
233 return setUpStaticFunctionSlot(vm, classInfo, entry, thisObject, propertyName, slot);
234
235 if (entry->attributes() & PropertyAttribute::ConstantInteger) {
236 slot.setValue(thisObject, attributesForStructure(entry->attributes()), jsNumber(entry->constantInteger()));
237 return true;
238 }
239
240 if (entry->attributes() & PropertyAttribute::DOMJITAttribute) {
241 const DOMJIT::GetterSetter* domJIT = entry->domJIT();
242 slot.setCacheableCustom(thisObject, attributesForStructure(entry->attributes()), domJIT->getter(), DOMAttributeAnnotation { classInfo, domJIT });
243 return true;
244 }
245
246 if (entry->attributes() & PropertyAttribute::DOMAttribute) {
247 slot.setCacheableCustom(thisObject, attributesForStructure(entry->attributes()), entry->propertyGetter(), DOMAttributeAnnotation { classInfo, nullptr });
248 return true;
249 }
250
251 slot.setCacheableCustom(thisObject, attributesForStructure(entry->attributes()), entry->propertyGetter());
252 return true;
253}
254
255inline bool replaceStaticPropertySlot(VM& vm, JSObject* thisObject, PropertyName propertyName, JSValue value)
256{
257 if (!thisObject->putDirect(vm, propertyName, value))
258 return false;
259
260 if (!thisObject->staticPropertiesReified(vm))
261 thisObject->JSObject::setStructure(vm, Structure::attributeChangeTransition(vm, thisObject->structure(vm), propertyName, 0));
262
263 return true;
264}
265
266// 'base' means the object holding the property (possibly in the prototype chain of the object put was called on).
267// 'thisValue' is the object that put is being applied to (in the case of a proxy, the proxy target).
268// 'slot.thisValue()' is the object the put was originally performed on (in the case of a proxy, the proxy itself).
269inline bool putEntry(JSGlobalObject* globalObject, const ClassInfo*, const HashTableValue* entry, JSObject* base, JSObject* thisValue, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
270{
271 VM& vm = getVM(globalObject);
272 auto scope = DECLARE_THROW_SCOPE(vm);
273
274 if (entry->attributes() & PropertyAttribute::BuiltinOrFunctionOrLazyProperty) {
275 if (!(entry->attributes() & PropertyAttribute::ReadOnly)) {
276 // If this is a function or lazy property put then we just do the put because
277 // logically the object already had the property, so this is just a replace.
278 if (JSObject* thisObject = jsDynamicCast<JSObject*>(vm, thisValue))
279 thisObject->putDirect(vm, propertyName, value);
280 return true;
281 }
282 return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
283 }
284
285 if (entry->attributes() & PropertyAttribute::Accessor)
286 return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
287
288 if (!(entry->attributes() & PropertyAttribute::ReadOnly)) {
289 ASSERT_WITH_MESSAGE(!(entry->attributes() & PropertyAttribute::DOMJITAttribute), "DOMJITAttribute supports readonly attributes currently.");
290 bool isAccessor = entry->attributes() & PropertyAttribute::CustomAccessor;
291 JSValue updateThisValue = entry->attributes() & PropertyAttribute::CustomAccessor ? slot.thisValue() : JSValue(base);
292 // We need to make sure that we decide to cache this property before we potentially execute aribitrary JS.
293 if (isAccessor)
294 slot.setCustomAccessor(base, entry->propertyPutter());
295 else
296 slot.setCustomValue(base, entry->propertyPutter());
297
298 bool result = callCustomSetter(globalObject, entry->propertyPutter(), isAccessor, updateThisValue, value);
299 RETURN_IF_EXCEPTION(scope, false);
300 return result;
301 }
302
303 return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
304}
305
306/**
307 * This one is for "put".
308 * It looks up a hash entry for the property to be set. If an entry
309 * is found it sets the value and returns true, else it returns false.
310 */
311inline bool lookupPut(JSGlobalObject* globalObject, PropertyName propertyName, JSObject* base, JSValue value, const HashTable& table, PutPropertySlot& slot, bool& putResult)
312{
313 const HashTableValue* entry = table.entry(propertyName);
314
315 if (!entry)
316 return false;
317
318 putResult = putEntry(globalObject, table.classForThis, entry, base, base, propertyName, value, slot);
319 return true;
320}
321
322inline void reifyStaticProperty(VM& vm, const ClassInfo* classInfo, const PropertyName& propertyName, const HashTableValue& value, JSObject& thisObj)
323{
324 if (value.attributes() & PropertyAttribute::Builtin) {
325 if (value.attributes() & PropertyAttribute::Accessor)
326 reifyStaticAccessor(vm, value, thisObj, propertyName);
327 else
328 thisObj.putDirectBuiltinFunction(vm, thisObj.globalObject(vm), propertyName, value.builtinGenerator()(vm), attributesForStructure(value.attributes()));
329 return;
330 }
331
332 if (value.attributes() & PropertyAttribute::Function) {
333 if (value.attributes() & PropertyAttribute::DOMJITFunction) {
334 thisObj.putDirectNativeFunction(
335 vm, thisObj.globalObject(vm), propertyName, value.functionLength(),
336 value.function(), value.intrinsic(), value.signature(), attributesForStructure(value.attributes()));
337 return;
338 }
339 thisObj.putDirectNativeFunction(
340 vm, thisObj.globalObject(vm), propertyName, value.functionLength(),
341 value.function(), value.intrinsic(), attributesForStructure(value.attributes()));
342 return;
343 }
344
345 if (value.attributes() & PropertyAttribute::ConstantInteger) {
346 thisObj.putDirect(vm, propertyName, jsNumber(value.constantInteger()), attributesForStructure(value.attributes()));
347 return;
348 }
349
350 if (value.attributes() & PropertyAttribute::Accessor) {
351 reifyStaticAccessor(vm, value, thisObj, propertyName);
352 return;
353 }
354
355 if (value.attributes() & PropertyAttribute::CellProperty) {
356 LazyCellProperty* property = bitwise_cast<LazyCellProperty*>(
357 bitwise_cast<char*>(&thisObj) + value.lazyCellPropertyOffset());
358 JSCell* result = property->get(&thisObj);
359 thisObj.putDirect(vm, propertyName, result, attributesForStructure(value.attributes()));
360 return;
361 }
362
363 if (value.attributes() & PropertyAttribute::ClassStructure) {
364 LazyClassStructure* lazyStructure = bitwise_cast<LazyClassStructure*>(
365 bitwise_cast<char*>(&thisObj) + value.lazyClassStructureOffset());
366 JSObject* constructor = lazyStructure->constructor(jsCast<JSGlobalObject*>(&thisObj));
367 thisObj.putDirect(vm, propertyName, constructor, attributesForStructure(value.attributes()));
368 return;
369 }
370
371 if (value.attributes() & PropertyAttribute::PropertyCallback) {
372 JSValue result = value.lazyPropertyCallback()(vm, &thisObj);
373 thisObj.putDirect(vm, propertyName, result, attributesForStructure(value.attributes()));
374 return;
375 }
376
377 if (value.attributes() & PropertyAttribute::DOMJITAttribute) {
378 ASSERT_WITH_MESSAGE(classInfo, "DOMJITAttribute should have class info for type checking.");
379 const DOMJIT::GetterSetter* domJIT = value.domJIT();
380 auto* customGetterSetter = DOMAttributeGetterSetter::create(vm, domJIT->getter(), value.propertyPutter(), DOMAttributeAnnotation { classInfo, domJIT });
381 thisObj.putDirectCustomAccessor(vm, propertyName, customGetterSetter, attributesForStructure(value.attributes()));
382 return;
383 }
384
385 if (value.attributes() & PropertyAttribute::DOMAttribute) {
386 ASSERT_WITH_MESSAGE(classInfo, "DOMAttribute should have class info for type checking.");
387 auto* customGetterSetter = DOMAttributeGetterSetter::create(vm, value.propertyGetter(), value.propertyPutter(), DOMAttributeAnnotation { classInfo, nullptr });
388 thisObj.putDirectCustomAccessor(vm, propertyName, customGetterSetter, attributesForStructure(value.attributes()));
389 return;
390 }
391
392 CustomGetterSetter* customGetterSetter = CustomGetterSetter::create(vm, value.propertyGetter(), value.propertyPutter());
393 thisObj.putDirectCustomAccessor(vm, propertyName, customGetterSetter, attributesForStructure(value.attributes()));
394}
395
396template<unsigned numberOfValues>
397inline void reifyStaticProperties(VM& vm, const ClassInfo* classInfo, const HashTableValue (&values)[numberOfValues], JSObject& thisObj)
398{
399 BatchedTransitionOptimizer transitionOptimizer(vm, &thisObj);
400 for (auto& value : values) {
401 if (!value.m_key)
402 continue;
403 auto key = Identifier::fromString(vm, reinterpret_cast<const LChar*>(value.m_key), strlen(value.m_key));
404 reifyStaticProperty(vm, classInfo, key, value, thisObj);
405 }
406}
407
408template<RawNativeFunction nativeFunction, int length> EncodedJSValue nonCachingStaticFunctionGetter(JSGlobalObject* globalObject, EncodedJSValue, PropertyName propertyName)
409{
410 return JSValue::encode(JSFunction::create(globalObject->vm(), globalObject, length, propertyName.publicName(), nativeFunction));
411}
412
413} // namespace JSC
414