comparison src/main/gov/nasa/jpf/listener/DeadlockAnalyzer.java @ 0:61d41facf527

initial v8 import (history reset)
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Fri, 23 Jan 2015 10:14:01 -0800
parents
children
comparison
equal deleted inserted replaced
-1:000000000000 0:61d41facf527
1 /*
2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
4 * All rights reserved.
5 *
6 * The Java Pathfinder core (jpf-core) platform is licensed under the
7 * Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0.
11 *
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
18 package gov.nasa.jpf.listener;
19
20 import gov.nasa.jpf.Config;
21 import gov.nasa.jpf.JPF;
22 import gov.nasa.jpf.ListenerAdapter;
23 import gov.nasa.jpf.jvm.bytecode.EXECUTENATIVE;
24 import gov.nasa.jpf.report.ConsolePublisher;
25 import gov.nasa.jpf.report.Publisher;
26 import gov.nasa.jpf.search.Search;
27 import gov.nasa.jpf.vm.ElementInfo;
28 import gov.nasa.jpf.vm.Instruction;
29 import gov.nasa.jpf.vm.VM;
30 import gov.nasa.jpf.vm.StackFrame;
31 import gov.nasa.jpf.vm.ThreadInfo;
32
33 import java.io.PrintWriter;
34 import java.util.ArrayList;
35 import java.util.Collection;
36 import java.util.HashMap;
37 import java.util.HashSet;
38 import java.util.LinkedHashSet;
39 import java.util.ListIterator;
40 import java.util.Stack;
41
42 /**
43 * example of a listener that creates property specific traces. The interesting
44 * thing is that it does so without the need to store steps, i.e. it maintains
45 * it's own transition stack.
46 * this is still work in progress, analyzing the trace can be much more
47 * elaborate (we just dump up to a max history size for now)
48 *
49 * <2do> DeadlockAnalyzer output can be confusing if a reorganizing
50 * ThreadList is used (which reassigns thread ids)
51 */
52 public class DeadlockAnalyzer extends ListenerAdapter {
53
54 enum OpType { block, lock, unlock, wait, notify, notifyAll, started, terminated };
55 static String[] opTypeMnemonic = { "B", "L", "U", "W", "N", "A", "S", "T" };
56
57 static class ThreadOp {
58 ThreadInfo ti;
59 ElementInfo ei;
60 Instruction insn;
61
62 // kind of redundant, but there might be context attributes in addition
63 // to the insn itself
64 OpType opType;
65
66 // we could identify this with the insn, but only in case this is
67 // a transition boundary, which is far less general than we can be
68 int stateId;
69 ThreadOp prevTransition;
70 ThreadOp prevOp;
71
72 ThreadOp (ThreadInfo ti, ElementInfo ei, OpType type) {
73 this.ti = ti;
74 this.ei = ei;
75 insn = getReportInsn(ti); // we haven't had the executeInsn notification yet
76 opType = type;
77
78 prevOp = null;
79 }
80
81 Instruction getReportInsn(ThreadInfo ti){
82 StackFrame frame = ti.getTopFrame();
83 if (frame != null) {
84 Instruction insn = frame.getPC();
85 if (insn instanceof EXECUTENATIVE) {
86 frame = frame.getPrevious();
87 if (frame != null) {
88 insn = frame.getPC();
89 }
90 }
91
92 return insn;
93 } else {
94 return null;
95 }
96 }
97
98 void printLocOn (PrintWriter pw) {
99 pw.print(String.format("%6d", new Integer(stateId)));
100
101 if (insn != null) {
102 pw.print(String.format(" %18.18s ", insn.getMnemonic()));
103 pw.print(insn.getFileLocation());
104 String line = insn.getSourceLine();
105 if (line != null){
106 pw.print( " : ");
107 pw.print(line.trim());
108 //pw.print(insn);
109 }
110 }
111 }
112
113 void printOn (PrintWriter pw){
114 pw.print( stateId);
115 pw.print( " : ");
116 pw.print( ti.getName());
117 pw.print( " : ");
118 pw.print( opType.name());
119 pw.print( " : ");
120 pw.println(ei);
121 }
122
123 @Override
124 public String toString() {
125 StringBuilder sb = new StringBuilder();
126 sb.append( stateId);
127 sb.append( " : ");
128 sb.append( ti.getName());
129 sb.append( " : ");
130 sb.append( opType.name());
131 sb.append( " : ");
132 sb.append(ei);
133 return sb.toString();
134 }
135
136 void printColumnOn(PrintWriter pw, Collection<ThreadInfo> tlist){
137 for (ThreadInfo t : tlist) {
138 if (ti == t) {
139 if (opType == OpType.started || opType == OpType.terminated) {
140 pw.print(String.format(" %1$s ", opTypeMnemonic[opType.ordinal()]));
141 } else {
142 pw.print(String.format("%1$s:%2$-5x ", opTypeMnemonic[opType.ordinal()], ei.getObjectRef()));
143 }
144 //break;
145 } else {
146 pw.print(" | ");
147 }
148 }
149 }
150 }
151
152 ThreadOp lastOp;
153 ThreadOp lastTransition;
154
155 int maxHistory;
156 String format;
157
158 VM vm;
159 Search search;
160
161 public DeadlockAnalyzer (Config config, JPF jpf){
162 jpf.addPublisherExtension(ConsolePublisher.class, this);
163
164 maxHistory = config.getInt("deadlock.max_history", Integer.MAX_VALUE);
165 format = config.getString("deadlock.format", "essential");
166
167 vm = jpf.getVM();
168 search = jpf.getSearch();
169 }
170
171 boolean requireAllOps() {
172 return (format.equalsIgnoreCase("essential"));
173 }
174
175 void addOp (ThreadInfo ti, ElementInfo ei, OpType opType){
176 ThreadOp op = new ThreadOp(ti, ei, opType);
177 if (lastOp == null){
178 lastOp = op;
179 } else {
180 assert lastOp.stateId == 0;
181
182 op.prevOp = lastOp;
183 lastOp = op;
184 }
185 }
186
187 void printRawOps (PrintWriter pw) {
188 int i=0;
189
190 for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){
191 for (ThreadOp op = tOp; op != null; op=op.prevOp) {
192 if (i++ >= maxHistory){
193 pw.println("...");
194 return;
195 }
196 op.printOn(pw);
197 }
198 }
199 }
200
201 /**
202 * include all threads that are currently blocked or waiting, and
203 * all the threads that had the last interaction with them. Note that
204 * we do this completely on the basis of the recorded ThreadOps, i.e.
205 * don't rely on when this is called
206 */
207 void printEssentialOps(PrintWriter pw) {
208 LinkedHashSet<ThreadInfo> threads = new LinkedHashSet<ThreadInfo>();
209 ArrayList<ThreadOp> ops = new ArrayList<ThreadOp>();
210 HashMap<ElementInfo,ThreadInfo> waits = new HashMap<ElementInfo,ThreadInfo>();
211 HashMap<ElementInfo,ThreadInfo> blocks = new HashMap<ElementInfo,ThreadInfo>();
212 HashSet<ThreadInfo> runnables = new HashSet<ThreadInfo>();
213
214 // collect all relevant threads and ops
215 for (ThreadOp trans = lastTransition; trans != null; trans = trans.prevTransition){
216 for (ThreadOp tOp = trans; tOp != null; tOp = tOp.prevOp) {
217 OpType ot = tOp.opType;
218 ThreadInfo oti = tOp.ti;
219
220 if (ot == OpType.wait || ot == OpType.block) {
221 if (!runnables.contains(oti) && !threads.contains(oti)){
222 HashMap<ElementInfo, ThreadInfo> map = (ot == OpType.block) ? blocks : waits;
223 threads.add(oti);
224 map.put(tOp.ei, oti);
225 ops.add(tOp);
226 }
227
228 } else if (ot == OpType.notify || ot == OpType.notifyAll || ot == OpType.lock) {
229 HashMap<ElementInfo, ThreadInfo> map = (ot == OpType.lock) ? blocks : waits;
230 ThreadInfo ti = map.get(tOp.ei);
231
232 if (ti != null && ti != oti){
233 if (!threads.contains(oti)){
234 threads.add(oti);
235 }
236 map.remove(tOp.ei);
237 ops.add(tOp);
238 }
239
240 runnables.add(oti);
241
242 } else if (ot == OpType.unlock) {
243 // not relevant
244 runnables.add(oti);
245
246 } else if (ot == OpType.terminated || ot == OpType.started) {
247 ops.add(tOp); // might be removed later-on
248 }
249 }
250 }
251
252 // remove all starts/terminates of irrelevant threads
253 for (ListIterator<ThreadOp> it = ops.listIterator(); it.hasNext(); ){
254 ThreadOp tOp = it.next();
255 if (tOp.opType == OpType.terminated || tOp.opType == OpType.started) {
256 if (!threads.contains(tOp.ti)){
257 it.remove();
258 }
259 }
260 }
261
262 // now we are ready to print
263 printHeader(pw,threads);
264
265 for (ThreadOp tOp : ops) {
266 tOp.printColumnOn(pw,threads);
267 tOp.printLocOn(pw);
268 pw.println();
269 }
270 }
271
272
273 Collection<ThreadInfo> getThreadList() {
274 ArrayList<ThreadInfo> tcol = new ArrayList<ThreadInfo>();
275 boolean allOps = requireAllOps();
276 int i=0;
277
278 prevTrans:
279 for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){
280 i++;
281 if (!allOps && (i >= maxHistory)){
282 break;
283 }
284
285 for (ThreadInfo ti : tcol) {
286 if (ti == tOp.ti) continue prevTrans;
287 }
288 tcol.add(tOp.ti);
289 }
290
291 return tcol;
292 }
293
294 void printHeader (PrintWriter pw, Collection<ThreadInfo> tlist){
295 for (ThreadInfo ti : tlist){
296 pw.print(String.format(" %1$2d ", ti.getId()));
297 }
298 pw.print(" trans insn loc : stmt");
299 pw.println();
300
301 for (int i=0; i<tlist.size(); i++){
302 pw.print("------- ");
303 }
304 pw.print("---------------------------------------------------");
305 pw.println();
306 }
307
308
309 void printColumnOps (PrintWriter pw){
310 int i = 0;
311 Collection<ThreadInfo> tlist = getThreadList();
312 printHeader(pw,tlist);
313
314 // and now the data
315 for (ThreadOp tOp = lastTransition; tOp != null; tOp = tOp.prevTransition){
316 for (ThreadOp op = tOp; op != null; op=op.prevOp) {
317 if (i++ >= maxHistory){
318 pw.println("...");
319 return;
320 }
321
322 op.printColumnOn(pw,tlist);
323 op.printLocOn(pw);
324 pw.println();
325 }
326 }
327 }
328
329 /**
330 * this is the workhorse - filter which ops should be shown, and which
331 * are irrelevant for the deadlock
332 */
333 boolean showOp (ThreadOp op, ThreadInfo[] tlist,
334 boolean[] waitSeen, boolean[] notifySeen,
335 boolean[] blockSeen, boolean[] lockSeen,
336 Stack<ElementInfo>[] unlocked) {
337 ThreadInfo ti = op.ti;
338 ElementInfo ei = op.ei;
339 int idx;
340 for (idx=0; idx < tlist.length; idx++){
341 if (tlist[idx] == ti) break;
342 }
343
344 // we could delegate this to the enum type, but let's not be too fancy
345 switch (op.opType) {
346 case block:
347 // only report the last one if thread is blocked
348 if (ti.isBlocked()) {
349 if (!blockSeen[idx]) {
350 blockSeen[idx] = true;
351 return true;
352 }
353 }
354 return false;
355
356 case unlock:
357 unlocked[idx].push(ei);
358 return false;
359
360 case lock:
361 // if we had a corresponding unlock, ignore
362 if (!unlocked[idx].isEmpty() && (unlocked[idx].peek() == ei)) {
363 unlocked[idx].pop();
364 return false;
365 }
366
367 // only report the last one if there is a thread that's currently blocked on it
368 for (int i=0; i<tlist.length; i++){
369 if ((i != idx) && tlist[i].isBlocked() && (tlist[i].getLockObject() == ei)) {
370 if (!lockSeen[i]){
371 lockSeen[i] = true;
372 return true;
373 }
374 }
375 }
376
377 return false;
378
379 case wait:
380 if (ti.isWaiting()){ // only show the last one if this is a waiting thread
381 if (!waitSeen[idx]) {
382 waitSeen[idx] = true;
383 return true;
384 }
385 }
386
387 return false;
388
389 case notify:
390 case notifyAll:
391 // only report the last one if there's a thread waiting on it
392 for (int i=0; i<tlist.length; i++){
393 if ((i != idx) && tlist[i].isWaiting() && (tlist[i].getLockObject() == ei)) {
394 if (!notifySeen[i]) {
395 notifySeen[i] = true;
396 return true;
397 }
398 }
399 }
400
401 return false;
402
403 case started:
404 case terminated:
405 return true;
406 }
407
408 return false;
409 }
410
411 void storeLastTransition(){
412 if (lastOp != null) {
413 int stateId = search.getStateId();
414 ThreadInfo ti = lastOp.ti;
415
416 for (ThreadOp op = lastOp; op != null; op = op.prevOp) {
417 assert op.stateId == 0;
418
419 op.stateId = stateId;
420 }
421
422 lastOp.prevTransition = lastTransition;
423 lastTransition = lastOp;
424
425 lastOp = null;
426 }
427 }
428
429 //--- VM listener interface
430
431 @Override
432 public void objectLocked (VM vm, ThreadInfo ti, ElementInfo ei) {
433 addOp(ti, ei, OpType.lock);
434 }
435
436 @Override
437 public void objectUnlocked (VM vm, ThreadInfo ti, ElementInfo ei) {
438 addOp(ti, ei, OpType.unlock);
439 }
440
441 @Override
442 public void objectWait (VM vm, ThreadInfo ti, ElementInfo ei) {
443 addOp(ti, ei, OpType.wait);
444 }
445
446 @Override
447 public void objectNotify (VM vm, ThreadInfo ti, ElementInfo ei) {
448 addOp(ti, ei, OpType.notify);
449 }
450
451 @Override
452 public void objectNotifyAll (VM vm, ThreadInfo ti, ElementInfo ei) {
453 addOp(ti, ei, OpType.notifyAll);
454 }
455
456 @Override
457 public void threadBlocked (VM vm, ThreadInfo ti, ElementInfo ei){
458 addOp(ti, ei, OpType.block);
459 }
460
461 @Override
462 public void threadStarted (VM vm, ThreadInfo ti){
463 addOp(ti, null, OpType.started);
464 }
465
466 @Override
467 public void threadTerminated (VM vm, ThreadInfo ti){
468 addOp(ti, null, OpType.terminated);
469 }
470
471 //--- SearchListener interface
472
473 @Override
474 public void stateAdvanced (Search search){
475 if (search.isNewState()) {
476 storeLastTransition();
477 }
478 }
479
480 @Override
481 public void stateBacktracked (Search search){
482 int stateId = search.getStateId();
483 while ((lastTransition != null) && (lastTransition.stateId > stateId)){
484 lastTransition = lastTransition.prevTransition;
485 }
486 lastOp = null;
487 }
488
489 // for HeuristicSearches. Ok, that's braindead but at least no need for cloning
490 HashMap<Integer,ThreadOp> storedTransition = new HashMap<Integer,ThreadOp>();
491
492 @Override
493 public void stateStored (Search search) {
494 // always called after stateAdvanced
495 storedTransition.put(search.getStateId(), lastTransition);
496 }
497
498 @Override
499 public void stateRestored (Search search) {
500 int stateId = search.getStateId();
501 ThreadOp op = storedTransition.get(stateId);
502 if (op != null) {
503 lastTransition = op;
504 storedTransition.remove(stateId); // not strictly required, but we don't come back
505 }
506 }
507
508 @Override
509 public void publishPropertyViolation (Publisher publisher) {
510 PrintWriter pw = publisher.getOut();
511 publisher.publishTopicStart("thread ops " + publisher.getLastErrorId());
512
513 if ("column".equalsIgnoreCase(format)){
514 printColumnOps(pw);
515 } else if ("essential".equalsIgnoreCase(format)) {
516 printEssentialOps(pw);
517 } else {
518 printRawOps(pw);
519 }
520 }
521 }