]>
Commit | Line | Data |
---|---|---|
ed1e77d3 A |
1 | /* |
2 | * Copyright (C) 2014 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 "HeapVerifier.h" | |
28 | ||
29 | #include "ButterflyInlines.h" | |
30 | #include "CopiedSpaceInlines.h" | |
31 | #include "HeapIterationScope.h" | |
32 | #include "JSCInlines.h" | |
33 | #include "JSObject.h" | |
34 | ||
35 | namespace JSC { | |
36 | ||
37 | LiveObjectData* LiveObjectList::findObject(JSObject* obj) | |
38 | { | |
39 | for (size_t i = 0; i < liveObjects.size(); i++) { | |
40 | LiveObjectData& data = liveObjects[i]; | |
41 | if (obj == data.obj) | |
42 | return &data; | |
43 | } | |
44 | return nullptr; | |
45 | } | |
46 | ||
47 | HeapVerifier::HeapVerifier(Heap* heap, unsigned numberOfGCCyclesToRecord) | |
48 | : m_heap(heap) | |
49 | , m_currentCycle(0) | |
50 | , m_numberOfCycles(numberOfGCCyclesToRecord) | |
51 | { | |
52 | RELEASE_ASSERT(m_numberOfCycles > 0); | |
53 | m_cycles = std::make_unique<GCCycle[]>(m_numberOfCycles); | |
54 | } | |
55 | ||
56 | const char* HeapVerifier::collectionTypeName(HeapOperation type) | |
57 | { | |
58 | switch (type) { | |
59 | case NoOperation: | |
60 | return "NoOperation"; | |
61 | case AnyCollection: | |
62 | return "AnyCollection"; | |
63 | case Allocation: | |
64 | return "Allocation"; | |
65 | case EdenCollection: | |
66 | return "EdenCollection"; | |
67 | case FullCollection: | |
68 | return "FullCollection"; | |
69 | } | |
70 | RELEASE_ASSERT_NOT_REACHED(); | |
71 | return nullptr; // Silencing a compiler warning. | |
72 | } | |
73 | ||
74 | const char* HeapVerifier::phaseName(HeapVerifier::Phase phase) | |
75 | { | |
76 | switch (phase) { | |
77 | case Phase::BeforeGC: | |
78 | return "BeforeGC"; | |
79 | case Phase::BeforeMarking: | |
80 | return "BeforeMarking"; | |
81 | case Phase::AfterMarking: | |
82 | return "AfterMarking"; | |
83 | case Phase::AfterGC: | |
84 | return "AfterGC"; | |
85 | } | |
86 | RELEASE_ASSERT_NOT_REACHED(); | |
87 | return nullptr; // Silencing a compiler warning. | |
88 | } | |
89 | ||
90 | static void getButterflyDetails(JSObject* obj, void*& butterflyBase, size_t& butterflyCapacityInBytes, CopiedBlock*& butterflyBlock) | |
91 | { | |
92 | Structure* structure = obj->structure(); | |
93 | Butterfly* butterfly = obj->butterfly(); | |
94 | butterflyBase = butterfly->base(structure); | |
95 | butterflyBlock = CopiedSpace::blockFor(butterflyBase); | |
96 | ||
97 | size_t propertyCapacity = structure->outOfLineCapacity(); | |
98 | size_t preCapacity; | |
99 | size_t indexingPayloadSizeInBytes; | |
100 | bool hasIndexingHeader = obj->hasIndexingHeader(); | |
101 | if (UNLIKELY(hasIndexingHeader)) { | |
102 | preCapacity = butterfly->indexingHeader()->preCapacity(structure); | |
103 | indexingPayloadSizeInBytes = butterfly->indexingHeader()->indexingPayloadSizeInBytes(structure); | |
104 | } else { | |
105 | preCapacity = 0; | |
106 | indexingPayloadSizeInBytes = 0; | |
107 | } | |
108 | butterflyCapacityInBytes = Butterfly::totalSize(preCapacity, propertyCapacity, hasIndexingHeader, indexingPayloadSizeInBytes); | |
109 | } | |
110 | ||
111 | void HeapVerifier::initializeGCCycle() | |
112 | { | |
113 | Heap* heap = m_heap; | |
114 | incrementCycle(); | |
115 | currentCycle().collectionType = heap->operationInProgress(); | |
116 | } | |
117 | ||
118 | struct GatherLiveObjFunctor : MarkedBlock::CountFunctor { | |
119 | GatherLiveObjFunctor(LiveObjectList& list) | |
120 | : m_list(list) | |
121 | { | |
122 | ASSERT(!list.liveObjects.size()); | |
123 | } | |
124 | ||
125 | inline void visit(JSCell* cell) | |
126 | { | |
127 | if (!cell->isObject()) | |
128 | return; | |
129 | LiveObjectData data(asObject(cell)); | |
130 | m_list.liveObjects.append(data); | |
131 | } | |
132 | ||
133 | IterationStatus operator()(JSCell* cell) | |
134 | { | |
135 | visit(cell); | |
136 | return IterationStatus::Continue; | |
137 | } | |
138 | ||
139 | LiveObjectList& m_list; | |
140 | }; | |
141 | ||
142 | void HeapVerifier::gatherLiveObjects(HeapVerifier::Phase phase) | |
143 | { | |
144 | Heap* heap = m_heap; | |
145 | LiveObjectList& list = *liveObjectListForGathering(phase); | |
146 | ||
147 | HeapIterationScope iterationScope(*heap); | |
148 | list.reset(); | |
149 | GatherLiveObjFunctor functor(list); | |
150 | heap->m_objectSpace.forEachLiveCell(iterationScope, functor); | |
151 | } | |
152 | ||
153 | LiveObjectList* HeapVerifier::liveObjectListForGathering(HeapVerifier::Phase phase) | |
154 | { | |
155 | switch (phase) { | |
156 | case Phase::BeforeMarking: | |
157 | return ¤tCycle().before; | |
158 | case Phase::AfterMarking: | |
159 | return ¤tCycle().after; | |
160 | case Phase::BeforeGC: | |
161 | case Phase::AfterGC: | |
162 | // We should not be gathering live objects during these phases. | |
163 | break; | |
164 | } | |
165 | RELEASE_ASSERT_NOT_REACHED(); | |
166 | return nullptr; // Silencing a compiler warning. | |
167 | } | |
168 | ||
169 | static void trimDeadObjectsFromList(HashSet<JSObject*>& knownLiveSet, LiveObjectList& list) | |
170 | { | |
171 | if (!list.hasLiveObjects) | |
172 | return; | |
173 | ||
174 | size_t liveObjectsFound = 0; | |
175 | for (size_t i = 0; i < list.liveObjects.size(); i++) { | |
176 | LiveObjectData& objData = list.liveObjects[i]; | |
177 | if (objData.isConfirmedDead) | |
178 | continue; // Don't "resurrect" known dead objects. | |
179 | if (!knownLiveSet.contains(objData.obj)) { | |
180 | objData.isConfirmedDead = true; | |
181 | continue; | |
182 | } | |
183 | liveObjectsFound++; | |
184 | } | |
185 | list.hasLiveObjects = !!liveObjectsFound; | |
186 | } | |
187 | ||
188 | void HeapVerifier::trimDeadObjects() | |
189 | { | |
190 | HashSet<JSObject*> knownLiveSet; | |
191 | ||
192 | LiveObjectList& after = currentCycle().after; | |
193 | for (size_t i = 0; i < after.liveObjects.size(); i++) { | |
194 | LiveObjectData& objData = after.liveObjects[i]; | |
195 | knownLiveSet.add(objData.obj); | |
196 | } | |
197 | ||
198 | trimDeadObjectsFromList(knownLiveSet, currentCycle().before); | |
199 | ||
200 | for (int i = -1; i > -m_numberOfCycles; i--) { | |
201 | trimDeadObjectsFromList(knownLiveSet, cycleForIndex(i).before); | |
202 | trimDeadObjectsFromList(knownLiveSet, cycleForIndex(i).after); | |
203 | } | |
204 | } | |
205 | ||
206 | bool HeapVerifier::verifyButterflyIsInStorageSpace(Phase phase, LiveObjectList& list) | |
207 | { | |
208 | auto& liveObjects = list.liveObjects; | |
209 | ||
210 | CopiedSpace& storageSpace = m_heap->m_storageSpace; | |
211 | bool listNamePrinted = false; | |
212 | bool success = true; | |
213 | for (size_t i = 0; i < liveObjects.size(); i++) { | |
214 | LiveObjectData& objectData = liveObjects[i]; | |
215 | if (objectData.isConfirmedDead) | |
216 | continue; | |
217 | ||
218 | JSObject* obj = objectData.obj; | |
219 | Butterfly* butterfly = obj->butterfly(); | |
220 | if (butterfly) { | |
221 | void* butterflyBase; | |
222 | size_t butterflyCapacityInBytes; | |
223 | CopiedBlock* butterflyBlock; | |
224 | getButterflyDetails(obj, butterflyBase, butterflyCapacityInBytes, butterflyBlock); | |
225 | ||
226 | if (!storageSpace.contains(butterflyBlock)) { | |
227 | if (!listNamePrinted) { | |
228 | dataLogF("Verification @ phase %s FAILED in object list '%s' (size %zu)\n", | |
229 | phaseName(phase), list.name, liveObjects.size()); | |
230 | listNamePrinted = true; | |
231 | } | |
232 | ||
233 | Structure* structure = obj->structure(); | |
234 | const char* structureClassName = structure->classInfo()->className; | |
235 | dataLogF(" butterfly %p (base %p size %zu block %p) NOT in StorageSpace | obj %p type '%s'\n", | |
236 | butterfly, butterflyBase, butterflyCapacityInBytes, butterflyBlock, obj, structureClassName); | |
237 | success = false; | |
238 | } | |
239 | } | |
240 | } | |
241 | return success; | |
242 | } | |
243 | ||
244 | void HeapVerifier::verify(HeapVerifier::Phase phase) | |
245 | { | |
246 | bool beforeVerified = verifyButterflyIsInStorageSpace(phase, currentCycle().before); | |
247 | bool afterVerified = verifyButterflyIsInStorageSpace(phase, currentCycle().after); | |
248 | RELEASE_ASSERT(beforeVerified && afterVerified); | |
249 | } | |
250 | ||
251 | void HeapVerifier::reportObject(LiveObjectData& objData, int cycleIndex, HeapVerifier::GCCycle& cycle, LiveObjectList& list) | |
252 | { | |
253 | JSObject* obj = objData.obj; | |
254 | ||
255 | if (objData.isConfirmedDead) { | |
256 | dataLogF("FOUND dead obj %p in GC[%d] %s list '%s'\n", | |
257 | obj, cycleIndex, cycle.collectionTypeName(), list.name); | |
258 | return; | |
259 | } | |
260 | ||
261 | Structure* structure = obj->structure(); | |
262 | Butterfly* butterfly = obj->butterfly(); | |
263 | void* butterflyBase; | |
264 | size_t butterflyCapacityInBytes; | |
265 | CopiedBlock* butterflyBlock; | |
266 | getButterflyDetails(obj, butterflyBase, butterflyCapacityInBytes, butterflyBlock); | |
267 | ||
268 | dataLogF("FOUND obj %p type '%s' butterfly %p (base %p size %zu block %p) in GC[%d] %s list '%s'\n", | |
269 | obj, structure->classInfo()->className, | |
270 | butterfly, butterflyBase, butterflyCapacityInBytes, butterflyBlock, | |
271 | cycleIndex, cycle.collectionTypeName(), list.name); | |
272 | } | |
273 | ||
274 | void HeapVerifier::checkIfRecorded(JSObject* obj) | |
275 | { | |
276 | bool found = false; | |
277 | ||
278 | for (int cycleIndex = 0; cycleIndex > -m_numberOfCycles; cycleIndex--) { | |
279 | GCCycle& cycle = cycleForIndex(cycleIndex); | |
280 | LiveObjectList& beforeList = cycle.before; | |
281 | LiveObjectList& afterList = cycle.after; | |
282 | ||
283 | LiveObjectData* objData; | |
284 | objData = beforeList.findObject(obj); | |
285 | if (objData) { | |
286 | reportObject(*objData, cycleIndex, cycle, beforeList); | |
287 | found = true; | |
288 | } | |
289 | objData = afterList.findObject(obj); | |
290 | if (objData) { | |
291 | reportObject(*objData, cycleIndex, cycle, afterList); | |
292 | found = true; | |
293 | } | |
294 | } | |
295 | ||
296 | if (!found) | |
297 | dataLogF("obj %p NOT FOUND\n", obj); | |
298 | } | |
299 | ||
300 | } // namespace JSC |