/* * 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. */ #ifndef PropertyCondition_h #define PropertyCondition_h #include "JSObject.h" #include 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::hash(m_uid) + static_cast(m_kind); switch (m_kind) { case Presence: result ^= u.presence.offset; result ^= u.presence.attributes; break; case Absence: case AbsenceOfSetter: result ^= WTF::PtrHash::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(JSValue value, unsigned attributes); bool isValidValueForPresence(JSValue) const; PropertyCondition attemptToMakeEquivalenceWithoutBarrier(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 struct DefaultHash; template<> struct DefaultHash { typedef JSC::PropertyConditionHash Hash; }; template struct HashTraits; template<> struct HashTraits : SimpleClassHashTraits { }; } // namespace WTF #endif // PropertyCondition_h