1/*
2 * Copyright (C) 2015-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 * 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#pragma once
27
28#include "JSObject.h"
29#include <wtf/CompactPointerTuple.h>
30#include <wtf/HashMap.h>
31
32namespace JSC {
33
34class TrackedReferences;
35
36class PropertyCondition {
37public:
38 enum Kind : uint8_t {
39 Presence,
40 Absence,
41 AbsenceOfSetEffect,
42 Equivalence, // An adaptive watchpoint on this will be a pair of watchpoints, and when the structure transitions, we will set the replacement watchpoint on the new structure.
43 CustomFunctionEquivalence, // Custom value or accessor.
44 HasPrototype
45 };
46
47 using Header = CompactPointerTuple<UniquedStringImpl*, Kind>;
48
49 PropertyCondition()
50 : m_header(nullptr, Presence)
51 {
52 memset(&u, 0, sizeof(u));
53 }
54
55 PropertyCondition(WTF::HashTableDeletedValueType)
56 : m_header(nullptr, Absence)
57 {
58 memset(&u, 0, sizeof(u));
59 }
60
61 static PropertyCondition presenceWithoutBarrier(UniquedStringImpl* uid, PropertyOffset offset, unsigned attributes)
62 {
63 PropertyCondition result;
64 result.m_header = Header(uid, Presence);
65 result.u.presence.offset = offset;
66 result.u.presence.attributes = attributes;
67 return result;
68 }
69
70 static PropertyCondition presence(
71 VM&, JSCell*, UniquedStringImpl* uid, PropertyOffset offset, unsigned attributes)
72 {
73 return presenceWithoutBarrier(uid, offset, attributes);
74 }
75
76 // NOTE: The prototype is the storedPrototype not the prototypeForLookup.
77 static PropertyCondition absenceWithoutBarrier(UniquedStringImpl* uid, JSObject* prototype)
78 {
79 PropertyCondition result;
80 result.m_header = Header(uid, Absence);
81 result.u.prototype.prototype = prototype;
82 return result;
83 }
84
85 static PropertyCondition absence(
86 VM& vm, JSCell* owner, UniquedStringImpl* uid, JSObject* prototype)
87 {
88 if (owner)
89 vm.heap.writeBarrier(owner);
90 return absenceWithoutBarrier(uid, prototype);
91 }
92
93 static PropertyCondition absenceOfSetEffectWithoutBarrier(
94 UniquedStringImpl* uid, JSObject* prototype)
95 {
96 PropertyCondition result;
97 result.m_header = Header(uid, AbsenceOfSetEffect);
98 result.u.prototype.prototype = prototype;
99 return result;
100 }
101
102 static PropertyCondition absenceOfSetEffect(
103 VM& vm, JSCell* owner, UniquedStringImpl* uid, JSObject* prototype)
104 {
105 if (owner)
106 vm.heap.writeBarrier(owner);
107 return absenceOfSetEffectWithoutBarrier(uid, prototype);
108 }
109
110 static PropertyCondition equivalenceWithoutBarrier(
111 UniquedStringImpl* uid, JSValue value)
112 {
113 PropertyCondition result;
114 result.m_header = Header(uid, Equivalence);
115 result.u.equivalence.value = JSValue::encode(value);
116 return result;
117 }
118
119 static PropertyCondition equivalence(
120 VM& vm, JSCell* owner, UniquedStringImpl* uid, JSValue value)
121 {
122 if (value.isCell() && owner)
123 vm.heap.writeBarrier(owner);
124 return equivalenceWithoutBarrier(uid, value);
125 }
126
127 static PropertyCondition customFunctionEquivalence(UniquedStringImpl* uid)
128 {
129 PropertyCondition result;
130 result.m_header = Header(uid, CustomFunctionEquivalence);
131 return result;
132 }
133
134 static PropertyCondition hasPrototypeWithoutBarrier(JSObject* prototype)
135 {
136 PropertyCondition result;
137 result.m_header = Header(nullptr, HasPrototype);
138 result.u.prototype.prototype = prototype;
139 return result;
140 }
141
142 static PropertyCondition hasPrototype(VM& vm, JSCell* owner, JSObject* prototype)
143 {
144 if (owner)
145 vm.heap.writeBarrier(owner);
146 return hasPrototypeWithoutBarrier(prototype);
147 }
148
149 explicit operator bool() const { return m_header.pointer() || m_header.type() != Presence; }
150
151 Kind kind() const { return m_header.type(); }
152 UniquedStringImpl* uid() const { return m_header.pointer(); }
153
154 bool hasOffset() const { return !!*this && m_header.type() == Presence; };
155 PropertyOffset offset() const
156 {
157 ASSERT(hasOffset());
158 return u.presence.offset;
159 }
160 bool hasAttributes() const { return !!*this && m_header.type() == Presence; };
161 unsigned attributes() const
162 {
163 ASSERT(hasAttributes());
164 return u.presence.attributes;
165 }
166
167 bool hasPrototype() const
168 {
169 return !!*this
170 && (m_header.type() == Absence || m_header.type() == AbsenceOfSetEffect || m_header.type() == HasPrototype);
171 }
172 JSObject* prototype() const
173 {
174 ASSERT(hasPrototype());
175 return u.prototype.prototype;
176 }
177
178 bool hasRequiredValue() const { return !!*this && m_header.type() == Equivalence; }
179 JSValue requiredValue() const
180 {
181 ASSERT(hasRequiredValue());
182 return JSValue::decode(u.equivalence.value);
183 }
184
185 void dumpInContext(PrintStream&, DumpContext*) const;
186 void dump(PrintStream&) const;
187
188 unsigned hash() const
189 {
190 unsigned result = WTF::PtrHash<UniquedStringImpl*>::hash(m_header.pointer()) + static_cast<unsigned>(m_header.type());
191 switch (m_header.type()) {
192 case Presence:
193 result ^= u.presence.offset;
194 result ^= u.presence.attributes;
195 break;
196 case Absence:
197 case AbsenceOfSetEffect:
198 case HasPrototype:
199 result ^= WTF::PtrHash<JSObject*>::hash(u.prototype.prototype);
200 break;
201 case Equivalence:
202 result ^= EncodedJSValueHash::hash(u.equivalence.value);
203 break;
204 case CustomFunctionEquivalence:
205 break;
206 }
207 return result;
208 }
209
210 bool operator==(const PropertyCondition& other) const
211 {
212 if (m_header.pointer() != other.m_header.pointer())
213 return false;
214 if (m_header.type() != other.m_header.type())
215 return false;
216 switch (m_header.type()) {
217 case Presence:
218 return u.presence.offset == other.u.presence.offset
219 && u.presence.attributes == other.u.presence.attributes;
220 case Absence:
221 case AbsenceOfSetEffect:
222 case HasPrototype:
223 return u.prototype.prototype == other.u.prototype.prototype;
224 case Equivalence:
225 return u.equivalence.value == other.u.equivalence.value;
226 case CustomFunctionEquivalence:
227 return true;
228 }
229 RELEASE_ASSERT_NOT_REACHED();
230 return false;
231 }
232
233 bool isHashTableDeletedValue() const
234 {
235 return !m_header.pointer() && m_header.type() == Absence;
236 }
237
238 // Two conditions are compatible if they are identical or if they speak of different uids. If
239 // false is returned, you have to decide how to resolve the conflict - for example if there is
240 // a Presence and an Equivalence then in some cases you'll want the more general of the two
241 // while in other cases you'll want the more specific of the two. This will also return false
242 // for contradictions, like Presence and Absence on the same uid. By convention, invalid
243 // conditions aren't compatible with anything.
244 bool isCompatibleWith(const PropertyCondition& other) const
245 {
246 if (!*this || !other)
247 return false;
248 return *this == other || uid() != other.uid();
249 }
250
251 // Checks if the object's structure claims that the property won't be intercepted.
252 bool isStillValidAssumingImpurePropertyWatchpoint(Structure*, JSObject* base = nullptr) const;
253
254 // Returns true if we need an impure property watchpoint to ensure validity even if
255 // isStillValidAccordingToStructure() returned true.
256 bool validityRequiresImpurePropertyWatchpoint(Structure*) const;
257
258 // Checks if the condition is still valid right now for the given object and structure.
259 // May conservatively return false, if the object and structure alone don't guarantee the
260 // condition. This happens for an Absence condition on an object that may have impure
261 // properties. If the object is not supplied, then a "true" return indicates that checking if
262 // an object has the given structure guarantees the condition still holds. If an object is
263 // supplied, then you may need to use some other watchpoints on the object to guarantee the
264 // condition in addition to the structure check.
265 bool isStillValid(Structure*, JSObject* base = nullptr) const;
266
267 // In some cases, the condition is not watchable, but could be made watchable by enabling the
268 // appropriate watchpoint. For example, replacement watchpoints are enabled only when some
269 // access is cached on the property in some structure. This is mainly to save space for
270 // dictionary properties or properties that never get very hot. But, it's always safe to
271 // enable watching, provided that this is called from the main thread.
272 enum WatchabilityEffort {
273 // This is the default. It means that we don't change the state of any Structure or
274 // object, and implies that if the property happens not to be watchable then we don't make
275 // it watchable. This is mandatory if calling from a JIT thread. This is also somewhat
276 // preferable when first deciding whether to watch a condition for the first time (i.e.
277 // not from a watchpoint fire that causes us to see if we should adapt), since a
278 // watchpoint not being initialized for watching implies that maybe we don't know enough
279 // yet to make it profitable to watch -- as in, the thing being watched may not have
280 // stabilized yet. We prefer to only assume that a condition will hold if it has been
281 // known to hold for a while already.
282 MakeNoChanges,
283
284 // Do what it takes to ensure that the property can be watched, if doing so has no
285 // user-observable effect. For now this just means that we will ensure that a property
286 // replacement watchpoint is enabled if it hadn't been enabled already. Do not use this
287 // from JIT threads, since the act of enabling watchpoints is not thread-safe.
288 EnsureWatchability
289 };
290
291 // This means that it's still valid and we could enforce validity by setting a transition
292 // watchpoint on the structure and possibly an impure property watchpoint.
293 bool isWatchableAssumingImpurePropertyWatchpoint(
294 Structure*, JSObject* base, WatchabilityEffort = MakeNoChanges) const;
295
296 // This means that it's still valid and we could enforce validity by setting a transition
297 // watchpoint on the structure.
298 bool isWatchable(
299 Structure*, JSObject*, WatchabilityEffort = MakeNoChanges) const;
300
301 bool watchingRequiresStructureTransitionWatchpoint() const
302 {
303 // Currently, this is required for all of our conditions.
304 return !!*this;
305 }
306 bool watchingRequiresReplacementWatchpoint() const
307 {
308 return !!*this && m_header.type() == Equivalence;
309 }
310
311 template<typename Functor>
312 void forEachDependentCell(const Functor& functor) const
313 {
314 if (hasPrototype() && prototype())
315 functor(prototype());
316
317 if (hasRequiredValue() && requiredValue() && requiredValue().isCell())
318 functor(requiredValue().asCell());
319 }
320
321 void validateReferences(const TrackedReferences&) const;
322
323 static bool isValidValueForAttributes(VM&, JSValue, unsigned attributes);
324
325 bool isValidValueForPresence(VM&, JSValue) const;
326
327 PropertyCondition attemptToMakeEquivalenceWithoutBarrier(VM&, JSObject* base) const;
328
329private:
330 bool isWatchableWhenValid(Structure*, WatchabilityEffort) const;
331
332 Header m_header;
333 union {
334 struct {
335 PropertyOffset offset;
336 unsigned attributes;
337 } presence;
338 struct {
339 JSObject* prototype;
340 } prototype;
341 struct {
342 EncodedJSValue value;
343 } equivalence;
344 } u;
345};
346
347struct PropertyConditionHash {
348 static unsigned hash(const PropertyCondition& key) { return key.hash(); }
349 static bool equal(
350 const PropertyCondition& a, const PropertyCondition& b)
351 {
352 return a == b;
353 }
354 static constexpr bool safeToCompareToEmptyOrDeleted = true;
355};
356
357} // namespace JSC
358
359namespace WTF {
360
361void printInternal(PrintStream&, JSC::PropertyCondition::Kind);
362
363template<typename T> struct DefaultHash;
364template<> struct DefaultHash<JSC::PropertyCondition> {
365 typedef JSC::PropertyConditionHash Hash;
366};
367
368template<typename T> struct HashTraits;
369template<> struct HashTraits<JSC::PropertyCondition> : SimpleClassHashTraits<JSC::PropertyCondition> { };
370
371} // namespace WTF
372