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