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