Inline caching is wrong for custom accessors and custom values
[WebKit-https.git] / Source / JavaScriptCore / bytecode / PropertyCondition.cpp
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