/ bytecode / ObjectPropertyConditionSet.cpp
ObjectPropertyConditionSet.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 "ObjectPropertyConditionSet.h"
 28  
 29  #include "JSCInlines.h"
 30  #include <wtf/ListDump.h>
 31  
 32  namespace JSC {
 33  
 34  ObjectPropertyCondition ObjectPropertyConditionSet::forObject(JSObject* object) const
 35  {
 36      for (const ObjectPropertyCondition& condition : *this) {
 37          if (condition.object() == object)
 38              return condition;
 39      }
 40      return ObjectPropertyCondition();
 41  }
 42  
 43  ObjectPropertyCondition ObjectPropertyConditionSet::forConditionKind(
 44      PropertyCondition::Kind kind) const
 45  {
 46      for (const ObjectPropertyCondition& condition : *this) {
 47          if (condition.kind() == kind)
 48              return condition;
 49      }
 50      return ObjectPropertyCondition();
 51  }
 52  
 53  unsigned ObjectPropertyConditionSet::numberOfConditionsWithKind(PropertyCondition::Kind kind) const
 54  {
 55      unsigned result = 0;
 56      for (const ObjectPropertyCondition& condition : *this) {
 57          if (condition.kind() == kind)
 58              result++;
 59      }
 60      return result;
 61  }
 62  
 63  bool ObjectPropertyConditionSet::hasOneSlotBaseCondition() const
 64  {
 65      bool sawBase = false;
 66      for (const ObjectPropertyCondition& condition : *this) {
 67          switch (condition.kind()) {
 68          case PropertyCondition::Presence:
 69          case PropertyCondition::Equivalence:
 70          case PropertyCondition::HasStaticProperty:
 71              if (sawBase)
 72                  return false;
 73              sawBase = true;
 74              break;
 75          default:
 76              break;
 77          }
 78      }
 79  
 80      return sawBase;
 81  }
 82  
 83  ObjectPropertyCondition ObjectPropertyConditionSet::slotBaseCondition() const
 84  {
 85      ObjectPropertyCondition result;
 86      unsigned numFound = 0;
 87      for (const ObjectPropertyCondition& condition : *this) {
 88          if (condition.kind() == PropertyCondition::Presence
 89              || condition.kind() == PropertyCondition::Equivalence
 90              || condition.kind() == PropertyCondition::HasStaticProperty) {
 91              result = condition;
 92              numFound++;
 93          }
 94      }
 95      RELEASE_ASSERT(numFound == 1);
 96      return result;
 97  }
 98  
 99  ObjectPropertyConditionSet ObjectPropertyConditionSet::mergedWith(
100      const ObjectPropertyConditionSet& other) const
101  {
102      if (!isValid() || !other.isValid())
103          return invalid();
104  
105      Vector<ObjectPropertyCondition> result;
106      
107      if (!isEmpty())
108          result.appendVector(m_data->vector);
109      
110      for (const ObjectPropertyCondition& newCondition : other) {
111          bool foundMatch = false;
112          for (const ObjectPropertyCondition& existingCondition : *this) {
113              if (newCondition == existingCondition) {
114                  foundMatch = true;
115                  continue;
116              }
117              if (!newCondition.isCompatibleWith(existingCondition))
118                  return invalid();
119          }
120          if (!foundMatch)
121              result.append(newCondition);
122      }
123  
124      return create(result);
125  }
126  
127  bool ObjectPropertyConditionSet::structuresEnsureValidity() const
128  {
129      if (!isValid())
130          return false;
131      
132      for (const ObjectPropertyCondition& condition : *this) {
133          if (!condition.structureEnsuresValidity())
134              return false;
135      }
136      return true;
137  }
138  
139  bool ObjectPropertyConditionSet::structuresEnsureValidityAssumingImpurePropertyWatchpoint() const
140  {
141      if (!isValid())
142          return false;
143      
144      for (const ObjectPropertyCondition& condition : *this) {
145          if (!condition.structureEnsuresValidityAssumingImpurePropertyWatchpoint())
146              return false;
147      }
148      return true;
149  }
150  
151  bool ObjectPropertyConditionSet::needImpurePropertyWatchpoint() const
152  {
153      for (const ObjectPropertyCondition& condition : *this) {
154          if (condition.validityRequiresImpurePropertyWatchpoint())
155              return true;
156      }
157      return false;
158  }
159  
160  bool ObjectPropertyConditionSet::areStillLive(VM& vm) const
161  {
162      bool stillLive = true;
163      forEachDependentCell([&](JSCell* cell) {
164          stillLive &= vm.heap.isMarked(cell);
165      });
166      return stillLive;
167  }
168  
169  void ObjectPropertyConditionSet::dumpInContext(PrintStream& out, DumpContext* context) const
170  {
171      if (!isValid()) {
172          out.print("<invalid>");
173          return;
174      }
175      
176      out.print("[");
177      if (m_data)
178          out.print(listDumpInContext(m_data->vector, context));
179      out.print("]");
180  }
181  
182  void ObjectPropertyConditionSet::dump(PrintStream& out) const
183  {
184      dumpInContext(out, nullptr);
185  }
186  
187  bool ObjectPropertyConditionSet::isValidAndWatchable() const
188  {
189      if (!isValid())
190          return false;
191  
192      for (ObjectPropertyCondition condition : m_data->vector) {
193          if (!condition.isWatchable())
194              return false;
195      }
196      return true;
197  }
198  
199  namespace {
200  
201  namespace ObjectPropertyConditionSetInternal {
202  static constexpr bool verbose = false;
203  }
204  
205  ObjectPropertyCondition generateCondition(
206      VM& vm, JSCell* owner, JSObject* object, UniquedStringImpl* uid, PropertyCondition::Kind conditionKind)
207  {
208      Structure* structure = object->structure(vm);
209      if (ObjectPropertyConditionSetInternal::verbose)
210          dataLog("Creating condition ", conditionKind, " for ", pointerDump(structure), "\n");
211  
212      ObjectPropertyCondition result;
213      switch (conditionKind) {
214      case PropertyCondition::Presence: {
215          unsigned attributes;
216          PropertyOffset offset = structure->getConcurrently(uid, attributes);
217          if (offset == invalidOffset)
218              return ObjectPropertyCondition();
219          result = ObjectPropertyCondition::presence(vm, owner, object, uid, offset, attributes);
220          break;
221      }
222      case PropertyCondition::Absence: {
223          if (structure->hasPolyProto())
224              return ObjectPropertyCondition();
225          result = ObjectPropertyCondition::absence(
226              vm, owner, object, uid, object->structure(vm)->storedPrototypeObject());
227          break;
228      }
229      case PropertyCondition::AbsenceOfSetEffect: {
230          if (structure->hasPolyProto())
231              return ObjectPropertyCondition();
232          result = ObjectPropertyCondition::absenceOfSetEffect(
233              vm, owner, object, uid, object->structure(vm)->storedPrototypeObject());
234          break;
235      }
236      case PropertyCondition::Equivalence: {
237          unsigned attributes;
238          PropertyOffset offset = structure->getConcurrently(uid, attributes);
239          if (offset == invalidOffset)
240              return ObjectPropertyCondition();
241          JSValue value = object->getDirectConcurrently(structure, offset);
242          if (!value)
243              return ObjectPropertyCondition();
244          result = ObjectPropertyCondition::equivalence(vm, owner, object, uid, value);
245          break;
246      }
247      case PropertyCondition::HasStaticProperty: {
248          auto entry = object->findPropertyHashEntry(vm, uid);
249          if (!entry)
250              return ObjectPropertyCondition();
251          result = ObjectPropertyCondition::hasStaticProperty(vm, owner, object, uid);
252          break;
253      }
254      default:
255          RELEASE_ASSERT_NOT_REACHED();
256          return ObjectPropertyCondition();
257      }
258  
259      if (!result.isStillValidAssumingImpurePropertyWatchpoint()) {
260          if (ObjectPropertyConditionSetInternal::verbose)
261              dataLog("Failed to create condition: ", result, "\n");
262          return ObjectPropertyCondition();
263      }
264  
265      if (ObjectPropertyConditionSetInternal::verbose)
266          dataLog("New condition: ", result, "\n");
267      return result;
268  }
269  
270  template<typename Functor>
271  ObjectPropertyConditionSet generateConditions(
272      VM& vm, JSGlobalObject* globalObject, Structure* structure, JSObject* prototype, const Functor& functor)
273  {
274      Vector<ObjectPropertyCondition> conditions;
275      
276      for (;;) {
277          if (ObjectPropertyConditionSetInternal::verbose)
278              dataLog("Considering structure: ", pointerDump(structure), "\n");
279          
280          if (structure->isProxy()) {
281              if (ObjectPropertyConditionSetInternal::verbose)
282                  dataLog("It's a proxy, so invalid.\n");
283              return ObjectPropertyConditionSet::invalid();
284          }
285  
286          if (structure->hasPolyProto()) {
287              // FIXME: Integrate this with PolyProtoAccessChain:
288              // https://bugs.webkit.org/show_bug.cgi?id=177339
289              // Or at least allow OPC set generation when the
290              // base is not poly proto:
291              // https://bugs.webkit.org/show_bug.cgi?id=177721
292              return ObjectPropertyConditionSet::invalid();
293          }
294          
295          JSValue value = structure->prototypeForLookup(globalObject);
296          
297          if (value.isNull()) {
298              if (!prototype) {
299                  if (ObjectPropertyConditionSetInternal::verbose)
300                      dataLog("Reached end of prototype chain as expected, done.\n");
301                  break;
302              }
303              if (ObjectPropertyConditionSetInternal::verbose)
304                  dataLog("Unexpectedly reached end of prototype chain, so invalid.\n");
305              return ObjectPropertyConditionSet::invalid();
306          }
307          
308          JSObject* object = jsCast<JSObject*>(value);
309          structure = object->structure(vm);
310          
311          if (structure->isDictionary()) {
312              if (ObjectPropertyConditionSetInternal::verbose)
313                  dataLog("Cannot cache dictionary.\n");
314              return ObjectPropertyConditionSet::invalid();
315          }
316  
317          if (!functor(conditions, object)) {
318              if (ObjectPropertyConditionSetInternal::verbose)
319                  dataLog("Functor failed, invalid.\n");
320              return ObjectPropertyConditionSet::invalid();
321          }
322          
323          if (object == prototype) {
324              if (ObjectPropertyConditionSetInternal::verbose)
325                  dataLog("Reached desired prototype, done.\n");
326              break;
327          }
328      }
329  
330      if (ObjectPropertyConditionSetInternal::verbose)
331          dataLog("Returning conditions: ", listDump(conditions), "\n");
332      return ObjectPropertyConditionSet::create(conditions);
333  }
334  
335  } // anonymous namespace
336  
337  ObjectPropertyConditionSet generateConditionsForPropertyMiss(
338      VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
339  {
340      return generateConditions(
341          vm, globalObject, headStructure, nullptr,
342          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
343              ObjectPropertyCondition result =
344                  generateCondition(vm, owner, object, uid, PropertyCondition::Absence);
345              if (!result)
346                  return false;
347              conditions.append(result);
348              return true;
349          });
350  }
351  
352  ObjectPropertyConditionSet generateConditionsForPropertySetterMiss(
353      VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
354  {
355      return generateConditions(
356          vm, globalObject, headStructure, nullptr,
357          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
358              ObjectPropertyCondition result =
359                  generateCondition(vm, owner, object, uid, PropertyCondition::AbsenceOfSetEffect);
360              if (!result)
361                  return false;
362              conditions.append(result);
363              return true;
364          });
365  }
366  
367  ObjectPropertyConditionSet generateConditionsForPrototypePropertyHit(
368      VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure, JSObject* prototype,
369      UniquedStringImpl* uid)
370  {
371      return generateConditions(
372          vm, globalObject, headStructure, prototype,
373          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
374              PropertyCondition::Kind kind =
375                  object == prototype ? PropertyCondition::Presence : PropertyCondition::Absence;
376              ObjectPropertyCondition result =
377                  generateCondition(vm, owner, object, uid, kind);
378              if (!result)
379                  return false;
380              conditions.append(result);
381              return true;
382          });
383  }
384  
385  ObjectPropertyConditionSet generateConditionsForPrototypePropertyHitCustom(
386      VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure, JSObject* prototype,
387      UniquedStringImpl* uid, unsigned attributes)
388  {
389      return generateConditions(
390          vm, globalObject, headStructure, prototype,
391          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
392              auto kind = PropertyCondition::Absence;
393              if (object == prototype) {
394                  Structure* structure = object->structure(vm);
395                  PropertyOffset offset = structure->get(vm, uid);
396                  if (isValidOffset(offset)) {
397                      // When we reify custom accessors, we wrap them in a JSFunction that we shove
398                      // inside a GetterSetter. So, once we've reified a custom accessor, we will
399                      // no longer see it as a "custom" accessor/value. Hence, if our property access actually
400                      // notices a custom, it must be a CustomGetterSetterType cell or something
401                      // in the static property table. Custom values get reified into CustomGetterSetters.
402                      JSValue value = object->getDirect(offset);
403  
404                      if (!value.isCell() || value.asCell()->type() != CustomGetterSetterType) {
405                          // The value could have just got changed to some other type, so check if it's still
406                          // a custom getter setter.
407                          return false;
408                      }
409  
410                      kind = PropertyCondition::Equivalence;
411                  } else if (structure->findPropertyHashEntry(uid))
412                      kind = PropertyCondition::HasStaticProperty;
413                  else if (attributes & PropertyAttribute::DontDelete) {
414                      // This can't change, so we can blindly cache it.
415                      return true;
416                  } else {
417                      // This means we materialized a custom out of thin air and it's not DontDelete (i.e, it can be
418                      // redefined). This is curious. We don't actually need to crash here. We could blindly cache
419                      // the function. Or we could blindly not cache it. However, we don't actually do this in WebKit
420                      // right now, so it's reasonable to decide what to do later (or to warn people of forgetting DoneDelete.)
421                      ASSERT_NOT_REACHED();
422                      return false;
423                  }
424              }
425              ObjectPropertyCondition result = generateCondition(vm, owner, object, uid, kind);
426              if (!result)
427                  return false;
428              conditions.append(result);
429              return true;
430          });
431  }
432  
433  ObjectPropertyConditionSet generateConditionsForInstanceOf(
434      VM& vm, JSCell* owner, JSGlobalObject* globalObject, Structure* headStructure, JSObject* prototype,
435      bool shouldHit)
436  {
437      bool didHit = false;
438      if (ObjectPropertyConditionSetInternal::verbose)
439          dataLog("Searching for prototype ", JSValue(prototype), " starting with structure ", RawPointer(headStructure), " with shouldHit = ", shouldHit, "\n");
440      ObjectPropertyConditionSet result = generateConditions(
441          vm, globalObject, headStructure, shouldHit ? prototype : nullptr,
442          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
443              if (ObjectPropertyConditionSetInternal::verbose)
444                  dataLog("Encountered object: ", RawPointer(object), "\n");
445              if (object == prototype) {
446                  RELEASE_ASSERT(shouldHit);
447                  didHit = true;
448                  return true;
449              }
450  
451              Structure* structure = object->structure(vm);
452              if (structure->hasPolyProto())
453                  return false;
454              conditions.append(
455                  ObjectPropertyCondition::hasPrototype(
456                      vm, owner, object, structure->storedPrototypeObject()));
457              return true;
458          });
459      if (result.isValid()) {
460          if (ObjectPropertyConditionSetInternal::verbose)
461              dataLog("didHit = ", didHit, ", shouldHit = ", shouldHit, "\n");
462          RELEASE_ASSERT(didHit == shouldHit);
463      }
464      return result;
465  }
466  
467  ObjectPropertyConditionSet generateConditionsForPrototypeEquivalenceConcurrently(
468      VM& vm, JSGlobalObject* globalObject, Structure* headStructure, JSObject* prototype, UniquedStringImpl* uid)
469  {
470      return generateConditions(vm, globalObject, headStructure, prototype,
471          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
472              PropertyCondition::Kind kind =
473                  object == prototype ? PropertyCondition::Equivalence : PropertyCondition::Absence;
474              ObjectPropertyCondition result = generateCondition(vm, nullptr, object, uid, kind);
475              if (!result)
476                  return false;
477              conditions.append(result);
478              return true;
479          });
480  }
481  
482  ObjectPropertyConditionSet generateConditionsForPropertyMissConcurrently(
483      VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
484  {
485      return generateConditions(
486          vm, globalObject, headStructure, nullptr,
487          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
488              ObjectPropertyCondition result = generateCondition(vm, nullptr, object, uid, PropertyCondition::Absence);
489              if (!result)
490                  return false;
491              conditions.append(result);
492              return true;
493          });
494  }
495  
496  ObjectPropertyConditionSet generateConditionsForPropertySetterMissConcurrently(
497      VM& vm, JSGlobalObject* globalObject, Structure* headStructure, UniquedStringImpl* uid)
498  {
499      return generateConditions(
500          vm, globalObject, headStructure, nullptr,
501          [&] (Vector<ObjectPropertyCondition>& conditions, JSObject* object) -> bool {
502              ObjectPropertyCondition result =
503                  generateCondition(vm, nullptr, object, uid, PropertyCondition::AbsenceOfSetEffect);
504              if (!result)
505                  return false;
506              conditions.append(result);
507              return true;
508          });
509  }
510  
511  ObjectPropertyCondition generateConditionForSelfEquivalence(
512      VM& vm, JSCell* owner, JSObject* object, UniquedStringImpl* uid)
513  {
514      return generateCondition(vm, owner, object, uid, PropertyCondition::Equivalence);
515  }
516  
517  // Current might be null. Structure can't be null.
518  static Optional<PrototypeChainCachingStatus> prepareChainForCaching(JSGlobalObject* globalObject, JSCell* current, Structure* structure, JSObject* target)
519  {
520      ASSERT(structure);
521      VM& vm = globalObject->vm();
522  
523      bool found = false;
524      bool usesPolyProto = false;
525      bool flattenedDictionary = false;
526  
527      while (true) {
528          if (structure->isDictionary()) {
529              if (!current)
530                  return WTF::nullopt;
531  
532              ASSERT(structure->isObject());
533              if (structure->hasBeenFlattenedBefore())
534                  return WTF::nullopt;
535  
536              structure->flattenDictionaryStructure(vm, asObject(current));
537              flattenedDictionary = true;
538          }
539  
540          if (!structure->propertyAccessesAreCacheable())
541              return WTF::nullopt;
542  
543          if (structure->isProxy())
544              return WTF::nullopt;
545  
546          if (current && current == target) {
547              found = true;
548              break;
549          }
550  
551          // We only have poly proto if we need to access our prototype via
552          // the poly proto protocol. If the slot base is the only poly proto
553          // thing in the chain, and we have a cache hit on it, then we're not
554          // poly proto.
555          JSValue prototype;
556          if (structure->hasPolyProto()) {
557              if (!current)
558                  return WTF::nullopt;
559              usesPolyProto = true;
560              prototype = structure->prototypeForLookup(globalObject, current);
561          } else
562              prototype = structure->prototypeForLookup(globalObject);
563  
564          if (prototype.isNull())
565              break;
566          current = asObject(prototype);
567          structure = current->structure(vm);
568      }
569  
570      if (!found && !!target)
571          return WTF::nullopt;
572  
573      PrototypeChainCachingStatus result;
574      result.usesPolyProto = usesPolyProto;
575      result.flattenedDictionary = flattenedDictionary;
576  
577      return result;
578  }
579  
580  Optional<PrototypeChainCachingStatus> prepareChainForCaching(JSGlobalObject* globalObject, JSCell* base, JSObject* target)
581  {
582      return prepareChainForCaching(globalObject, base, base->structure(globalObject->vm()), target);
583  }
584  
585  Optional<PrototypeChainCachingStatus> prepareChainForCaching(JSGlobalObject* globalObject, JSCell* base, const PropertySlot& slot)
586  {
587      JSObject* target = slot.isUnset() ? nullptr : slot.slotBase();
588      return prepareChainForCaching(globalObject, base, target);
589  }
590  
591  Optional<PrototypeChainCachingStatus> prepareChainForCaching(JSGlobalObject* globalObject, Structure* baseStructure, JSObject* target)
592  {
593      return prepareChainForCaching(globalObject, nullptr, baseStructure, target);
594  }
595  
596  } // namespace JSC
597