comparison src/main/gov/nasa/jpf/listener/CoverageAnalyzer.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.GOTO;
24 import gov.nasa.jpf.jvm.bytecode.IfInstruction;
25 import gov.nasa.jpf.jvm.bytecode.JVMInvokeInstruction;
26 import gov.nasa.jpf.jvm.bytecode.JVMReturnInstruction;
27 import gov.nasa.jpf.report.ConsolePublisher;
28 import gov.nasa.jpf.report.Publisher;
29 import gov.nasa.jpf.report.PublisherExtension;
30 import gov.nasa.jpf.util.Misc;
31 import gov.nasa.jpf.util.StringSetMatcher;
32 import gov.nasa.jpf.vm.AnnotationInfo;
33 import gov.nasa.jpf.vm.ChoiceGenerator;
34 import gov.nasa.jpf.vm.ClassInfo;
35 import gov.nasa.jpf.vm.ClassInfoException;
36 import gov.nasa.jpf.vm.ClassLoaderInfo;
37 import gov.nasa.jpf.vm.ExceptionHandler;
38 import gov.nasa.jpf.vm.Instruction;
39 import gov.nasa.jpf.vm.VM;
40 import gov.nasa.jpf.vm.MethodInfo;
41 import gov.nasa.jpf.vm.ThreadInfo;
42
43 import java.io.File;
44 import java.io.IOException;
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.BitSet;
48 import java.util.Comparator;
49 import java.util.Enumeration;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.List;
53 import java.util.Map;
54 import java.util.jar.JarEntry;
55 import java.util.jar.JarFile;
56 import java.util.logging.Logger;
57
58 /**
59 * a listener to report coverage statistics
60 *
61 * The idea is to collect per-class/-method/-thread information about
62 * executed instructions, and then analyze this deeper when it comes to
63 * report time (e.g. branch coverage, basic blocks, ..)
64 *
65 * Keep in mind that this is potentially a concurrent, model checked program,
66 * i.e. there is more to coverage than what hits the eye of a static analyzer
67 * (exceptions, data and thread CGs)
68 */
69 public class CoverageAnalyzer extends ListenerAdapter implements PublisherExtension {
70
71 static Logger log = JPF.getLogger("gov.nasa.jpf.listener.CoverageAnalyzer");
72
73 static class Coverage {
74
75 int total;
76 int covered;
77
78 Coverage(int total, int covered) {
79 this.total = total;
80 this.covered = covered;
81 }
82
83 public void add(Coverage cov) {
84 total += cov.total;
85 covered += cov.covered;
86 }
87
88 public int percent() {
89 if (total > 0) {
90 return covered * 100 / total;
91 }
92
93 return (Integer.MIN_VALUE);
94 }
95
96 public int covered() {
97 return covered;
98 }
99
100 public int total() {
101 return total;
102 }
103
104 public boolean isPartiallyCovered() {
105 return ((covered > 0) && (covered < total));
106 }
107
108 public boolean isNotCovered() {
109 return (covered == 0);
110 }
111
112 public boolean isFullyCovered() {
113 return (covered == total);
114 }
115 }
116
117 static class MethodCoverage {
118
119 MethodInfo mi;
120
121 // we base everything else on bytecode instruction coverage
122 BitSet[] covered;
123 BitSet basicBlocks; // set on demand
124 BitSet handlers; // set on demand
125 BitSet branches; // set on demand
126 BitSet branchTrue;
127 BitSet branchFalse;
128
129 MethodCoverage(MethodInfo mi) {
130 this.mi = mi;
131 log.info("add method: " + mi.getUniqueName());
132 }
133
134 MethodInfo getMethodInfo() {
135 return mi;
136 }
137
138 void setExecuted(ThreadInfo ti, Instruction insn) {
139 int idx = ti.getId();
140
141 if (covered == null) {
142 covered = new BitSet[idx + 1];
143 } else if (idx >= covered.length) {
144 BitSet[] a = new BitSet[idx + 1];
145 System.arraycopy(covered, 0, a, 0, covered.length);
146 covered = a;
147 }
148
149 if (covered[idx] == null) {
150 covered[idx] = new BitSet(mi.getInstructions().length);
151 }
152
153 int off = insn.getInstructionIndex();
154 covered[idx].set(off);
155
156 if (showBranchCoverage && (insn instanceof IfInstruction)) {
157 if (branchTrue == null) {
158 branchTrue = new BitSet(mi.getInstructions().length);
159 branchFalse = new BitSet(branchTrue.size());
160 }
161 if (!((IfInstruction) insn).getConditionValue()) {
162 branchTrue.set(off);
163 } else {
164 branchFalse.set(off);
165 }
166 }
167 }
168
169 void setCGed(ThreadInfo ti, Instruction insn) {
170 ti = null; // Remove IDE warning about unused variable.
171 // Hmm, we have to store the bb set at this point
172 BitSet bb = getBasicBlocks();
173 Instruction next = insn.getNext();
174 if (next != null) { // insn might be a sync return
175 bb.set(next.getInstructionIndex());
176 }
177 }
178
179 BitSet getExecutedInsn() {
180 int nTotal = mi.getInstructions().length;
181 BitSet bUnion = new BitSet(nTotal);
182
183 if (covered != null) {
184 for (BitSet b : covered) {
185 if (b != null) {
186 bUnion.or(b);
187 }
188 }
189 }
190
191 return bUnion;
192 }
193
194 Coverage getCoveredInsn() {
195 int nTotal = mi.getInstructions().length;
196
197 if (excludeHandlers) {
198 nTotal -= getHandlers().cardinality();
199 }
200
201 if (covered != null) {
202 BitSet bExec = getExecutedInsn();
203 if (excludeHandlers) {
204 bExec.andNot(getHandlers());
205 }
206 return new Coverage(nTotal, bExec.cardinality());
207 } else {
208 return new Coverage(nTotal, 0);
209 }
210 }
211
212 Coverage getCoveredLines() {
213 BitSet executable = new BitSet();
214 BitSet covered = new BitSet();
215
216 getCoveredLines(executable, covered);
217
218 return new Coverage(executable.cardinality(), covered.cardinality());
219 }
220
221 boolean getCoveredLines(BitSet executable, BitSet covered) {
222 Instruction inst[] = mi.getInstructions();
223 BitSet insn;
224 int i, line;
225
226 if (covered == null) {
227 return false;
228 }
229
230 insn = getExecutedInsn();
231 if (excludeHandlers) {
232 insn.andNot(getHandlers());
233 }
234
235 if (branchTrue != null) {
236 for (i = branchTrue.length() - 1; i >= 0; i--) {
237 boolean cTrue = branchTrue.get(i);
238 boolean cFalse = branchFalse.get(i);
239
240 if ((!cTrue || !cFalse) && (inst[i] instanceof IfInstruction)) {
241 insn.clear(i);
242 }
243 }
244 }
245
246 for (i = inst.length - 1; i >= 0; i--) {
247 line = inst[i].getLineNumber();
248
249 if (line > 0) {
250 executable.set(line);
251 covered.set(line);
252 }
253 }
254
255 for (i = inst.length - 1; i >= 0; i--) {
256 line = inst[i].getLineNumber();
257 if ((!insn.get(i)) && (line > 0)) { // TODO What do we do with instructions that don't have a line number. Is this even possible?
258 covered.clear(line);
259 }
260 }
261
262 return true;
263 }
264
265 Coverage getCoveredBranches() {
266 BitSet b = getBranches();
267 int nTotal = b.cardinality();
268 int nCovered = 0;
269
270 if (branchTrue != null) {
271 int n = branchTrue.size();
272
273 for (int i = 0; i < n; i++) {
274 boolean cTrue = branchTrue.get(i);
275 boolean cFalse = branchFalse.get(i);
276
277 if (cTrue && cFalse) {
278 nCovered++;
279 }
280 }
281 }
282
283 return new Coverage(nTotal, nCovered);
284 }
285
286 BitSet getHandlerStarts() {
287 BitSet b = new BitSet(mi.getInstructions().length);
288 ExceptionHandler[] handler = mi.getExceptions();
289
290 if (handler != null) {
291 for (int i = 0; i < handler.length; i++) {
292 Instruction hs = mi.getInstructionAt(handler[i].getHandler());
293 b.set(hs.getInstructionIndex());
294 }
295 }
296
297 return b;
298 }
299
300 BitSet getHandlers() {
301 // this algorithm is a bit subtle - we walk through the code until
302 // we hit a forward GOTO (or RETURN). If the insn following the goto is the
303 // beginning of a handler, we mark everything up to the jump address
304 // as a handler block
305
306 if (handlers == null) {
307 BitSet hs = getHandlerStarts();
308 Instruction[] code = mi.getInstructions();
309 BitSet b = new BitSet(code.length);
310
311 if (!hs.isEmpty()) {
312 for (int i = 0; i < code.length; i++) {
313 Instruction insn = code[i];
314 if (insn instanceof GOTO) {
315 GOTO gotoInsn = (GOTO) insn;
316 if (!gotoInsn.isBackJump() && hs.get(i + 1)) { // jump around handler
317 int handlerEnd = gotoInsn.getTarget().getInstructionIndex();
318 for (i++; i < handlerEnd; i++) {
319 b.set(i);
320 }
321 }
322 } else if (insn instanceof JVMReturnInstruction) { // everything else is handler
323 for (i++; i < code.length; i++) {
324 b.set(i);
325 }
326 }
327 }
328 }
329
330 handlers = b;
331 }
332
333 return handlers;
334 }
335
336 // that's kind of redundant with basic blocks, but not really - here
337 // we are interested in the logic behind branches, i.e. we want to know
338 // what the condition values were. We are not interested in GOTOs and exceptions
339 BitSet getBranches() {
340 if (branches == null) {
341 Instruction[] code = mi.getInstructions();
342 BitSet br = new BitSet(code.length);
343
344 for (int i = 0; i < code.length; i++) {
345 Instruction insn = code[i];
346 if (insn instanceof IfInstruction) {
347 br.set(i);
348 }
349 }
350
351 branches = br;
352 }
353
354 return branches;
355 }
356
357 BitSet getBasicBlocks() {
358 if (basicBlocks == null) {
359 Instruction[] code = mi.getInstructions();
360 BitSet bb = new BitSet(code.length);
361
362 bb.set(0); // first insn is always a bb start
363
364 // first, look at the insn type
365 for (int i = 0; i < code.length; i++) {
366 Instruction insn = code[i];
367 if (insn instanceof IfInstruction) {
368 IfInstruction ifInsn = (IfInstruction) insn;
369
370 Instruction tgt = ifInsn.getTarget();
371 bb.set(tgt.getInstructionIndex());
372
373 tgt = ifInsn.getNext();
374 bb.set(tgt.getInstructionIndex());
375 } else if (insn instanceof GOTO) {
376 Instruction tgt = ((GOTO) insn).getTarget();
377 bb.set(tgt.getInstructionIndex());
378 } else if (insn instanceof JVMInvokeInstruction) {
379 // hmm, this might be a bit too conservative, but who says we
380 // don't jump out of a caller into a handler, or even that we
381 // ever return from the call?
382 Instruction tgt = insn.getNext();
383 bb.set(tgt.getInstructionIndex());
384 }
385 }
386
387 // and now look at the handlers (every first insn is a bb start)
388 ExceptionHandler[] handlers = mi.getExceptions();
389 if (handlers != null) {
390 for (int i = 0; i < handlers.length; i++) {
391 Instruction tgt = mi.getInstructionAt(handlers[i].getHandler());
392 bb.set(tgt.getInstructionIndex());
393 }
394 }
395
396 basicBlocks = bb;
397
398 /** dump
399 System.out.println();
400 System.out.println(mi.getFullName());
401 for (int i=0; i<code.length; i++) {
402 System.out.print(String.format("%1$2d %2$c ",i, bb.get(i) ? '>' : ' '));
403 System.out.println(code[i]);
404 }
405 **/
406 }
407
408 return basicBlocks;
409 }
410
411 Coverage getCoveredBasicBlocks() {
412 BitSet bExec = getExecutedInsn();
413 BitSet bb = getBasicBlocks();
414 int nCov = 0;
415
416 if (excludeHandlers) {
417 BitSet handlers = getHandlers();
418 bb.and(handlers);
419 }
420
421 if (bExec != null) {
422 BitSet bCov = new BitSet(bb.size());
423 bCov.or(bb);
424 bCov.and(bExec);
425 nCov = bCov.cardinality();
426 }
427
428 return new Coverage(bb.cardinality(), nCov);
429 }
430 }
431
432 static class ClassCoverage {
433
434 String className; // need to store since we never might get a ClassInfo
435 ClassInfo ci; // not initialized before the class is loaded
436 boolean covered;
437 HashMap<MethodInfo, MethodCoverage> methods;
438
439 ClassCoverage(String className) {
440 this.className = className;
441 }
442
443 void setLoaded(ClassInfo ci) {
444 if (methods == null) {
445 this.ci = ci;
446 covered = true;
447 log.info("used class: " + className);
448
449 methods = new HashMap<MethodInfo, MethodCoverage>();
450 for (MethodInfo mi : ci.getDeclaredMethodInfos()) {
451 // <2do> what about MJI methods? we should report why we don't cover them
452 if (!mi.isNative() && !mi.isAbstract()) {
453 MethodCoverage mc = new MethodCoverage(mi);
454 methods.put(mi, mc);
455 }
456 }
457 }
458 }
459
460 boolean isInterface() {
461 if (ci == null) // never loaded
462 if (!isCodeLoaded()) // can it be loaded?
463 return false; // will never load
464
465 return ci.isInterface();
466 }
467
468 boolean isCodeLoaded() {
469 if (ci != null)
470 return true;
471
472 try {
473 ci = ClassLoaderInfo.getCurrentResolvedClassInfo(className);
474 } catch (ClassInfoException cie) {
475 log.warning("CoverageAnalyzer problem: " + cie); // Just log the problem but don't fail. We still want the report to be written.
476 }
477
478 return ci != null;
479 }
480
481 MethodCoverage getMethodCoverage(MethodInfo mi) {
482 if (methods == null) {
483 setLoaded(ci);
484 }
485 return methods.get(mi);
486 }
487
488 Coverage getCoveredMethods() {
489 Coverage cov = new Coverage(0, 0);
490
491 if (methods != null) {
492 cov.total = methods.size();
493
494 for (MethodCoverage mc : methods.values()) {
495 if (mc.covered != null) {
496 cov.covered++;
497 }
498 }
499 }
500
501 return cov;
502 }
503
504 Coverage getCoveredInsn() {
505 Coverage cov = new Coverage(0, 0);
506
507 if (methods != null) {
508 for (MethodCoverage mc : methods.values()) {
509 Coverage c = mc.getCoveredInsn();
510 cov.total += c.total;
511 cov.covered += c.covered;
512 }
513 }
514
515 return cov;
516 }
517
518 boolean getCoveredLines(BitSet executable, BitSet covered) {
519 boolean result = false;
520
521 if (methods == null) {
522 return false;
523 }
524
525 for (MethodCoverage mc : methods.values()) {
526 result = mc.getCoveredLines(executable, covered) || result;
527 }
528
529 return result;
530 }
531
532 Coverage getCoveredLines() {
533 BitSet executable = new BitSet();
534 BitSet covered = new BitSet();
535
536 getCoveredLines(executable, covered);
537
538 return new Coverage(executable.cardinality(), covered.cardinality());
539 }
540
541 Coverage getCoveredBasicBlocks() {
542 Coverage cov = new Coverage(0, 0);
543
544 if (methods != null) {
545 for (MethodCoverage mc : methods.values()) {
546 Coverage c = mc.getCoveredBasicBlocks();
547 cov.total += c.total;
548 cov.covered += c.covered;
549 }
550 }
551
552 return cov;
553 }
554
555 Coverage getCoveredBranches() {
556 Coverage cov = new Coverage(0, 0);
557
558 if (methods != null) {
559 for (MethodCoverage mc : methods.values()) {
560 Coverage c = mc.getCoveredBranches();
561 cov.total += c.total;
562 cov.covered += c.covered;
563 }
564 }
565
566 return cov;
567 }
568 }
569
570 StringSetMatcher includes = null; // means all
571 StringSetMatcher excludes = null; // means none
572 StringSetMatcher loaded;
573 static boolean loadedOnly; // means we only check loaded classes that are not filtered
574 static boolean showMethods; // do we want to show per-method coverage?
575 static boolean showMethodBodies;
576 static boolean excludeHandlers; // do we count the handlers in? (off-nominal CF)
577 static boolean showBranchCoverage; // makes only sense with showMethods
578 static boolean showRequirements; // report requirements coverage
579 HashMap<String, ClassCoverage> classes = new HashMap<String, ClassCoverage>();
580
581 public CoverageAnalyzer(Config conf, JPF jpf) {
582 includes = StringSetMatcher.getNonEmpty(conf.getStringArray("coverage.include"));
583 excludes = StringSetMatcher.getNonEmpty(conf.getStringArray("coverage.exclude"));
584
585 showMethods = conf.getBoolean("coverage.show_methods", false);
586 showMethodBodies = conf.getBoolean("coverage.show_bodies", false);
587 excludeHandlers = conf.getBoolean("coverage.exclude_handlers", false);
588 showBranchCoverage = conf.getBoolean("coverage.show_branches", true);
589 loadedOnly = conf.getBoolean("coverage.loaded_only", true);
590 showRequirements = conf.getBoolean("coverage.show_requirements", false);
591
592 if (!loadedOnly) {
593 getCoverageCandidates(); // this might take a little while
594 }
595
596 jpf.addPublisherExtension(ConsolePublisher.class, this);
597 }
598
599 void getCoverageCandidates() {
600
601 // doesn't matter in which order we process this, since we
602 // just store one entry per qualified class name (i.e. there won't be
603 // multiple entries)
604 // NOTE : this doesn't yet deal with ClassLoaders, but that's also true for BCEL
605 ClassLoaderInfo cl = ClassLoaderInfo.getCurrentClassLoader();
606 for (String s : cl.getClassPathElements()) {
607 log.fine("analyzing classpath element: " + s);
608 File f = new File(s);
609 if (f.exists()) {
610 if (f.isDirectory()) {
611 traverseDir(f, null);
612 } else if (s.endsWith(".jar")) {
613 traverseJar(f);
614 }
615 }
616 }
617 }
618
619 void addClassEntry(String clsName) {
620 ClassCoverage cc = new ClassCoverage(clsName);
621 classes.put(clsName, cc);
622 log.info("added class candidate: " + clsName);
623 }
624
625 boolean isAnalyzedClass(String clsName) {
626 return StringSetMatcher.isMatch(clsName, includes, excludes);
627 }
628
629 void traverseDir(File dir, String pkgPrefix) {
630 for (File f : dir.listFiles()) {
631 if (f.isDirectory()) {
632 String prefix = f.getName();
633 if (pkgPrefix != null) {
634 prefix = pkgPrefix + '.' + prefix;
635 }
636 traverseDir(f, prefix);
637 } else {
638 String fname = f.getName();
639 if (fname.endsWith(".class")) {
640 if (f.canRead() && (f.length() > 0)) {
641 String clsName = fname.substring(0, fname.length() - 6);
642 if (pkgPrefix != null) {
643 clsName = pkgPrefix + '.' + clsName;
644 }
645
646 if (isAnalyzedClass(clsName)) {
647 addClassEntry(clsName);
648 }
649 } else {
650 log.warning("cannot read class file: " + fname);
651 }
652 }
653 }
654 }
655 }
656
657 void traverseJar(File jar) {
658 try {
659 JarFile jf = new JarFile(jar);
660 for (Enumeration<JarEntry> entries = jf.entries(); entries.hasMoreElements();) {
661 JarEntry e = entries.nextElement();
662 if (!e.isDirectory()) {
663 String eName = e.getName();
664 if (eName.endsWith(".class")) {
665 if (e.getSize() > 0) {
666 String clsName = eName.substring(0, eName.length() - 6);
667 clsName = clsName.replace('/', '.');
668 if (isAnalyzedClass(clsName)) {
669 addClassEntry(clsName);
670 }
671 } else {
672 log.warning("cannot read jar entry: " + eName);
673 }
674 }
675 }
676 }
677 } catch (IOException iox) {
678 iox.printStackTrace();
679 }
680 }
681
682
683 HashMap<String, Integer> getGlobalRequirementsMethods() {
684 HashMap<String, Integer> map = new HashMap<String, Integer>();
685
686 // <2do> this is extremely braindead, since inefficient
687 // it would be much better to do this with Class<?> instead of ClassInfo, but
688 // then we need a JPF- ClassLoader, since the path might be different. As a
689 // middle ground, we could use BCEL
690
691 for (ClassCoverage cc : classes.values()) {
692 ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(cc.className);
693 for (MethodInfo mi : ci.getDeclaredMethodInfos()) {
694 AnnotationInfo ai = getRequirementsAnnotation(mi);
695 if (ai != null) {
696 for (String id : ai.getValueAsStringArray()) {
697 Integer n = map.get(id);
698 if (n == null) {
699 map.put(id, 1);
700 } else {
701 map.put(id, n + 1);
702 }
703 }
704 }
705 }
706 }
707
708 return map;
709 }
710
711 int computeTotalRequirementsMethods(HashMap<String, Integer> map) {
712 int n = 0;
713 for (Integer i : map.values()) {
714 n += i;
715 }
716 return n;
717 }
718
719 private void computeCoverages(String packageFilter, List<Map.Entry<String, ClassCoverage>> clsEntries, Coverage cls, Coverage mth, Coverage branch, Coverage block, Coverage line, Coverage insn) {
720 for (Map.Entry<String, ClassCoverage> e : clsEntries) {
721 if (e.getKey().startsWith(packageFilter)) {
722 ClassCoverage cc = e.getValue();
723
724 if (cc.isInterface()) {
725 continue; // no code
726 }
727
728 cls.total++;
729
730 if (cc.covered) {
731 cls.covered++;
732 }
733
734 insn.add(cc.getCoveredInsn());
735 line.add(cc.getCoveredLines());
736 block.add(cc.getCoveredBasicBlocks());
737 branch.add(cc.getCoveredBranches());
738 mth.add(cc.getCoveredMethods());
739 }
740 }
741 }
742
743
744 /**
745 * print uncovered source ranges
746 */
747
748 //-------- the listener interface
749 @Override
750 public void classLoaded(VM vm, ClassInfo ci) {
751 String clsName = ci.getName();
752
753 if (loadedOnly) {
754 if (isAnalyzedClass(clsName)) {
755 addClassEntry(clsName);
756 }
757 }
758
759 ClassCoverage cc = classes.get(clsName);
760 if (cc != null) {
761 cc.setLoaded(ci);
762 }
763 }
764 MethodInfo lastMi = null;
765 MethodCoverage lastMc = null;
766
767 MethodCoverage getMethodCoverage(Instruction insn) {
768
769 if (!insn.isExtendedInstruction()) {
770 MethodInfo mi = insn.getMethodInfo();
771 if (mi != lastMi) {
772 lastMc = null;
773 lastMi = mi;
774 ClassInfo ci = mi.getClassInfo();
775 if (ci != null) {
776 ClassCoverage cc = classes.get(ci.getName());
777 if (cc != null) {
778 lastMc = cc.getMethodCoverage(mi);
779 }
780 }
781 }
782
783 return lastMc;
784 }
785
786 return null;
787 }
788 HashMap<String, HashSet<MethodCoverage>> requirements;
789
790 void updateRequirementsCoverage(String[] ids, MethodCoverage mc) {
791 if (requirements == null) {
792 requirements = new HashMap<String, HashSet<MethodCoverage>>();
793 }
794
795 for (String id : ids) {
796 HashSet<MethodCoverage> mcs = requirements.get(id);
797 if (mcs == null) {
798 mcs = new HashSet<MethodCoverage>();
799 requirements.put(id, mcs);
800 }
801
802 if (!mcs.contains(mc)) {
803 mcs.add(mc);
804 }
805 }
806 }
807
808 AnnotationInfo getRequirementsAnnotation(MethodInfo mi) {
809 // <2do> should probably look for overridden method annotations
810 return mi.getAnnotation("gov.nasa.jpf.Requirement");
811 }
812
813 @Override
814 public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
815 MethodCoverage mc = getMethodCoverage(executedInsn);
816
817 if (mc != null) {
818 mc.setExecuted(ti, executedInsn);
819
820 if (showRequirements) {
821 if (executedInsn.getPosition() == 0) { // first insn in method, check for Requirements
822 AnnotationInfo ai = getRequirementsAnnotation(mc.getMethodInfo());
823 if (ai != null) {
824 String[] ids = ai.getValueAsStringArray();
825 updateRequirementsCoverage(ids, mc);
826 }
827 }
828 }
829 }
830 }
831
832 @Override
833 public void choiceGeneratorSet(VM vm, ChoiceGenerator<?> newCG) {
834 /*** should be an option
835 Instruction insn = vm.getLastInstruction();
836 MethodCoverage mc = getMethodCoverage(vm);
837 mc.setCGed(vm.getLastThreadInfo(),insn);
838 ***/
839 }
840
841 //-------- the publisher interface
842 private Publisher publisher;
843 private ArrayList<Map.Entry<String, ClassCoverage>> clsEntries;
844
845
846 abstract class Publish {
847 PrintWriter pw;
848
849 Publish () {}
850 Publish (Publisher p){
851 pw = p.getOut();
852 }
853
854 abstract void publish();
855 abstract void printClassCoverages();
856 abstract void printRequirementsCoverage();
857 }
858
859 class PublishConsole extends Publish {
860 PublishConsole (ConsolePublisher p){
861 super(p);
862 }
863
864 @Override
865 void publish() {
866 publisher.publishTopicStart("coverage statistics");
867
868 printClassCoverages();
869
870 if (showRequirements) {
871 printRequirementsCoverage();
872 }
873
874 }
875
876 void printCoverage (Coverage cov){
877 int nTotal = cov.total();
878 int nCovered = cov.covered();
879
880 String s;
881 if (nTotal <= 0) {
882 s = " - ";
883 } else {
884 s = String.format("%.2f (%d/%d)", ((double) nCovered / nTotal), nCovered, nTotal);
885 }
886 pw.print(String.format("%1$-18s", s));
887 }
888
889
890 @Override
891 void printClassCoverages() {
892 String space = " ";
893 Coverage clsCoverage = new Coverage(0, 0);
894 Coverage mthCoverage = new Coverage(0, 0);
895 Coverage bbCoverage = new Coverage(0, 0);
896 Coverage lineCoverage = new Coverage(0, 0);
897 Coverage insnCoverage = new Coverage(0, 0);
898 Coverage branchCoverage = new Coverage(0, 0);
899
900 computeCoverages("", clsEntries, clsCoverage, mthCoverage, branchCoverage, bbCoverage, lineCoverage, insnCoverage);
901
902 pw.println();
903 pw.println("-------------------------------------------- class coverage ------------------------------------------------");
904 pw.println("bytecode line basic-block branch methods location");
905 pw.println("------------------------------------------------------------------------------------------------------------");
906
907 // Write Body
908 for (Map.Entry<String, ClassCoverage> e : clsEntries) {
909 ClassCoverage cc = e.getValue();
910
911 printCoverage(cc.getCoveredInsn());
912 pw.print(space);
913
914 printCoverage(cc.getCoveredLines());
915 pw.print(space);
916
917 printCoverage(cc.getCoveredBasicBlocks());
918 pw.print(space);
919
920 printCoverage(cc.getCoveredBranches());
921 pw.print(space);
922
923 printCoverage(cc.getCoveredMethods());
924 pw.print(space);
925
926 pw.println(e.getKey());
927
928 if (showMethods) {
929 printMethodCoverages(cc);
930 }
931 }
932
933 pw.println();
934 pw.println("------------------------------------------------------------------------------------------------------------");
935
936 printCoverage(insnCoverage);
937 pw.print(space);
938 printCoverage(lineCoverage);
939 pw.print(space);
940 printCoverage(bbCoverage);
941 pw.print(space);
942 printCoverage(branchCoverage);
943 pw.print(space);
944 printCoverage(mthCoverage);
945 pw.print(space);
946 printCoverage(clsCoverage);
947 pw.println(" total");
948
949 }
950
951 @Override
952 void printRequirementsCoverage() {
953 HashMap<String, Integer> reqMethods = getGlobalRequirementsMethods();
954
955 String space = " ";
956 Coverage bbAll = new Coverage(0, 0);
957 Coverage insnAll = new Coverage(0, 0);
958 Coverage branchAll = new Coverage(0, 0);
959 Coverage mthAll = new Coverage(0, 0);
960 Coverage reqAll = new Coverage(0, 0);
961
962 reqAll.total = reqMethods.size();
963 mthAll.total = computeTotalRequirementsMethods(reqMethods);
964
965 pw.println();
966 pw.println();
967 pw.println("--------------------------------- requirements coverage -----------------------------------");
968 pw.println("bytecode basic-block branch methods requirement");
969 pw.println("-------------------------------------------------------------------------------------------");
970
971 for (String id : Misc.getSortedKeyStrings(reqMethods)) {
972
973 Coverage bbCoverage = new Coverage(0, 0);
974 Coverage insnCoverage = new Coverage(0, 0);
975 Coverage branchCoverage = new Coverage(0, 0);
976 Coverage reqMth = new Coverage(reqMethods.get(id), 0);
977
978 if (requirements != null && requirements.containsKey(id)) {
979 reqAll.covered++;
980 for (MethodCoverage mc : requirements.get(id)) {
981 insnCoverage.add(mc.getCoveredInsn());
982 bbCoverage.add(mc.getCoveredBasicBlocks());
983 branchCoverage.add(mc.getCoveredBranches());
984
985 mthAll.covered++;
986 reqMth.covered++;
987 }
988
989
990 printCoverage(insnCoverage);
991 pw.print(space);
992 printCoverage(bbCoverage);
993 pw.print(space);
994 printCoverage(branchCoverage);
995 pw.print(space);
996 printCoverage(reqMth);
997 pw.print("\"" + id + "\"");
998
999
1000 pw.println();
1001
1002 if (showMethods) {
1003 for (MethodCoverage mc : requirements.get(id)) {
1004
1005 pw.print(space);
1006 printCoverage(mc.getCoveredInsn());
1007 pw.print(space);
1008 printCoverage(mc.getCoveredBasicBlocks());
1009 pw.print(space);
1010 printCoverage(mc.getCoveredBranches());
1011 pw.print(space);
1012
1013 pw.print(mc.getMethodInfo().getFullName());
1014 pw.println();
1015 }
1016 }
1017 } else { // requirement not covered
1018 pw.print(" - - - ");
1019
1020 printCoverage(reqMth);
1021 pw.print("\"" + id + "\"");
1022 pw.println();
1023 }
1024
1025 insnAll.add(insnCoverage);
1026 bbAll.add(bbCoverage);
1027 branchAll.add(branchCoverage);
1028 }
1029
1030 pw.println();
1031 pw.println("------------------------------------------------------------------------------------------");
1032
1033 printCoverage(insnAll);
1034 pw.print(space);
1035 printCoverage(bbAll);
1036 pw.print(space);
1037 printCoverage(branchAll);
1038 pw.print(space);
1039 printCoverage(mthAll);
1040 pw.print(space);
1041 printCoverage(reqAll);
1042 pw.print(" total");
1043
1044 pw.println();
1045 }
1046
1047 void printMethodCoverages(ClassCoverage cc) {
1048 String space = " ";
1049 boolean result = true;
1050
1051 if (cc.methods == null) {
1052 return;
1053 }
1054
1055 ArrayList<Map.Entry<MethodInfo, MethodCoverage>> mthEntries =
1056 Misc.createSortedEntryList(cc.methods, new Comparator<Map.Entry<MethodInfo, MethodCoverage>>() {
1057
1058 @Override
1059 public int compare(Map.Entry<MethodInfo, MethodCoverage> o1,
1060 Map.Entry<MethodInfo, MethodCoverage> o2) {
1061 int a = o2.getValue().getCoveredInsn().percent();
1062 int b = o1.getValue().getCoveredInsn().percent();
1063
1064 if (a == b) {
1065 return o2.getKey().getUniqueName().compareTo(o1.getKey().getUniqueName());
1066 } else {
1067 return a - b;
1068 }
1069 }
1070 });
1071
1072 Coverage emptyCoverage = new Coverage(0, 0);
1073
1074 for (Map.Entry<MethodInfo, MethodCoverage> e : mthEntries) {
1075 MethodCoverage mc = e.getValue();
1076 MethodInfo mi = mc.getMethodInfo();
1077 Coverage insnCoverage = mc.getCoveredInsn();
1078 Coverage lineCoverage = mc.getCoveredLines();
1079 Coverage branchCoverage = mc.getCoveredBranches();
1080
1081 result = result && insnCoverage.isFullyCovered();
1082
1083
1084 pw.print(space);
1085 printCoverage(insnCoverage);
1086
1087 pw.print(space);
1088 printCoverage(lineCoverage);
1089
1090 pw.print(space);
1091 printCoverage(mc.getCoveredBasicBlocks());
1092
1093 pw.print(space);
1094 printCoverage(branchCoverage);
1095
1096 pw.print(space);
1097 printCoverage(emptyCoverage);
1098
1099 pw.print(space);
1100 pw.print(mi.getLongName());
1101 pw.println();
1102
1103 if (showMethodBodies &&
1104 (!insnCoverage.isFullyCovered() || !branchCoverage.isFullyCovered())) {
1105 printBodyCoverage(mc);
1106 }
1107 }
1108 }
1109
1110 void printBodyCoverage(MethodCoverage mc) {
1111 MethodInfo mi = mc.getMethodInfo();
1112 Instruction[] code = mi.getInstructions();
1113 BitSet cov = mc.getExecutedInsn();
1114 int i, start = -1;
1115
1116 BitSet handlers = mc.getHandlers();
1117
1118 if (excludeHandlers) {
1119 cov.andNot(handlers);
1120 }
1121
1122 for (i = 0; i < code.length; i++) {
1123 if (!cov.get(i)) { // not covered
1124 if (start == -1) {
1125 start = i;
1126 }
1127 } else { // covered
1128 if (start != -1) {
1129 printSourceRange(code, handlers, start, i - 1, "");
1130 start = -1;
1131 }
1132 }
1133 }
1134 if (start != -1) {
1135 printSourceRange(code, handlers, start, i - 1, "");
1136 }
1137
1138 // now print the missing branches
1139 BitSet branches = mc.getBranches();
1140 lastStart = -1; // reset in case condition and branch are in same line
1141 for (i = 0; i < code.length; i++) {
1142 if (branches.get(i)) {
1143 String prefix = "";
1144 BitSet bTrue = mc.branchTrue;
1145 BitSet bFalse = mc.branchFalse;
1146 if (bTrue != null) { // means we have condition bit sets
1147 boolean cTrue = bTrue.get(i);
1148 boolean cFalse = bFalse.get(i);
1149 if (cTrue) {
1150 prefix = cFalse ? "" : "F "; // covered or false missing
1151 } else {
1152 prefix = cFalse ? "T " : "N "; // true or both missing
1153 }
1154 } else {
1155 prefix = "N "; // not covered at all
1156 }
1157
1158 if (prefix != null) {
1159 printSourceRange(code, handlers, i, i, prefix);
1160 }
1161 }
1162 }
1163 }
1164
1165 // there might be several ranges within the same source line
1166 int lastStart = -1;
1167
1168 void printSourceRange(Instruction[] code, BitSet handlers,
1169 int start, int end, String prefix) {
1170
1171 int line = code[start].getLineNumber();
1172
1173 if (lastStart == line) {
1174 return;
1175 }
1176
1177 lastStart = line;
1178
1179 printLocation(prefix, "at", code[start].getSourceLocation(), handlers.get(start) ? "x" : "");
1180
1181 if (line != code[end].getLineNumber()) {
1182 printLocation(prefix, "..", code[end].getSourceLocation(), handlers.get(end) ? "x" : "");
1183 }
1184 // we need the "(..)" wrapper so that Eclipse parses this
1185 // as a source location when displaying the report in a console
1186 }
1187
1188 private void printLocation(String prefix, String at, String location, String suffix) {
1189
1190 printBlanks(pw, 84);
1191 pw.print(prefix);
1192 pw.print(at);
1193 pw.print(' ');
1194 pw.print(location);
1195 pw.print(' ');
1196 pw.println(suffix);
1197 }
1198
1199 void printBlanks(PrintWriter pw, int n) {
1200 for (int i = 0; i < n; i++) {
1201 pw.print(' ');
1202 }
1203 }
1204
1205 }
1206
1207
1208 @Override
1209 public void publishFinished(Publisher publisher) {
1210
1211 if (clsEntries == null) {
1212 clsEntries = Misc.createSortedEntryList(classes, new Comparator<Map.Entry<String, ClassCoverage>>() {
1213
1214 @Override
1215 public int compare(Map.Entry<String, ClassCoverage> o1,
1216 Map.Entry<String, ClassCoverage> o2) {
1217 return o2.getKey().compareTo(o1.getKey());
1218 }
1219 });
1220 }
1221
1222 this.publisher = publisher;
1223
1224 if (publisher instanceof ConsolePublisher) {
1225 new PublishConsole((ConsolePublisher) publisher).publish();
1226 }
1227 }
1228 }