text: /*
   * Copyright (C) 2015-2016 Apple Inc. All rights reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
   * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
   * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
   * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
   * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
   * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
   */
  
  #pragma once
  
  #include "JSObject.h"
  #include <wtf/HashMap.h>
  
  namespace JSC {
  
  class TrackedReferences;
  
  class PropertyCondition {
  public:
      enum Kind {
          Presence,
          Absence,
          AbsenceOfSetter,
          Equivalence // An adaptive watchpoint on this will be a pair of watchpoints, and when the structure transitions, we will set the replacement watchpoint on the new structure.
      };
      
      PropertyCondition()
          : m_uid(nullptr)
          , m_kind(Presence)
      {
          memset(&u, 0, sizeof(u));
      }
      
      PropertyCondition(WTF::HashTableDeletedValueType)
          : m_uid(nullptr)
          , m_kind(Absence)
      {
          memset(&u, 0, sizeof(u));
      }
      
      static PropertyCondition presenceWithoutBarrier(UniquedStringImpl* uid, PropertyOffset offset, unsigned attributes)
      {
          PropertyCondition result;
          result.m_uid = uid;
          result.m_kind = Presence;
          result.u.presence.offset = offset;
          result.u.presence.attributes = attributes;
          return result;
      }
      
      static PropertyCondition presence(
          VM&, JSCell*, UniquedStringImpl* uid, PropertyOffset offset, unsigned attributes)
      {
          return presenceWithoutBarrier(uid, offset, attributes);
      }
  
      // NOTE: The prototype is the storedPrototype not the prototypeForLookup.
      static PropertyCondition absenceWithoutBarrier(UniquedStringImpl* uid, JSObject* prototype)
      {
          PropertyCondition result;
          result.m_uid = uid;
          result.m_kind = Absence;
          result.u.absence.prototype = prototype;
          return result;
      }
      
      static PropertyCondition absence(
          VM& vm, JSCell* owner, UniquedStringImpl* uid, JSObject* prototype)
      {
          if (owner)
              vm.heap.writeBarrier(owner);
          return absenceWithoutBarrier(uid, prototype);
      }
      
      static PropertyCondition absenceOfSetterWithoutBarrier(
          UniquedStringImpl* uid, JSObject* prototype)
      {
          PropertyCondition result;
          result.m_uid = uid;
          result.m_kind = AbsenceOfSetter;
          result.u.absence.prototype = prototype;
          return result;
      }
      
      static PropertyCondition absenceOfSetter(
          VM& vm, JSCell* owner, UniquedStringImpl* uid, JSObject* prototype)
      {
          if (owner)
              vm.heap.writeBarrier(owner);
          return absenceOfSetterWithoutBarrier(uid, prototype);
      }
      
      static PropertyCondition equivalenceWithoutBarrier(
          UniquedStringImpl* uid, JSValue value)
      {
          PropertyCondition result;
          result.m_uid = uid;
          result.m_kind = Equivalence;
          result.u.equivalence.value = JSValue::encode(value);
          return result;
      }
          
      static PropertyCondition equivalence(
          VM& vm, JSCell* owner, UniquedStringImpl* uid, JSValue value)
      {
          if (value.isCell() && owner)
              vm.heap.writeBarrier(owner);
          return equivalenceWithoutBarrier(uid, value);
      }
      
      explicit operator bool() const { return m_uid || m_kind != Presence; }
      
      Kind kind() const { return m_kind; }
      UniquedStringImpl* uid() const { return m_uid; }
      
      bool hasOffset() const { return !!*this && m_kind == Presence; };
      PropertyOffset offset() const
      {
          ASSERT(hasOffset());
          return u.presence.offset;
      }
      bool hasAttributes() const { return !!*this && m_kind == Presence; };
      unsigned attributes() const
      {
          ASSERT(hasAttributes());
          return u.presence.attributes;
      }
      
      bool hasPrototype() const { return !!*this && (m_kind == Absence || m_kind == AbsenceOfSetter); }
      JSObject* prototype() const
      {
          ASSERT(hasPrototype());
          return u.absence.prototype;
      }
      
      bool hasRequiredValue() const { return !!*this && m_kind == Equivalence; }
      JSValue requiredValue() const
      {
          ASSERT(hasRequiredValue());
          return JSValue::decode(u.equivalence.value);
      }
      
      void dumpInContext(PrintStream&, DumpContext*) const;
      void dump(PrintStream&) const;
      
      unsigned hash() const
      {
          unsigned result = WTF::PtrHash<UniquedStringImpl*>::hash(m_uid) + static_cast<unsigned>(m_kind);
          switch (m_kind) {
          case Presence:
              result ^= u.presence.offset;
              result ^= u.presence.attributes;
              break;
          case Absence:
          case AbsenceOfSetter:
              result ^= WTF::PtrHash<JSObject*>::hash(u.absence.prototype);
              break;
          case Equivalence:
              result ^= EncodedJSValueHash::hash(u.equivalence.value);
              break;
          }
          return result;
      }
      
      bool operator==(const PropertyCondition& other) const
      {
          if (m_uid != other.m_uid)
              return false;
          if (m_kind != other.m_kind)
              return false;
          switch (m_kind) {
          case Presence:
              return u.presence.offset == other.u.presence.offset
                  && u.presence.attributes == other.u.presence.attributes;
          case Absence:
          case AbsenceOfSetter:
              return u.absence.prototype == other.u.absence.prototype;
          case Equivalence:
              return u.equivalence.value == other.u.equivalence.value;
          }
          RELEASE_ASSERT_NOT_REACHED();
          return false;
      }
      
      bool isHashTableDeletedValue() const
      {
          return !m_uid && m_kind == Absence;
      }
      
      // Two conditions are compatible if they are identical or if they speak of different uids. If
      // false is returned, you have to decide how to resolve the conflict - for example if there is
      // a Presence and an Equivalence then in some cases you'll want the more general of the two
      // while in other cases you'll want the more specific of the two. This will also return false
      // for contradictions, like Presence and Absence on the same uid. By convention, invalid
      // conditions aren't compatible with anything.
      bool isCompatibleWith(const PropertyCondition& other) const
      {
          if (!*this || !other)
              return false;
          return *this == other || uid() != other.uid();
      }
      
      // Checks if the object's structure claims that the property won't be intercepted.
      bool isStillValidAssumingImpurePropertyWatchpoint(Structure*, JSObject* base = nullptr) const;
      
      // Returns true if we need an impure property watchpoint to ensure validity even if
      // isStillValidAccordingToStructure() returned true.
      bool validityRequiresImpurePropertyWatchpoint(Structure*) const;
      
      // Checks if the condition is still valid right now for the given object and structure.
      // May conservatively return false, if the object and structure alone don't guarantee the
      // condition. This happens for an Absence condition on an object that may have impure
      // properties. If the object is not supplied, then a "true" return indicates that checking if
      // an object has the given structure guarantees the condition still holds. If an object is
      // supplied, then you may need to use some other watchpoints on the object to guarantee the
      // condition in addition to the structure check.
      bool isStillValid(Structure*, JSObject* base = nullptr) const;
      
      // In some cases, the condition is not watchable, but could be made watchable by enabling the
      // appropriate watchpoint. For example, replacement watchpoints are enabled only when some
      // access is cached on the property in some structure. This is mainly to save space for
      // dictionary properties or properties that never get very hot. But, it's always safe to
      // enable watching, provided that this is called from the main thread.
      enum WatchabilityEffort {
          // This is the default. It means that we don't change the state of any Structure or
          // object, and implies that if the property happens not to be watchable then we don't make
          // it watchable. This is mandatory if calling from a JIT thread. This is also somewhat
          // preferable when first deciding whether to watch a condition for the first time (i.e.
          // not from a watchpoint fire that causes us to see if we should adapt), since a
          // watchpoint not being initialized for watching implies that maybe we don't know enough
          // yet to make it profitable to watch -- as in, the thing being watched may not have
          // stabilized yet. We prefer to only assume that a condition will hold if it has been
          // known to hold for a while already.
          MakeNoChanges,
          
          // Do what it takes to ensure that the property can be watched, if doing so has no
          // user-observable effect. For now this just means that we will ensure that a property
          // replacement watchpoint is enabled if it hadn't been enabled already. Do not use this
          // from JIT threads, since the act of enabling watchpoints is not thread-safe.
          EnsureWatchability
      };
      
      // This means that it's still valid and we could enforce validity by setting a transition
      // watchpoint on the structure and possibly an impure property watchpoint.
      bool isWatchableAssumingImpurePropertyWatchpoint(
          Structure*, JSObject* base = nullptr, WatchabilityEffort = MakeNoChanges) const;
      
      // This means that it's still valid and we could enforce validity by setting a transition
      // watchpoint on the structure.
      bool isWatchable(
          Structure*, JSObject* base = nullptr, WatchabilityEffort = MakeNoChanges) const;
      
      bool watchingRequiresStructureTransitionWatchpoint() const
      {
          // Currently, this is required for all of our conditions.
          return !!*this;
      }
      bool watchingRequiresReplacementWatchpoint() const
      {
          return !!*this && m_kind == Equivalence;
      }
      
      // This means that the objects involved in this are still live.
      bool isStillLive() const;
      
      void validateReferences(const TrackedReferences&) const;
  
      static bool isValidValueForAttributes(VM&, JSValue, unsigned attributes);
  
      bool isValidValueForPresence(VM&, JSValue) const;
  
      PropertyCondition attemptToMakeEquivalenceWithoutBarrier(VM&, JSObject* base) const;
  
  private:
      bool isWatchableWhenValid(Structure*, WatchabilityEffort) const;
  
      UniquedStringImpl* m_uid;
      Kind m_kind;
      union {
          struct {
              PropertyOffset offset;
              unsigned attributes;
          } presence;
          struct {
              JSObject* prototype;
          } absence;
          struct {
              EncodedJSValue value;
          } equivalence;
      } u;
  };
  
  struct PropertyConditionHash {
      static unsigned hash(const PropertyCondition& key) { return key.hash(); }
      static bool equal(
          const PropertyCondition& a, const PropertyCondition& b)
      {
          return a == b;
      }
      static const bool safeToCompareToEmptyOrDeleted = true;
  };
  
  } // namespace JSC
  
  namespace WTF {
  
  void printInternal(PrintStream&, JSC::PropertyCondition::Kind);
  
  template<typename T> struct DefaultHash;
  template<> struct DefaultHash<JSC::PropertyCondition> {
      typedef JSC::PropertyConditionHash Hash;
  };
  
  template<typename T> struct HashTraits;
  template<> struct HashTraits<JSC::PropertyCondition> : SimpleClassHashTraits<JSC::PropertyCondition> { };
  
  } // namespace WTF
  