Shrink size of PropertyCondition by packing UniquedStringImpl* and Kind
[WebKit-https.git] / Source / JavaScriptCore / bytecode / PropertyCondition.cpp
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::CustomAccessor)) {
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() const
356 {
357     if (hasPrototype() && prototype() && !Heap::isMarked(prototype()))
358         return false;
359     
360     if (hasRequiredValue()
361         && requiredValue()
362         && requiredValue().isCell()
363         && !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