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#include "config.h"
27#include "PropertyCondition.h"
28
29#include "GetterSetter.h"
30#include "JSCInlines.h"
31#include "TrackedReferences.h"
32
33namespace JSC {
34
35namespace PropertyConditionInternal {
36static bool verbose = false;
37}
38
39void PropertyCondition::dumpInContext(PrintStream& out, DumpContext* context) const
40{
41 if (!*this) {
42 out.print("<invalid>");
43 return;
44 }
45
46 switch (m_header.type()) {
47 case Presence:
48 out.print(m_header.type(), " of ", m_header.pointer(), " at ", offset(), " with attributes ", attributes());
49 return;
50 case Absence:
51 case AbsenceOfSetEffect:
52 out.print(m_header.type(), " of ", m_header.pointer(), " with prototype ", inContext(JSValue(prototype()), context));
53 return;
54 case Equivalence:
55 out.print(m_header.type(), " of ", m_header.pointer(), " with ", inContext(requiredValue(), context));
56 return;
57 case HasPrototype:
58 out.print(m_header.type(), " with prototype ", inContext(JSValue(prototype()), context));
59 return;
60 }
61 RELEASE_ASSERT_NOT_REACHED();
62}
63
64void PropertyCondition::dump(PrintStream& out) const
65{
66 dumpInContext(out, nullptr);
67}
68
69bool PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint(
70 Structure* structure, JSObject* base) const
71{
72 if (PropertyConditionInternal::verbose) {
73 dataLog(
74 "Determining validity of ", *this, " with structure ", pointerDump(structure), " and base ",
75 JSValue(base), " assuming impure property watchpoints are set.\n");
76 }
77
78 if (!*this) {
79 if (PropertyConditionInternal::verbose)
80 dataLog("Invalid because unset.\n");
81 return false;
82 }
83
84 switch (m_header.type()) {
85 case Presence:
86 case Absence:
87 case AbsenceOfSetEffect:
88 case Equivalence:
89 if (!structure->propertyAccessesAreCacheable()) {
90 if (PropertyConditionInternal::verbose)
91 dataLog("Invalid because property accesses are not cacheable.\n");
92 return false;
93 }
94 break;
95
96 case HasPrototype:
97 if (!structure->prototypeQueriesAreCacheable()) {
98 if (PropertyConditionInternal::verbose)
99 dataLog("Invalid because prototype queries are not cacheable.\n");
100 return false;
101 }
102 break;
103 }
104
105 switch (m_header.type()) {
106 case Presence: {
107 unsigned currentAttributes;
108 PropertyOffset currentOffset = structure->getConcurrently(uid(), currentAttributes);
109 if (currentOffset != offset() || currentAttributes != attributes()) {
110 if (PropertyConditionInternal::verbose) {
111 dataLog(
112 "Invalid because we need offset, attributes to be ", offset(), ", ", attributes(),
113 " but they are ", currentOffset, ", ", currentAttributes, "\n");
114 }
115 return false;
116 }
117 return true;
118 }
119
120 case Absence: {
121 if (structure->isDictionary()) {
122 if (PropertyConditionInternal::verbose)
123 dataLog("Invalid because it's a dictionary.\n");
124 return false;
125 }
126
127 if (structure->hasPolyProto()) {
128 // FIXME: I think this is too conservative. We can probably prove this if
129 // we have the base. Anyways, we should make this work when integrating
130 // OPC and poly proto.
131 // https://bugs.webkit.org/show_bug.cgi?id=177339
132 return false;
133 }
134
135 PropertyOffset currentOffset = structure->getConcurrently(uid());
136 if (currentOffset != invalidOffset) {
137 if (PropertyConditionInternal::verbose)
138 dataLog("Invalid because the property exists at offset: ", currentOffset, "\n");
139 return false;
140 }
141
142 if (structure->storedPrototypeObject() != prototype()) {
143 if (PropertyConditionInternal::verbose) {
144 dataLog(
145 "Invalid because the prototype is ", structure->storedPrototype(), " even though "
146 "it should have been ", JSValue(prototype()), "\n");
147 }
148 return false;
149 }
150
151 return true;
152 }
153
154 case AbsenceOfSetEffect: {
155 if (structure->isDictionary()) {
156 if (PropertyConditionInternal::verbose)
157 dataLog("Invalid because it's a dictionary.\n");
158 return false;
159 }
160
161 unsigned currentAttributes;
162 PropertyOffset currentOffset = structure->getConcurrently(uid(), currentAttributes);
163 if (currentOffset != invalidOffset) {
164 if (currentAttributes & (PropertyAttribute::ReadOnly | PropertyAttribute::Accessor | PropertyAttribute::CustomAccessorOrValue)) {
165 if (PropertyConditionInternal::verbose) {
166 dataLog(
167 "Invalid because we expected not to have a setter, but we have one at offset ",
168 currentOffset, " with attributes ", currentAttributes, "\n");
169 }
170 return false;
171 }
172 }
173
174 if (structure->hasPolyProto()) {
175 // FIXME: I think this is too conservative. We can probably prove this if
176 // we have the base. Anyways, we should make this work when integrating
177 // OPC and poly proto.
178 // https://bugs.webkit.org/show_bug.cgi?id=177339
179 return false;
180 }
181
182 if (structure->storedPrototypeObject() != prototype()) {
183 if (PropertyConditionInternal::verbose) {
184 dataLog(
185 "Invalid because the prototype is ", structure->storedPrototype(), " even though "
186 "it should have been ", JSValue(prototype()), "\n");
187 }
188 return false;
189 }
190
191 return true;
192 }
193
194 case HasPrototype: {
195 if (structure->hasPolyProto()) {
196 // FIXME: I think this is too conservative. We can probably prove this if
197 // we have the base. Anyways, we should make this work when integrating
198 // OPC and poly proto.
199 // https://bugs.webkit.org/show_bug.cgi?id=177339
200 return false;
201 }
202
203 if (structure->storedPrototypeObject() != prototype()) {
204 if (PropertyConditionInternal::verbose) {
205 dataLog(
206 "Invalid because the prototype is ", structure->storedPrototype(), " even though "
207 "it should have been ", JSValue(prototype()), "\n");
208 }
209 return false;
210 }
211
212 return true;
213 }
214
215 case Equivalence: {
216 if (!base || base->structure() != structure) {
217 // Conservatively return false, since we cannot verify this one without having the
218 // object.
219 if (PropertyConditionInternal::verbose) {
220 dataLog(
221 "Invalid because we don't have a base or the base has the wrong structure: ",
222 RawPointer(base), "\n");
223 }
224 return false;
225 }
226
227 // FIXME: This is somewhat racy, and maybe more risky than we want.
228 // https://bugs.webkit.org/show_bug.cgi?id=134641
229
230 PropertyOffset currentOffset = structure->getConcurrently(uid());
231 if (currentOffset == invalidOffset) {
232 if (PropertyConditionInternal::verbose) {
233 dataLog(
234 "Invalid because the base no long appears to have ", uid(), " on its structure: ",
235 RawPointer(base), "\n");
236 }
237 return false;
238 }
239
240 JSValue currentValue = base->getDirectConcurrently(structure, currentOffset);
241 if (currentValue != requiredValue()) {
242 if (PropertyConditionInternal::verbose) {
243 dataLog(
244 "Invalid because the value is ", currentValue, " but we require ", requiredValue(),
245 "\n");
246 }
247 return false;
248 }
249
250 return true;
251 } }
252
253 RELEASE_ASSERT_NOT_REACHED();
254 return false;
255}
256
257bool PropertyCondition::validityRequiresImpurePropertyWatchpoint(Structure* structure) const
258{
259 if (!*this)
260 return false;
261
262 switch (m_header.type()) {
263 case Presence:
264 case Absence:
265 case Equivalence:
266 return structure->needImpurePropertyWatchpoint();
267 case AbsenceOfSetEffect:
268 case HasPrototype:
269 return false;
270 }
271
272 RELEASE_ASSERT_NOT_REACHED();
273 return false;
274}
275
276bool PropertyCondition::isStillValid(Structure* structure, JSObject* base) const
277{
278 if (!isStillValidAssumingImpurePropertyWatchpoint(structure, base))
279 return false;
280
281 // Currently we assume that an impure property can cause a property to appear, and can also
282 // "shadow" an existing JS property on the same object. Hence it affects both presence and
283 // absence. It doesn't affect AbsenceOfSetEffect because impure properties aren't ever setters.
284 switch (m_header.type()) {
285 case Absence:
286 if (structure->typeInfo().getOwnPropertySlotIsImpure() || structure->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence())
287 return false;
288 break;
289 case Presence:
290 case Equivalence:
291 if (structure->typeInfo().getOwnPropertySlotIsImpure())
292 return false;
293 break;
294 default:
295 break;
296 }
297
298 return true;
299}
300
301bool PropertyCondition::isWatchableWhenValid(
302 Structure* structure, WatchabilityEffort effort) const
303{
304 if (structure->transitionWatchpointSetHasBeenInvalidated())
305 return false;
306
307 switch (m_header.type()) {
308 case Equivalence: {
309 PropertyOffset offset = structure->getConcurrently(uid());
310
311 // This method should only be called when some variant of isValid returned true, which
312 // implies that we already confirmed that the structure knows of the property. We should
313 // also have verified that the Structure is a cacheable dictionary, which means we
314 // shouldn't have a TOCTOU race either.
315 RELEASE_ASSERT(offset != invalidOffset);
316
317 WatchpointSet* set = nullptr;
318 switch (effort) {
319 case MakeNoChanges:
320 set = structure->propertyReplacementWatchpointSet(offset);
321 break;
322 case EnsureWatchability:
323 set = structure->ensurePropertyReplacementWatchpointSet(
324 *structure->vm(), offset);
325 break;
326 }
327
328 if (!set || !set->isStillValid())
329 return false;
330
331 break;
332 }
333
334 default:
335 break;
336 }
337
338 return true;
339}
340
341bool PropertyCondition::isWatchableAssumingImpurePropertyWatchpoint(
342 Structure* structure, JSObject* base, WatchabilityEffort effort) const
343{
344 return isStillValidAssumingImpurePropertyWatchpoint(structure, base)
345 && isWatchableWhenValid(structure, effort);
346}
347
348bool PropertyCondition::isWatchable(
349 Structure* structure, JSObject* base, WatchabilityEffort effort) const
350{
351 return isStillValid(structure, base)
352 && isWatchableWhenValid(structure, effort);
353}
354
355bool PropertyCondition::isStillLive(VM& vm) const
356{
357 if (hasPrototype() && prototype() && !vm.heap.isMarked(prototype()))
358 return false;
359
360 if (hasRequiredValue()
361 && requiredValue()
362 && requiredValue().isCell()
363 && !vm.heap.isMarked(requiredValue().asCell()))
364 return false;
365
366 return true;
367}
368
369void PropertyCondition::validateReferences(const TrackedReferences& tracked) const
370{
371 if (hasPrototype())
372 tracked.check(prototype());
373
374 if (hasRequiredValue())
375 tracked.check(requiredValue());
376}
377
378bool PropertyCondition::isValidValueForAttributes(VM& vm, JSValue value, unsigned attributes)
379{
380 if (!value)
381 return false;
382 bool attributesClaimAccessor = !!(attributes & PropertyAttribute::Accessor);
383 bool valueClaimsAccessor = !!jsDynamicCast<GetterSetter*>(vm, value);
384 return attributesClaimAccessor == valueClaimsAccessor;
385}
386
387bool PropertyCondition::isValidValueForPresence(VM& vm, JSValue value) const
388{
389 return isValidValueForAttributes(vm, value, attributes());
390}
391
392PropertyCondition PropertyCondition::attemptToMakeEquivalenceWithoutBarrier(VM& vm, JSObject* base) const
393{
394 Structure* structure = base->structure(vm);
395
396 JSValue value = base->getDirectConcurrently(structure, offset());
397 if (!isValidValueForPresence(vm, value))
398 return PropertyCondition();
399 return equivalenceWithoutBarrier(uid(), value);
400}
401
402} // namespace JSC
403
404namespace WTF {
405
406void printInternal(PrintStream& out, JSC::PropertyCondition::Kind condition)
407{
408 switch (condition) {
409 case JSC::PropertyCondition::Presence:
410 out.print("Presence");
411 return;
412 case JSC::PropertyCondition::Absence:
413 out.print("Absence");
414 return;
415 case JSC::PropertyCondition::AbsenceOfSetEffect:
416 out.print("Absence");
417 return;
418 case JSC::PropertyCondition::Equivalence:
419 out.print("Equivalence");
420 return;
421 case JSC::PropertyCondition::HasPrototype:
422 out.print("HasPrototype");
423 return;
424 }
425 RELEASE_ASSERT_NOT_REACHED();
426}
427
428} // namespace WTF
429