/* * Copyright (C) 2015 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. */ #include "config.h" #include "PropertyCondition.h" #include "GetterSetter.h" #include "JSCInlines.h" #include "TrackedReferences.h" namespace JSC { static bool verbose = false; void PropertyCondition::dumpInContext(PrintStream& out, DumpContext* context) const { if (!*this) { out.print(""); return; } out.print(m_kind, " of ", m_uid); switch (m_kind) { case Presence: out.print(" at ", offset(), " with attributes ", attributes()); return; case Absence: case AbsenceOfSetter: out.print(" with prototype ", inContext(JSValue(prototype()), context)); return; case Equivalence: out.print(" with ", inContext(requiredValue(), context)); return; } RELEASE_ASSERT_NOT_REACHED(); } void PropertyCondition::dump(PrintStream& out) const { dumpInContext(out, nullptr); } bool PropertyCondition::isStillValidAssumingImpurePropertyWatchpoint( Structure* structure, JSObject* base) const { if (verbose) { dataLog( "Determining validity of ", *this, " with structure ", pointerDump(structure), " and base ", JSValue(base), " assuming impure property watchpoints are set.\n"); } if (!*this) { if (verbose) dataLog("Invalid because unset.\n"); return false; } if (!structure->propertyAccessesAreCacheable()) { if (verbose) dataLog("Invalid because accesses are not cacheable.\n"); return false; } switch (m_kind) { case Presence: { unsigned currentAttributes; PropertyOffset currentOffset = structure->getConcurrently(uid(), currentAttributes); if (currentOffset != offset() || currentAttributes != attributes()) { if (verbose) { dataLog( "Invalid because we need offset, attributes to be ", offset(), ", ", attributes(), " but they are ", currentOffset, ", ", currentAttributes, "\n"); } return false; } return true; } case Absence: { if (structure->isDictionary()) { if (verbose) dataLog("Invalid because it's a dictionary.\n"); return false; } PropertyOffset currentOffset = structure->getConcurrently(uid()); if (currentOffset != invalidOffset) { if (verbose) dataLog("Invalid because the property exists at offset: ", currentOffset, "\n"); return false; } if (structure->storedPrototypeObject() != prototype()) { if (verbose) { dataLog( "Invalid because the prototype is ", structure->storedPrototype(), " even though " "it should have been ", JSValue(prototype()), "\n"); } return false; } return true; } case AbsenceOfSetter: { if (structure->isDictionary()) { if (verbose) dataLog("Invalid because it's a dictionary.\n"); return false; } unsigned currentAttributes; PropertyOffset currentOffset = structure->getConcurrently(uid(), currentAttributes); if (currentOffset != invalidOffset) { if (currentAttributes & (Accessor | CustomAccessor)) { if (verbose) { dataLog( "Invalid because we expected not to have a setter, but we have one at offset ", currentOffset, " with attributes ", currentAttributes, "\n"); } return false; } } if (structure->storedPrototypeObject() != prototype()) { if (verbose) { dataLog( "Invalid because the prototype is ", structure->storedPrototype(), " even though " "it should have been ", JSValue(prototype()), "\n"); } return false; } return true; } case Equivalence: { if (!base || base->structure() != structure) { // Conservatively return false, since we cannot verify this one without having the // object. if (verbose) { dataLog( "Invalid because we don't have a base or the base has the wrong structure: ", RawPointer(base), "\n"); } return false; } // FIXME: This is somewhat racy, and maybe more risky than we want. // https://bugs.webkit.org/show_bug.cgi?id=134641 PropertyOffset currentOffset = structure->getConcurrently(uid()); if (currentOffset == invalidOffset) { if (verbose) { dataLog( "Invalid because the base no long appears to have ", uid(), " on its structure: ", RawPointer(base), "\n"); } return false; } JSValue currentValue = base->getDirect(currentOffset); if (currentValue != requiredValue()) { if (verbose) { dataLog( "Invalid because the value is ", currentValue, " but we require ", requiredValue(), "\n"); } return false; } return true; } } RELEASE_ASSERT_NOT_REACHED(); return false; } bool PropertyCondition::validityRequiresImpurePropertyWatchpoint(Structure* structure) const { if (!*this) return false; switch (m_kind) { case Presence: case Absence: case Equivalence: return structure->needImpurePropertyWatchpoint(); default: return false; } } bool PropertyCondition::isStillValid(Structure* structure, JSObject* base) const { if (!isStillValidAssumingImpurePropertyWatchpoint(structure, base)) return false; // Currently we assume that an impure property can cause a property to appear, and can also // "shadow" an existing JS property on the same object. Hence it affects both presence and // absence. It doesn't affect AbsenceOfSetter because impure properties aren't ever setters. switch (m_kind) { case Absence: if (structure->typeInfo().getOwnPropertySlotIsImpure() || structure->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence()) return false; break; case Presence: case Equivalence: if (structure->typeInfo().getOwnPropertySlotIsImpure()) return false; break; default: break; } return true; } bool PropertyCondition::isWatchableWhenValid( Structure* structure, WatchabilityEffort effort) const { if (structure->transitionWatchpointSetHasBeenInvalidated()) return false; switch (m_kind) { case Equivalence: { PropertyOffset offset = structure->getConcurrently(uid()); // This method should only be called when some variant of isValid returned true, which // implies that we already confirmed that the structure knows of the property. We should // also have verified that the Structure is a cacheable dictionary, which means we // shouldn't have a TOCTOU race either. RELEASE_ASSERT(offset != invalidOffset); WatchpointSet* set = nullptr; switch (effort) { case MakeNoChanges: set = structure->propertyReplacementWatchpointSet(offset); break; case EnsureWatchability: set = structure->ensurePropertyReplacementWatchpointSet( *Heap::heap(structure)->vm(), offset); break; } if (!set || !set->isStillValid()) return false; break; } default: break; } return true; } bool PropertyCondition::isWatchableAssumingImpurePropertyWatchpoint( Structure* structure, JSObject* base, WatchabilityEffort effort) const { return isStillValidAssumingImpurePropertyWatchpoint(structure, base) && isWatchableWhenValid(structure, effort); } bool PropertyCondition::isWatchable( Structure* structure, JSObject* base, WatchabilityEffort effort) const { return isStillValid(structure, base) && isWatchableWhenValid(structure, effort); } bool PropertyCondition::isStillLive() const { if (hasPrototype() && prototype() && !Heap::isMarked(prototype())) return false; if (hasRequiredValue() && requiredValue() && requiredValue().isCell() && !Heap::isMarked(requiredValue().asCell())) return false; return true; } void PropertyCondition::validateReferences(const TrackedReferences& tracked) const { if (hasPrototype()) tracked.check(prototype()); if (hasRequiredValue()) tracked.check(requiredValue()); } bool PropertyCondition::isValidValueForAttributes(JSValue value, unsigned attributes) { bool attributesClaimAccessor = !!(attributes & Accessor); bool valueClaimsAccessor = !!jsDynamicCast(value); return attributesClaimAccessor == valueClaimsAccessor; } bool PropertyCondition::isValidValueForPresence(JSValue value) const { return isValidValueForAttributes(value, attributes()); } PropertyCondition PropertyCondition::attemptToMakeEquivalenceWithoutBarrier(JSObject* base) const { Structure* structure = base->structure(); if (!structure->isValidOffset(offset())) return PropertyCondition(); JSValue value = base->getDirect(offset()); if (!isValidValueForPresence(value)) return PropertyCondition(); return equivalenceWithoutBarrier(uid(), value); } } // namespace JSC namespace WTF { void printInternal(PrintStream& out, JSC::PropertyCondition::Kind condition) { switch (condition) { case JSC::PropertyCondition::Presence: out.print("Presence"); return; case JSC::PropertyCondition::Absence: out.print("Absence"); return; case JSC::PropertyCondition::AbsenceOfSetter: out.print("Absence"); return; case JSC::PropertyCondition::Equivalence: out.print("Equivalence"); return; } RELEASE_ASSERT_NOT_REACHED(); } } // namespace WTF