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
33namespace JSC {
34
35namespace PropertyConditionInternal {
36static bool verbose = false;
37}
38
39void 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 CustomFunctionEquivalence:
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
67void PropertyCondition::dump(PrintStream& out) const
68{
69 dumpInContext(out, nullptr);
70}
71
72bool 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 CustomFunctionEquivalence:
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 CustomFunctionEquivalence: {
257 if (structure->staticPropertiesReified())
258 return false;
259 return !!structure->findPropertyHashEntry(uid());
260 }
261 }
262
263 RELEASE_ASSERT_NOT_REACHED();
264 return false;
265}
266
267bool PropertyCondition::validityRequiresImpurePropertyWatchpoint(Structure* structure) const
268{
269 if (!*this)
270 return false;
271
272 switch (m_header.type()) {
273 case Presence:
274 case Absence:
275 case Equivalence:
276 case CustomFunctionEquivalence:
277 return structure->needImpurePropertyWatchpoint();
278 case AbsenceOfSetEffect:
279 case HasPrototype:
280 return false;
281 }
282
283 RELEASE_ASSERT_NOT_REACHED();
284 return false;
285}
286
287bool PropertyCondition::isStillValid(Structure* structure, JSObject* base) const
288{
289 if (!isStillValidAssumingImpurePropertyWatchpoint(structure, base))
290 return false;
291
292 // Currently we assume that an impure property can cause a property to appear, and can also
293 // "shadow" an existing JS property on the same object. Hence it affects both presence and
294 // absence. It doesn't affect AbsenceOfSetEffect because impure properties aren't ever setters.
295 switch (m_header.type()) {
296 case Absence:
297 if (structure->typeInfo().getOwnPropertySlotIsImpure() || structure->typeInfo().getOwnPropertySlotIsImpureForPropertyAbsence())
298 return false;
299 break;
300 case Presence:
301 case Equivalence:
302 case CustomFunctionEquivalence:
303 if (structure->typeInfo().getOwnPropertySlotIsImpure())
304 return false;
305 break;
306 default:
307 break;
308 }
309
310 return true;
311}
312
313bool PropertyCondition::isWatchableWhenValid(
314 Structure* structure, WatchabilityEffort effort) const
315{
316 if (structure->transitionWatchpointSetHasBeenInvalidated())
317 return false;
318
319 switch (m_header.type()) {
320 case Equivalence: {
321 PropertyOffset offset = structure->getConcurrently(uid());
322
323 // This method should only be called when some variant of isValid returned true, which
324 // implies that we already confirmed that the structure knows of the property. We should
325 // also have verified that the Structure is a cacheable dictionary, which means we
326 // shouldn't have a TOCTOU race either.
327 RELEASE_ASSERT(offset != invalidOffset);
328
329 WatchpointSet* set = nullptr;
330 switch (effort) {
331 case MakeNoChanges:
332 set = structure->propertyReplacementWatchpointSet(offset);
333 break;
334 case EnsureWatchability:
335 set = structure->ensurePropertyReplacementWatchpointSet(structure->vm(), offset);
336 break;
337 }
338
339 if (!set || !set->isStillValid())
340 return false;
341
342 break;
343 }
344
345 case CustomFunctionEquivalence: {
346 // We just use the structure transition watchpoint for this. A structure S starts
347 // off with a property P in the static property hash table. If S transitions to
348 // S', either P remains in the static property table or not. If not, then we
349 // are no longer valid. So the above check of transitionWatchpointSetHasBeenInvalidated
350 // is sufficient.
351 //
352 // We could make this smarter in the future, since we sometimes reify static properties.
353 // We could make this adapt to looking at the object's storage for such reified custom
354 // functions, but we don't do that right now. We just allow this property condition to
355 // invalidate and create an Equivalence watchpoint for the materialized property sometime
356 // in the future.
357 break;
358 }
359
360 default:
361 break;
362 }
363
364 return true;
365}
366
367bool PropertyCondition::isWatchableAssumingImpurePropertyWatchpoint(
368 Structure* structure, JSObject* base, WatchabilityEffort effort) const
369{
370 return isStillValidAssumingImpurePropertyWatchpoint(structure, base)
371 && isWatchableWhenValid(structure, effort);
372}
373
374bool PropertyCondition::isWatchable(
375 Structure* structure, JSObject* base, WatchabilityEffort effort) const
376{
377 return isStillValid(structure, base)
378 && isWatchableWhenValid(structure, effort);
379}
380
381void PropertyCondition::validateReferences(const TrackedReferences& tracked) const
382{
383 if (hasPrototype())
384 tracked.check(prototype());
385
386 if (hasRequiredValue())
387 tracked.check(requiredValue());
388}
389
390bool PropertyCondition::isValidValueForAttributes(VM& vm, JSValue value, unsigned attributes)
391{
392 if (!value)
393 return false;
394 bool attributesClaimAccessor = !!(attributes & PropertyAttribute::Accessor);
395 bool valueClaimsAccessor = !!jsDynamicCast<GetterSetter*>(vm, value);
396 return attributesClaimAccessor == valueClaimsAccessor;
397}
398
399bool PropertyCondition::isValidValueForPresence(VM& vm, JSValue value) const
400{
401 return isValidValueForAttributes(vm, value, attributes());
402}
403
404PropertyCondition PropertyCondition::attemptToMakeEquivalenceWithoutBarrier(VM& vm, JSObject* base) const
405{
406 Structure* structure = base->structure(vm);
407
408 JSValue value = base->getDirectConcurrently(structure, offset());
409 if (!isValidValueForPresence(vm, value))
410 return PropertyCondition();
411 return equivalenceWithoutBarrier(uid(), value);
412}
413
414} // namespace JSC
415
416namespace WTF {
417
418void printInternal(PrintStream& out, JSC::PropertyCondition::Kind condition)
419{
420 switch (condition) {
421 case JSC::PropertyCondition::Presence:
422 out.print("Presence");
423 return;
424 case JSC::PropertyCondition::Absence:
425 out.print("Absence");
426 return;
427 case JSC::PropertyCondition::AbsenceOfSetEffect:
428 out.print("Absence");
429 return;
430 case JSC::PropertyCondition::Equivalence:
431 out.print("Equivalence");
432 return;
433 case JSC::PropertyCondition::CustomFunctionEquivalence:
434 out.print("CustomFunctionEquivalence");
435 return;
436 case JSC::PropertyCondition::HasPrototype:
437 out.print("HasPrototype");
438 return;
439 }
440 RELEASE_ASSERT_NOT_REACHED();
441}
442
443} // namespace WTF
444