/ bytecode / PropertyCondition.cpp
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 HasStaticProperty:
 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 HasStaticProperty:
 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 HasStaticProperty: {
257          if (isValidOffset(structure->getConcurrently(uid())))
258              return false;
259          if (structure->staticPropertiesReified())
260              return false;
261          return !!structure->findPropertyHashEntry(uid());
262      }
263      }
264      
265      RELEASE_ASSERT_NOT_REACHED();
266      return false;
267  }
268  
269  bool PropertyCondition::validityRequiresImpurePropertyWatchpoint(Structure* structure) const
270  {
271      if (!*this)
272          return false;
273      
274      switch (m_header.type()) {
275      case Presence:
276      case Absence:
277      case Equivalence:
278      case HasStaticProperty:
279          return structure->needImpurePropertyWatchpoint();
280      case AbsenceOfSetEffect:
281      case HasPrototype:
282          return false;
283      }
284      
285      RELEASE_ASSERT_NOT_REACHED();
286      return false;
287  }
288  
289  bool PropertyCondition::isStillValid(Structure* structure, JSObject* base) const
290  {
291      if (!isStillValidAssumingImpurePropertyWatchpoint(structure, base))
292          return false;
293  
294      // Currently we assume that an impure property can cause a property to appear, and can also
295      // "shadow" an existing JS property on the same object. Hence it affects both presence and
296      // absence. It doesn't affect AbsenceOfSetEffect because impure properties aren't ever setters.
297      switch (m_header.type()) {
298      case Absence:
299          if (structure->typeInfo().getOwnPropertySlotIsImpure() || structure->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence())
300              return false;
301          break;
302      case Presence:
303      case Equivalence:
304      case HasStaticProperty:
305          if (structure->typeInfo().getOwnPropertySlotIsImpure())
306              return false;
307          break;
308      default:
309          break;
310      }
311      
312      return true;
313  }
314  
315  bool PropertyCondition::isWatchableWhenValid(
316      Structure* structure, WatchabilityEffort effort) const
317  {
318      if (structure->transitionWatchpointSetHasBeenInvalidated())
319          return false;
320      
321      switch (m_header.type()) {
322      case Equivalence: {
323          PropertyOffset offset = structure->getConcurrently(uid());
324          
325          // This method should only be called when some variant of isValid returned true, which
326          // implies that we already confirmed that the structure knows of the property. We should
327          // also have verified that the Structure is a cacheable dictionary, which means we
328          // shouldn't have a TOCTOU race either.
329          RELEASE_ASSERT(offset != invalidOffset);
330          
331          WatchpointSet* set = nullptr;
332          switch (effort) {
333          case MakeNoChanges:
334              set = structure->propertyReplacementWatchpointSet(offset);
335              break;
336          case EnsureWatchability:
337              set = structure->ensurePropertyReplacementWatchpointSet(structure->vm(), offset);
338              break;
339          }
340          
341          if (!set || !set->isStillValid())
342              return false;
343          
344          break;
345      }
346  
347      case HasStaticProperty: {
348          // We just use the structure transition watchpoint for this. A structure S starts
349          // off with a property P in the static property hash table. If S transitions to
350          // S', either P remains in the static property table or not. If not, then we
351          // are no longer valid. So the above check of transitionWatchpointSetHasBeenInvalidated
352          // is sufficient.
353          //
354          // We could make this smarter in the future, since we sometimes reify static properties.
355          // We could make this adapt to looking at the object's storage for such reified custom
356          // functions, but we don't do that right now. We just allow this property condition to
357          // invalidate and create an Equivalence watchpoint for the materialized property sometime
358          // in the future.
359          break;
360      }
361          
362      default:
363          break;
364      }
365      
366      return true;
367  }
368  
369  bool PropertyCondition::isWatchableAssumingImpurePropertyWatchpoint(
370      Structure* structure, JSObject* base, WatchabilityEffort effort) const
371  {
372      return isStillValidAssumingImpurePropertyWatchpoint(structure, base)
373          && isWatchableWhenValid(structure, effort);
374  }
375  
376  bool PropertyCondition::isWatchable(
377      Structure* structure, JSObject* base, WatchabilityEffort effort) const
378  {
379      return isStillValid(structure, base)
380          && isWatchableWhenValid(structure, effort);
381  }
382  
383  void PropertyCondition::validateReferences(const TrackedReferences& tracked) const
384  {
385      if (hasPrototype())
386          tracked.check(prototype());
387      
388      if (hasRequiredValue())
389          tracked.check(requiredValue());
390  }
391  
392  bool PropertyCondition::isValidValueForAttributes(VM& vm, JSValue value, unsigned attributes)
393  {
394      if (!value)
395          return false;
396      bool attributesClaimAccessor = !!(attributes & PropertyAttribute::Accessor);
397      bool valueClaimsAccessor = !!jsDynamicCast<GetterSetter*>(vm, value);
398      return attributesClaimAccessor == valueClaimsAccessor;
399  }
400  
401  bool PropertyCondition::isValidValueForPresence(VM& vm, JSValue value) const
402  {
403      return isValidValueForAttributes(vm, value, attributes());
404  }
405  
406  PropertyCondition PropertyCondition::attemptToMakeEquivalenceWithoutBarrier(VM& vm, JSObject* base) const
407  {
408      Structure* structure = base->structure(vm);
409  
410      JSValue value = base->getDirectConcurrently(structure, offset());
411      if (!isValidValueForPresence(vm, value))
412          return PropertyCondition();
413      return equivalenceWithoutBarrier(uid(), value);
414  }
415  
416  } // namespace JSC
417  
418  namespace WTF {
419  
420  void printInternal(PrintStream& out, JSC::PropertyCondition::Kind condition)
421  {
422      switch (condition) {
423      case JSC::PropertyCondition::Presence:
424          out.print("Presence");
425          return;
426      case JSC::PropertyCondition::Absence:
427          out.print("Absence");
428          return;
429      case JSC::PropertyCondition::AbsenceOfSetEffect:
430          out.print("Absence");
431          return;
432      case JSC::PropertyCondition::Equivalence:
433          out.print("Equivalence");
434          return;
435      case JSC::PropertyCondition::HasStaticProperty:
436          out.print("HasStaticProperty");
437          return;
438      case JSC::PropertyCondition::HasPrototype:
439          out.print("HasPrototype");
440          return;
441      }
442      RELEASE_ASSERT_NOT_REACHED();
443  }
444  
445  } // namespace WTF