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 | |
33 | namespace JSC { |
34 | |
35 | namespace PropertyConditionInternal { |
36 | static bool verbose = false; |
37 | } |
38 | |
39 | void 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 | |
64 | void PropertyCondition::dump(PrintStream& out) const |
65 | { |
66 | dumpInContext(out, nullptr); |
67 | } |
68 | |
69 | bool 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 | |
257 | bool 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 | |
276 | bool 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 | |
301 | bool 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 | |
341 | bool PropertyCondition::isWatchableAssumingImpurePropertyWatchpoint( |
342 | Structure* structure, JSObject* base, WatchabilityEffort effort) const |
343 | { |
344 | return isStillValidAssumingImpurePropertyWatchpoint(structure, base) |
345 | && isWatchableWhenValid(structure, effort); |
346 | } |
347 | |
348 | bool PropertyCondition::isWatchable( |
349 | Structure* structure, JSObject* base, WatchabilityEffort effort) const |
350 | { |
351 | return isStillValid(structure, base) |
352 | && isWatchableWhenValid(structure, effort); |
353 | } |
354 | |
355 | bool 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 | |
369 | void PropertyCondition::validateReferences(const TrackedReferences& tracked) const |
370 | { |
371 | if (hasPrototype()) |
372 | tracked.check(prototype()); |
373 | |
374 | if (hasRequiredValue()) |
375 | tracked.check(requiredValue()); |
376 | } |
377 | |
378 | bool 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 | |
387 | bool PropertyCondition::isValidValueForPresence(VM& vm, JSValue value) const |
388 | { |
389 | return isValidValueForAttributes(vm, value, attributes()); |
390 | } |
391 | |
392 | PropertyCondition 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 | |
404 | namespace WTF { |
405 | |
406 | void 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 | |