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