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