001 package edu.rice.cs.cunit;
002
003 import edu.rice.cs.cunit.instrumentors.*;
004 import edu.rice.cs.cunit.instrumentors.threadCheck.AThreadCheckStrategy;
005 import edu.rice.cs.cunit.instrumentors.util.IScannerStrategy;
006 import edu.rice.cs.cunit.classFile.ClassFile;
007 import edu.rice.cs.cunit.classFile.ClassFileTools;
008 import edu.rice.cs.cunit.classFile.constantPool.AUTFPoolInfo;
009 import edu.rice.cs.cunit.classFile.constantPool.visitors.CheckUTFVisitor;
010 import edu.rice.cs.cunit.classFile.attributes.InstrumentationAttributeInfo;
011 import edu.rice.cs.cunit.classFile.attributes.AAttributeInfo;
012 import edu.rice.cs.cunit.classFile.attributes.RuntimeInvisibleAnnotationsAttributeInfo;
013 import edu.rice.cs.cunit.classFile.attributes.AAnnotationsAttributeInfo;
014 import edu.rice.cs.cunit.util.Debug;
015 import edu.rice.cs.cunit.util.FileOps;
016 import edu.rice.cs.cunit.util.ILambda;
017 import edu.rice.cs.cunit.util.StringOps;
018
019 import java.util.*;
020 import java.util.zip.ZipException;
021 import java.util.jar.JarOutputStream;
022 import java.util.jar.JarFile;
023 import java.util.jar.JarEntry;
024 import java.util.jar.Manifest;
025 import java.io.*;
026
027 /**
028 * @author Mathias Ricken
029 */
030 public class FileInstrumentor {
031 /**
032 * List of instrumentors that should be applied to a class.
033 */
034 protected ArrayList<IInstrumentationStrategy> _instrumentors = new ArrayList<IInstrumentationStrategy>();
035
036 /**
037 * Package prefix of where instrumentors are usually located (includes the '.' at the end).
038 */
039 public static final String INSTRUMENTOR_PACKAGE_PREFIX = "edu.rice.cs.cunit.instrumentors.";
040
041 /**
042 * File output stream if output is to a file instead of to regular Debug.out. Otherwise null.
043 */
044 protected static FileOutputStream _debugOut;
045
046 /**
047 * Add an instrumentation describing that this class has been instrumented?
048 */
049 protected static boolean _addAnnotationAttribute = true;
050
051 /**
052 * Force instrumentation even if the class has already been instrumented?
053 */
054 protected static boolean _forceInstrumentation = false;
055
056 /**
057 * Default name of the destination file when instrumenting the rt.jar.
058 */
059 protected static String _defaultDestRtJarName = "rt_i.jar";
060
061 /**
062 * Default name of the Concutest jar file that gets mixed in when instrumenting the rt.jar.
063 */
064 protected static String _defaultCunitRtJarName = "cunitrt.jar";
065
066 /**
067 * Default instrumentor name.
068 */
069 protected static String _defaultInstrumentorName = CompoundCompactRecordStrategy.class.getName();
070
071 /**
072 * Default system instrumentor name..
073 */
074 protected static String _defaultSystemInstrumentorName = CompoundCompactSystemStrategy.class.getName();
075
076 /**
077 * Returns a list of instrumentors.
078 *
079 * @return list of instrumentors
080 */
081 public List<IInstrumentationStrategy> getInstrumentors() {
082 return _instrumentors;
083 }
084
085 /**
086 * Creates a new instrumenting class loader using specified instrumentors.
087 *
088 * @param instrumentors array of instrumentors
089 */
090 public FileInstrumentor(IInstrumentationStrategy[] instrumentors) {
091 for(IInstrumentationStrategy itor : instrumentors) {
092 _instrumentors.add(itor);
093 }
094 }
095
096 /**
097 * Call done on all instrumentors.
098 */
099 public void done() {
100 // apply all instrumentors
101 for(IInstrumentationStrategy itor : _instrumentors) {
102 itor.done();
103 }
104
105 }
106
107 /**
108 * Instrument the class from the input stream and generate a class for the output stream.
109 *
110 * @param is input stream
111 * @param os output stream
112 *
113 * @return true if class was instrumented
114 *
115 * @throws java.io.IOException
116 * @throws ClassFormatError
117 */
118 public boolean instrumentStream(InputStream is, OutputStream os) throws IOException, ClassFormatError {
119 ClassFile cf = null;
120
121 byte[] b = new byte[is.available()];
122 int offset = 0, bytesRead;
123 while((offset < b.length) && (-1 != (bytesRead = is.read(b, offset, b.length - offset)))) {
124 offset += bytesRead;
125 }
126
127 ByteArrayOutputStream bos = new ByteArrayOutputStream();
128 boolean doInstrument = false;
129 try {
130 cf = new ClassFile(new ByteArrayInputStream(b));
131
132 InstrumentationAttributeInfo instrumentationAttribute =
133 (InstrumentationAttributeInfo)cf.getAttribute(InstrumentationAttributeInfo.NAME);
134 HashSet<String> appliedInstrumentors = new HashSet<String>();
135 if (instrumentationAttribute!=null) {
136 String[] instrClassNames = instrumentationAttribute.getInstrumentatorClassNames();
137 for(String s: instrClassNames) appliedInstrumentors.add(s);
138 }
139 // doInstrument = _forceInstrumentation || (instrumentationAttribute == null);
140 // if (doInstrument) {
141 String[] doNotInstrumentPatterns = new String[0];
142 AAttributeInfo attr = cf.getAttribute(RuntimeInvisibleAnnotationsAttributeInfo.getAttributeName());
143 if (null != attr) {
144 RuntimeInvisibleAnnotationsAttributeInfo ann = (RuntimeInvisibleAnnotationsAttributeInfo)attr;
145 for(AAnnotationsAttributeInfo.Annotation a : ann.getAnnotations()) {
146 String typeString = ClassFileTools.getTypeString(a.getType(), "");
147 if (typeString.substring(0, typeString.length() - 1).equals(DoNotInstrument.class.getName())) {
148 for(AAnnotationsAttributeInfo.Annotation.NameValuePair nvp : a.getPairs()) {
149 if (nvp.getName().toString().equals("instrumentors")) {
150 AAnnotationsAttributeInfo.Annotation.ConstantMemberValue c = nvp.getValue().
151 execute(AAnnotationsAttributeInfo.Annotation.CheckConstantMemberVisitor.singleton(), null);
152 AUTFPoolInfo patternString = c.getConstValue()
153 .execute(CheckUTFVisitor.singleton(), null);
154 doNotInstrumentPatterns = patternString.toString().split(";");
155 break;
156 }
157 }
158 break;
159 }
160 }
161 }
162 // apply all instrumentors
163 for(IInstrumentationStrategy itor : _instrumentors) {
164 if (!ClassFileTools.classNameMatches(itor.getClass().getName(), doNotInstrumentPatterns) &&
165 !appliedInstrumentors.contains(itor.getClass().getName())) {
166 itor.instrument(cf);
167 /*
168 // process all methods in this class
169 for(MethodInfo mi : cf.getMethods()) {
170 ArrayList<LineNumberRecord> lntErrors = mi.checkLineNumbers();
171 if (lntErrors.size()>0) {
172 Debug.out.println("\n"+itor.getClass().getName()+" introduced "+
173 LineNumberTableAttributeInfo.getAttributeName()+" Errors in "+
174 cf.getThisClassName()+"."+mi.getName()+mi.getDescriptor()+"!\n");
175 Debug.out.println(mi.toString(cf.getConstantPool(),true,true));
176 }
177 }
178 */
179 appliedInstrumentors.add(itor.getClass().getName());
180 }
181 else {
182 Debug.out.println("Class " + cf.getThisClassName() + " not instrumented with "
183 + itor.getClass().getName()
184 + " because it matched @DoNotInstrument instrumentors");
185 }
186 }
187 if (_addAnnotationAttribute) { InstrumentationAttributeInfo.addInstrumentationAttributeInfo(cf, appliedInstrumentors); }
188 // }
189
190 Debug.out.endDot();
191
192 cf.write(bos);
193 }
194 catch(ClassFormatError cfe) {
195 cfe.printStackTrace(Debug.out);
196 Debug.out.println("Copying unmodified class file.");
197 bos.write(b, 0, b.length);
198 }
199
200 os.write(bos.toByteArray());
201
202 // return doInstrument;
203 return true;
204 }
205
206 /**
207 * Instrument the class in the specified class file and write the output the another class file.
208 *
209 * @param inName input file name
210 * @param outName output file name
211 *
212 * @return true if class was instrumented
213 *
214 * @throws java.io.IOException
215 * @throws ClassFormatError
216 */
217 public boolean instrumentFile(String inName, String outName) throws IOException, ClassFormatError {
218 FileInputStream fis = new FileInputStream(inName);
219 FileOutputStream fos = new FileOutputStream(outName);
220
221 boolean wasInstrumented = instrumentStream(fis, fos);
222
223 fis.close();
224 fos.close();
225
226 return wasInstrumented;
227 }
228
229 /**
230 * Instrument all classes in the specified Jar file and write the output to a Jar stream.
231 *
232 * @param inJarName name of the input Jar
233 * @param jos Jar output stream
234 * @param filters filters; if non-empty and no filter matches, file is skipped
235 *
236 * @throws java.io.IOException
237 * @throws ClassFormatError
238 */
239 public void instrumentJarStream(String inJarName, JarOutputStream jos,
240 List<String> filters) throws IOException, ClassFormatError {
241 JarFile jf = null;
242
243 try {
244 jf = new JarFile(inJarName);
245
246 Enumeration<JarEntry> entries = jf.entries();
247 while(entries.hasMoreElements()) {
248 JarEntry e = entries.nextElement();
249 Debug.out.print(e.getName());
250 boolean matches = true;
251 if (filters.size()>0) {
252 matches = false;
253 for(String f: filters) {
254 if (e.getName().startsWith(f)) {
255 String rest = e.getName().substring(f.length());
256 // there may be no more "/"
257 if (rest.indexOf('/')<0) {
258 matches = true;
259 break;
260 }
261 }
262 }
263 }
264
265 if (matches && !"META-INF/MANIFEST.MF".equals(e.getName())) {
266 InputStream is = null;
267 try {
268 is = jf.getInputStream(e);
269 try {
270 jos.putNextEntry(new JarEntry(e.getName()));
271 if (e.getName().endsWith(".class")) {
272 if (instrumentStream(is, jos)) {
273 Debug.out.print(" <instrumented>");
274 }
275 else {
276 Debug.out.print(" <skipped: attribute/annotation>");
277 }
278 }
279 else {
280 byte[] buffer = new byte[1024];
281 int bytesRead;
282 while(-1 != (bytesRead = is.read(buffer))) {
283 jos.write(buffer, 0, bytesRead);
284 }
285
286 }
287 }
288 catch(ZipException zipExc) {
289 // ignore zipExc if it's "duplicate entry"
290 if (!zipExc.getMessage().startsWith("duplicate entry:")) {
291 throw zipExc;
292 }
293 else {
294 Debug.out.print(" <duplicate>");
295 }
296 }
297 Debug.out.println();
298 }
299 finally {
300 if (is!=null) is.close();
301 }
302 }
303 else {
304 if (!matches) {
305 Debug.out.println(" <skipped: no filter matched>");
306 }
307 else {
308 Debug.out.println(" <skipped>");
309 }
310 }
311 }
312 }
313 finally {
314 if (jf!=null) jf.close();
315 }
316 }
317
318 /**
319 * Instrument all classes in the specified Jar file and write the output to another Jar file.
320 *
321 * @param inJarName name of the input Jar
322 * @param outJarName name of the output Jar
323 *
324 * @throws java.io.IOException
325 * @throws ClassFormatError
326 */
327 public void instrumentJar(String inJarName, String outJarName) throws IOException, ClassFormatError {
328 JarFile jf = null;
329 FileOutputStream fos = null;
330 JarOutputStream jos = null;
331 try {
332 jf = new JarFile(inJarName);
333
334 fos = new FileOutputStream(outJarName);
335 Manifest manif = jf.getManifest();
336 if (manif!=null) {
337 jos = new JarOutputStream(fos, manif);
338 }
339 else {
340 jos = new JarOutputStream(fos);
341 }
342
343 instrumentJarStream(inJarName, jos, new ArrayList<String>());
344 }
345 catch(IOException ioe) {
346 IOException n = new IOException("Error instrumenting jar file "+inJarName+" to "+outJarName);
347 n.initCause(ioe);
348 throw n;
349 }
350 catch(ClassFormatError cfe) {
351 ClassFormatError n = new ClassFormatError("Error instrumenting jar file "+inJarName+" to "+outJarName);
352 n.initCause(cfe);
353 throw n;
354 }
355 finally {
356 if (jf!=null) jf.close();
357 if (jos!=null) jos.close();
358 if (fos!=null) fos.close();
359 }
360 }
361
362 /**
363 * Add a class to a Jar stream.
364 *
365 * @param inFileName file name of the class to add
366 * @param outFileName file name inside the Jar
367 * @param jos Jar output stream
368 * @param markAsInstrumented mark this class as instrumented
369 *
370 * @throws java.io.IOException
371 */
372 public static void addClassJarStream(String inFileName,
373 String outFileName,
374 JarOutputStream jos,
375 boolean markAsInstrumented) throws IOException {
376 jos.putNextEntry(new JarEntry(outFileName));
377 FileInputStream fis = new FileInputStream(inFileName);
378
379 if (markAsInstrumented && _addAnnotationAttribute) {
380 try {
381 ClassFile cf = new ClassFile(fis);
382 InstrumentationAttributeInfo.addInstrumentationAttributeInfo(cf, "");
383 cf.write(jos);
384 }
385 catch(ClassFormatError cfe) {
386 cfe.printStackTrace(Debug.out);
387 Debug.out.println("Copying unmodified class file.");
388 byte[] buffer = new byte[1024];
389 int bytesRead;
390 while(-1 != (bytesRead = fis.read(buffer))) {
391 jos.write(buffer, 0, bytesRead);
392 }
393 }
394 }
395 else {
396 byte[] buffer = new byte[1024];
397 int bytesRead;
398 while(-1 != (bytesRead = fis.read(buffer))) {
399 jos.write(buffer, 0, bytesRead);
400 }
401 }
402 fis.close();
403 }
404
405 /**
406 * Instrument all classes in the specified Jar file and write the output to a Jar stream.
407 *
408 * @param inJarName name of the input Jar
409 * @param jos Jar output stream
410 * @param markAsInstrumented true if these classes should be marked as instrumented
411 *
412 * @throws java.io.IOException
413 * @throws ClassFormatError
414 */
415 public void addJarStream(String inJarName, JarOutputStream jos, boolean markAsInstrumented) throws IOException,
416 ClassFormatError {
417 JarFile jf = new JarFile(inJarName);
418
419 Enumeration<JarEntry> entries = jf.entries();
420 while(entries.hasMoreElements()) {
421 JarEntry e = entries.nextElement();
422 Debug.out.print(e.getName());
423
424 if (!"META-INF/MANIFEST.MF".equals(e.getName())) {
425 InputStream is = jf.getInputStream(e);
426 try {
427 jos.putNextEntry(new JarEntry(e.getName()));
428
429 if (markAsInstrumented && (e.getName().toLowerCase().endsWith(".class")) && _addAnnotationAttribute) {
430 try {
431 ClassFile cf = new ClassFile(is);
432 InstrumentationAttributeInfo.addInstrumentationAttributeInfo(cf, "");
433 cf.write(jos);
434 }
435 catch(ClassFormatError cfe) {
436 cfe.printStackTrace(Debug.out);
437 Debug.out.println("Copying unmodified class file.");
438 byte[] buffer = new byte[1024];
439 int bytesRead;
440 while(-1 != (bytesRead = is.read(buffer))) {
441 jos.write(buffer, 0, bytesRead);
442 }
443 }
444 }
445 else {
446 byte[] buffer = new byte[1024];
447 int bytesRead;
448 while(-1 != (bytesRead = is.read(buffer))) {
449 jos.write(buffer, 0, bytesRead);
450 }
451 }
452 }
453 catch(ZipException zipExc) {
454 // ignore zipExc if it's "duplicate entry"
455 if (!zipExc.getMessage().startsWith("duplicate entry:")) {
456 throw zipExc;
457 }
458 else {
459 Debug.out.print(" <duplicate>");
460 }
461 }
462 Debug.out.println();
463 is.close();
464 }
465 }
466
467 jf.close();
468 }
469
470 /**
471 * @return the default name of the system instrumentor.
472 */
473 public static String getDefaultSystemInstrumentorName() {
474 return _defaultSystemInstrumentorName;
475 }
476
477 /**
478 * Set the name of the system instrumentor.
479 * @param defaultSystemInstrumentorName new name
480 */
481 public static void setDefaultSystemInstrumentorName(String defaultSystemInstrumentorName) {
482 _defaultSystemInstrumentorName = defaultSystemInstrumentorName;
483 }
484
485 /**
486 * @return the default name of the instrumentor.
487 */
488 public static String getDefaultInstrumentorName() {
489 return _defaultInstrumentorName;
490 }
491
492 /**
493 * Set the name of the instrumentor.
494 * @param defaultInstrumentorName new name
495 */
496 public static void setDefaultInstrumentorName(String defaultInstrumentorName) {
497 _defaultInstrumentorName = defaultInstrumentorName;
498 }
499
500 /**
501 * @return the default name of the destination rt.jar file.
502 */
503 public static String getDefaultDestRtJarName() {
504 return _defaultDestRtJarName;
505 }
506
507 /**
508 * Set the name of the default destination rt.jar file.
509 * @param defaultDestRtJarName new name
510 */
511 public static void setDefaultDestRtJarName(String defaultDestRtJarName) {
512 _defaultDestRtJarName = defaultDestRtJarName;
513 }
514
515 /**
516 * @return the default name of the cunit jar file.
517 */
518 public static String getDefaultCunitRtJarName() {
519 return _defaultCunitRtJarName;
520 }
521
522 /**
523 * Set the name of the CUnit mixin jar file.
524 * @param defaultCunitRtJarName new name
525 */
526 public static void setDefaultCunitRtJarName(String defaultCunitRtJarName) {
527 _defaultCunitRtJarName = defaultCunitRtJarName;
528 }
529
530 /**
531 * @return the default name of the rt.jar file.
532 */
533 public static String getDefaultSourceRtJarName() {
534 if (System.getProperty("os.name").equals("Mac OS X")) {
535 String p = System.getProperty("java.home") + File.separator + ".." + File.separator + "Classes" + File.separator;
536 return p + "classes.jar" + File.pathSeparator + p + "ui.jar";
537 } else {
538 return System.getProperty("java.home") + File.separator + "lib" + File.separator + "rt.jar";
539 }
540 }
541
542 /**
543 * Instrument a list of files.
544 *
545 * @param createBackups true if backups should be created (unless in a temporary directory)
546 * @param recurse recurse into subdirectories
547 * @param fi TCFileInstrumentor to use
548 * @param tempDirMap map from jar file name to temporary directory
549 * @param exceptionAction action to execute if an IOException occurs; if null, IOExceptions will not be caught
550 * @param names array of file names
551 *
552 * @throws java.io.IOException IOException during instrumentation
553 */
554 private static void instrumentList(boolean createBackups,
555 boolean recurse,
556 FileInstrumentor fi,
557 HashMap<String, File> tempDirMap,
558 ILambda.Binary<Void, String, IOException> exceptionAction,
559 String... names) throws IOException {
560 for(String name : names) {
561 try {
562 Debug.out.print(name);
563 File f = new File(name);
564
565 boolean cb = createBackups;
566 if (cb) {
567 for(File tempDir: tempDirMap.values()) {
568 if (FileOps.isContainedIn(f, tempDir)) {
569 cb = false;
570 break;
571 }
572 }
573 }
574
575 if (f.isDirectory()) {
576 if (recurse) {
577 Debug.out.println(" <directory>");
578 File[] files = f.listFiles();
579 HashSet<String> fileNames = new HashSet<String>();
580 for(File file : files) {
581 fileNames.add(file.getPath());
582 }
583 instrumentList(cb, recurse, fi, tempDirMap,
584 exceptionAction, fileNames.toArray(new String[0]));
585 }
586 else {
587 Debug.out.println(" <skipped: not recursing into directory>");
588 }
589 }
590 else {
591 if (name.endsWith(".class")) {
592 Debug.out.print(" <class>");
593 boolean wasInstrumented;
594 if (cb) {
595 wasInstrumented = instrumentAndRename(name, fi);
596 }
597 else {
598 String n = name.replace(".class", "_i.class");
599 wasInstrumented = fi.instrumentFile(name, n);
600 if (!(new File(name).delete()) || !(new File(n).renameTo(new File(name)))) {
601 throw new CouldNotDeleteAndRenameException(new File(name), new File(n));
602 }
603 }
604 if (wasInstrumented) {
605 Debug.out.println(" <instrumented>");
606 }
607 else {
608 Debug.out.println(" <skipped>");
609 }
610 }
611 else if (name.endsWith(".jar")) {
612 Debug.out.println(" <jar>");
613 if (cb) {
614 instrumentAndRenameJar(name, fi);
615 }
616 else {
617 String n = name.replace(".jar", "_i.jar");
618 fi.instrumentJar(name, n);
619 if (!(new File(name).delete()) || !(new File(n).renameTo(new File(name)))) {
620 throw new CouldNotDeleteAndRenameException(new File(name), new File(n));
621 }
622 }
623 }
624 else {
625 Debug.out.println(" <skipped: unknown file type>");
626 }
627 }
628 }
629 catch(IOException e) {
630 if (exceptionAction != null) {
631 exceptionAction.apply(name, e);
632 }
633 else {
634 throw e; // rethrow
635 }
636 }
637 }
638 }
639
640
641 private static boolean instrumentAndRename(String classFileName, FileInstrumentor fi) throws IOException {
642 String newFileName = classFileName.replace(".class", "_i.class");
643 boolean wasInstrumented = fi.instrumentFile(classFileName, newFileName);
644
645 if (wasInstrumented) {
646 String oldFileName = classFileName.replace(".class", ".class~");
647 if (!new File(classFileName).renameTo(new File(oldFileName))) {
648 throw new CouldNotDoubleRenameException(new File(classFileName), new File(oldFileName), new File(newFileName));
649 }
650 if (!new File(newFileName).renameTo(new File(classFileName))) {
651 throw new CouldNotRenameException(new File(newFileName), new File(classFileName));
652 }
653 }
654 return wasInstrumented;
655 }
656
657 private static void instrumentAndRenameJar(String jarFileName, FileInstrumentor fi) throws IOException {
658 String instrFileName = jarFileName.replace(".jar", "_i.jar");
659 fi.instrumentJar(jarFileName, instrFileName);
660
661 String oldFileName = jarFileName.replace(".jar", ".jar~");
662 if (!new File(jarFileName).renameTo(new File(oldFileName))) {
663 throw new CouldNotDoubleRenameException(new File(jarFileName), new File(oldFileName), new File(instrFileName));
664 }
665 if (!new File(instrFileName).renameTo(new File(jarFileName))) {
666 throw new CouldNotRenameException(new File(instrFileName), new File(jarFileName));
667 }
668 }
669
670 private static void instrumentRTJar(String sourceRtJarName,
671 String destRtJarName,
672 String cunitRtJarName,
673 List<String> cunitFilters,
674 FileInstrumentor fi,
675 FileInstrumentor fiSystem) throws IOException {
676 FileOutputStream fos = new FileOutputStream(destRtJarName);
677 JarOutputStream jos = null;
678
679 String[] paths = FileOps.splitPaths(sourceRtJarName, File.pathSeparatorChar);
680
681 // detect if we are instrumenting the rt.jar of the Java version we are currently running
682 // by seeing if the intersection of paths and the boot classpath is non-empty
683 String[] bootClassPaths = FileOps.splitPaths(System.getProperty("sun.boot.class.path"),
684 File.pathSeparatorChar);
685 HashSet<File> bootClassPathFiles = new HashSet<File>();
686 for(String p: bootClassPaths) {
687 File f = new File(p);
688 try {
689 f = f.getCanonicalFile();
690 }
691 catch(IOException ioe) { /* just use the non-canonical file */ }
692 bootClassPathFiles.add(f);
693 }
694 HashSet<File> inputFiles = new HashSet<File>();
695 for(String p: paths) {
696 File f = new File(p);
697 try {
698 f = f.getCanonicalFile();
699 }
700 catch(IOException ioe) { /* just use the non-canonical file */ }
701 inputFiles.add(f);
702 }
703 inputFiles.retainAll(bootClassPathFiles);
704 boolean instrumentingCurrentJavaVersion = !inputFiles.isEmpty();
705
706 File cunitRtJarFile = new File(cunitRtJarName);
707 for(String p: paths) {
708 Debug.out.println("Instrumenting " + p);
709 JarFile jf = new JarFile(p);
710 if (jos==null) {
711 Manifest manif = jf.getManifest();
712 if (instrumentingCurrentJavaVersion) {
713 // NOTE: Only set version information if we are running with the same version of Java
714 // as the rt.jar that we are instrumenting.
715 manif.getMainAttributes().put(new java.util.jar.Attributes.Name("Edu-Rice-Cs-CUnit-JavaVersion-Vendor"),
716 edu.rice.cs.plt.reflect.JavaVersion.CURRENT_FULL.vendor().toString());
717 manif.getMainAttributes().put(new java.util.jar.Attributes.Name("Edu-Rice-Cs-CUnit-JavaVersion"),
718 edu.rice.cs.plt.reflect.JavaVersion.CURRENT_FULL.toString());
719 manif.getMainAttributes().put(new java.util.jar.Attributes.Name("Edu-Rice-Cs-CUnit-CUnitRuntimeJar"),
720 cunitRtJarFile.getName()+"/"+cunitRtJarFile.length());
721 }
722 jos = new JarOutputStream(fos, manif);
723 }
724 fi.instrumentJarStream(p, jos, new ArrayList<String>());
725 jf.close();
726 }
727 Debug.out.println("Instrumenting " + cunitRtJarName);
728 fiSystem.instrumentJarStream(cunitRtJarName, jos, cunitFilters);
729
730 if (jos!=null) { jos.close(); }
731 fos.close();
732 }
733
734 /**
735 * Run the general file instrumentor.
736 * @param args command line arguments.
737 */
738 public static void main(String[] args) {
739 new Tool().run(args);
740 }
741
742 /**
743 * Class to perform the command line utility tasks.
744 */
745 public static class Tool {
746 protected Date _startDate;
747
748 protected boolean _createBackups = true;
749 protected boolean _unpackJars = false;
750 protected boolean _instrumentRt = false;
751 protected String _cunitRtJarName = getDefaultCunitRtJarName();
752 protected List<String> _cunitFilters = new ArrayList<String>();
753 protected String _destRtJarName = getDefaultDestRtJarName();
754 protected String _sourceRtJarName = getDefaultSourceRtJarName();
755 protected String _instrumentorClassName = getDefaultInstrumentorName();
756 protected String _sysInstrumentorClassName = getDefaultSystemInstrumentorName();
757 protected List<String> _parameters = new ArrayList<String>();
758
759 /**
760 * Run the file instrumentor.
761 * @param args command line arguments
762 */
763 public void run(String[] args) {
764 _startDate = new Date();
765 Debug.out.setDebug(true);
766
767 int argIndex = checkArgs(args);
768
769 Debug.out.println("Start time = " + _startDate);
770
771 HashSet<String> fileNames = new HashSet<String>();
772 for(int i=argIndex; i<args.length; ++i) {
773 if (fileNames.contains(args[i])) {
774 Debug.out.println("Duplicate input file: "+args[i]);
775 }
776 fileNames.add(args[i]);
777 }
778 String[] names = fileNames.toArray(new String[]{});
779
780 // map from jar file name to temp directory
781 HashMap<String, File> tempDirMap = new HashMap<String, File>();
782
783 if (_unpackJars) {
784 for (int i=0; i<names.length; ++i) {
785 String name = names[i];
786 File f = new File(name);
787 if (f.isFile() && (name.endsWith(".jar"))) {
788 File tempDir = null;
789 try {
790 tempDir = FileOps.createTempDirectory("cunitfi",null);
791
792 Debug.out.println("Unpacking jar "+name+" to "+tempDir.getPath());
793 long unpackCount = FileOps.unpackJar(f, tempDir);
794 Debug.out.println("\t"+unpackCount+" files unpacked");
795
796 tempDirMap.put(name,tempDir);
797 names[i] = tempDir.getPath();
798 }
799 catch(IOException ioe) {
800 Debug.out.println("Error: Could not unpack jar "+name+" to " + tempDir);
801 tempDirMap.remove(name);
802 names[i] = args[argIndex + i];
803 }
804 }
805 }
806
807 // change "class-path=" parameter
808 for(int paramIndex=0; paramIndex<_parameters.size(); ++paramIndex) {
809 String p = _parameters.get(paramIndex);
810 if (p.startsWith(AThreadCheckStrategy.CLASS_PATH_PARAM_PREFIX)) {
811 String cp = p.substring(AThreadCheckStrategy.CLASS_PATH_PARAM_PREFIX.length());
812 String[] paths = cp.split(System.getProperty("path.separator"));
813 StringBuilder sb = new StringBuilder();
814 sb.append(AThreadCheckStrategy.CLASS_PATH_PARAM_PREFIX);
815 boolean first = true;
816 for(String path: paths) {
817 if (first) { first = false; } else { sb.append(System.getProperty("path.separator")); }
818 File tempDir = tempDirMap.get(path);
819 if (tempDir!=null) {
820 sb.append(tempDir.getPath());
821 }
822 else {
823 sb.append(path);
824 }
825 }
826 _parameters.set(paramIndex, sb.toString());
827 }
828 }
829 }
830
831 FileInstrumentor fi = null;
832 try {
833 @SuppressWarnings("unchecked") Class<IInstrumentationStrategy> c = (Class<IInstrumentationStrategy>)Class
834 .forName(_instrumentorClassName);
835 fi = new FileInstrumentor(
836 new IInstrumentationStrategy[]{c.getConstructor(List.class).newInstance(_parameters)});
837 }
838 catch(NoSuchMethodException e) {
839 try {
840 @SuppressWarnings("unchecked") Class<IInstrumentationStrategy> c
841 = (Class<IInstrumentationStrategy>)Class.forName(_instrumentorClassName);
842 fi = new FileInstrumentor(new IInstrumentationStrategy[]{c.getConstructor().newInstance()});
843 }
844 catch(Throwable e2) {
845 e2.printStackTrace(Debug.out);
846 System.exit(1);
847 }
848 }
849 catch(Throwable e2) {
850 e2.printStackTrace(Debug.out);
851 System.exit(1);
852 }
853 FileInstrumentor fiSystem = null;
854 try {
855 @SuppressWarnings("unchecked") Class<IInstrumentationStrategy> c = (Class<IInstrumentationStrategy>)Class
856 .forName(_sysInstrumentorClassName);
857 fiSystem = new FileInstrumentor(
858 new IInstrumentationStrategy[]{c.getConstructor(List.class).newInstance(_parameters)});
859 }
860 catch(NoSuchMethodException e) {
861 try {
862 @SuppressWarnings("unchecked") Class<IInstrumentationStrategy> c
863 = (Class<IInstrumentationStrategy>)Class.forName(_sysInstrumentorClassName);
864 fiSystem = new FileInstrumentor(new IInstrumentationStrategy[]{c.getConstructor().newInstance()});
865 }
866 catch(Throwable e2) {
867 e2.printStackTrace(Debug.out);
868 System.exit(1);
869 }
870 }
871 catch(Throwable e2) {
872 e2.printStackTrace(Debug.out);
873 System.exit(1);
874 }
875
876 Debug.out.println("Parameters: " + _parameters.size());
877 for(String s : _parameters) {
878 Debug.out.println(s);
879 }
880 Debug.out.println();
881 final ArrayList<IOException> exceptionList = new ArrayList<IOException>();
882
883 try {
884 if (_instrumentRt) {
885 instrumentRTJar(_sourceRtJarName, _destRtJarName, _cunitRtJarName, _cunitFilters, fi, fiSystem);
886 }
887
888 instrumentList(_createBackups, true, fi, tempDirMap, new ILambda.Binary<Void, String, IOException>() {
889 public Void apply(String fileName, IOException ioe) {
890 exceptionList.add(ioe);
891 return null;
892 }
893 }, names);
894 if (fi!=null) { fi.done(); }
895 if (fiSystem!=null) { fiSystem.done(); }
896 }
897 catch(Throwable t) {
898 Debug.out.println(t);
899 t.printStackTrace(Debug.out);
900 }
901
902 if (_unpackJars) {
903 for(String name: tempDirMap.keySet()) {
904 try {
905 File tempDir = tempDirMap.get(name);
906
907 String newJarName = name.replace(".jar", "_i.jar");
908 try {
909 long packCount = FileOps.packJar(tempDir, new File(newJarName));
910 Debug.out.println("\t"+packCount+" files packed");
911 }
912 catch(IOException ioe) {
913 Debug.out.println("Error: Could not pack " + tempDir + " to jar " + newJarName);
914 System.exit(1);
915 }
916 if (_createBackups) {
917 String oldJarName = name.replace(".jar", ".jar~");
918 boolean res = new File(name).renameTo(new File(oldJarName));
919 if (!res) {
920 throw new CouldNotDoubleRenameException(new File(name), new File(oldJarName), new File(newJarName));
921 }
922 res = new File(newJarName).renameTo(new File(name));
923 if (!res) {
924 throw new CouldNotRenameException(new File(newJarName), new File(name));
925 }
926 }
927 else {
928 if (!(new File(name).delete()) || !(new File(newJarName).renameTo(new File(name)))) {
929 throw new CouldNotDeleteAndRenameException(new File(name), new File(newJarName));
930 }
931 }
932 if (!FileOps.deleteDirectory(tempDir)) {
933 Debug.out.println("Error: Could not delete directory " + tempDir);
934 System.exit(1);
935 }
936 }
937 catch(IOException e) {
938 e.printStackTrace();
939 exceptionList.add(e);
940 }
941 }
942 }
943
944 // print out scan results
945 Debug.out.println("Scanner results: ");
946 boolean found = false;
947 for(IInstrumentationStrategy itor : fi.getInstrumentors()) {
948 if (itor instanceof IScannerStrategy) {
949 IScannerStrategy scanner = (IScannerStrategy)itor;
950 for(IScannerStrategy.IScanResult sr : scanner.getScanResults()) {
951 found = true;
952 Debug.out.println("\t"+sr.getPropertyName()+": "+sr);
953 }
954 }
955 Debug.out.println();
956 }
957 if (!found) { Debug.out.println("\tNone"); }
958
959 Debug.out.println();
960 Debug.out.println("IOExceptions: " + exceptionList.size());
961 if (exceptionList.size()>0) {
962 Debug.out.println("Retrying IOExceptions...");
963 int undoneCount = 0;
964 boolean first = true;
965 for(IOException ioe: exceptionList) {
966 if (ioe instanceof RetryIOException) {
967 RetryIOException rioe = (RetryIOException)ioe;
968 try {
969 rioe.retry();
970 ++undoneCount;
971 }
972 catch(IOException e) {
973 if (first) { first = false; } else { Debug.out.println("The following IOExceptions could not be undone:"); }
974 Debug.out.println("\t"+ioe.getMessage());
975 ioe.printStackTrace(Debug.out);
976 Debug.out.println("----------------------------------------");
977 }
978 }
979 else {
980 if (first) { first = false; } else { Debug.out.println("The following IOExceptions could not be undone:"); }
981 Debug.out.println("\t"+ioe.getMessage());
982 ioe.printStackTrace(Debug.out);
983 Debug.out.println("----------------------------------------");
984 }
985 }
986 Debug.out.println();
987 Debug.out.println("IOExceptions: " + (exceptionList.size()-undoneCount));
988 }
989
990 Date endDate = new Date();
991 Debug.out.println("End time = " + endDate);
992 Debug.out.println(StringOps.toStringMillis(endDate.getTime() - _startDate.getTime()));
993 if (_debugOut!=null) {
994 Debug.out.close();
995 try {
996 _debugOut.close();
997 }
998 catch(IOException e) { /* ignore */ }
999 Debug.out.setOutput(System.out);
1000 }
1001 }
1002
1003 /**
1004 * Check the arguments, set the fields, and return the index of the first file.
1005 * @param args command line arguments
1006 * @return index of first file
1007 */
1008 protected int checkArgs(String[] args) {
1009 if (args.length == 0) {
1010 help(System.out);
1011 System.exit(1);
1012 }
1013
1014 int argIndex = 0;
1015 while((argIndex < args.length) && (args[argIndex].startsWith("-"))) {
1016 String a = args[argIndex++];
1017 if (a.equals("-h")) {
1018 help(System.out);
1019 System.exit(1);
1020 }
1021 else if (a.equals("-quiet")) {
1022 Debug.out.setDebug(false);
1023 }
1024 else if (a.equals("-nobackup")) {
1025 _createBackups = false;
1026 }
1027 else if (a.equals("-unpackjars")) {
1028 _unpackJars = true;
1029 }
1030 else if (a.equals("-force")) {
1031 _forceInstrumentation = true;
1032 }
1033 else if (a.equals("-noannot")) {
1034 _addAnnotationAttribute = false;
1035 }
1036 else if (a.equals("-output")) {
1037 if (argIndex + 1 > args.length) {
1038 System.err.println("Error: <txt> file parameter missing.");
1039 help(System.err);
1040 System.exit(1);
1041 }
1042 String name = args[argIndex++];
1043 try {
1044 _debugOut = new FileOutputStream(name);
1045 Debug.out.setOutput(new PrintStream(_debugOut, true));
1046 }
1047 catch(FileNotFoundException e) {
1048 System.err.println("Error: Could not open <txt> file " + name + " for output.");
1049 System.exit(1);
1050 }
1051 }
1052 else if (a.equals("-rt")) {
1053 _instrumentRt = true;
1054 }
1055 else if (a.equals("-rts")) {
1056 if (argIndex + 1 > args.length) {
1057 System.err.println("Error: <s> jar file parameter missing.");
1058 help(System.err);
1059 System.exit(1);
1060 }
1061 _sourceRtJarName = args[argIndex++];
1062 }
1063 else if (a.equals("-rtm")) {
1064 if (argIndex + 1 > args.length) {
1065 System.err.println("Error: <m> jar file parameter missing.");
1066 help(System.err);
1067 System.exit(1);
1068 }
1069 _cunitRtJarName = args[argIndex++];
1070 }
1071 else if (a.equals("-rtmfilter")) {
1072 if (argIndex + 1 > args.length) {
1073 System.err.println("Error: <f> filter parameter missing.");
1074 help(System.err);
1075 System.exit(1);
1076 }
1077 String filters = args[argIndex++];
1078 _cunitFilters = new ArrayList<String>();
1079 String pathSep = File.pathSeparator;
1080 for(String f: filters.split(File.pathSeparator)) { _cunitFilters.add(f); }
1081 }
1082 else if (a.equals("-rtd")) {
1083 if (argIndex + 1 > args.length) {
1084 System.err.println("Error: <d> jar file parameter missing.");
1085 help(System.err);
1086 System.exit(1);
1087 }
1088 _destRtJarName = args[argIndex++];
1089 }
1090 else if (a.equals("-i")) {
1091 if (argIndex + 1 > args.length) {
1092 System.err.println("Error: <iclass> file parameter missing.");
1093 help(System.err);
1094 System.exit(1);
1095 }
1096 _instrumentorClassName = args[argIndex++];
1097 if (_instrumentorClassName.indexOf('.') < 0) {
1098 _instrumentorClassName = INSTRUMENTOR_PACKAGE_PREFIX + _instrumentorClassName;
1099 }
1100 }
1101 else if (a.equals("-s")) {
1102 if (argIndex + 1 > args.length) {
1103 System.err.println("Error: <sclass> file parameter missing.");
1104 help(System.err);
1105 System.exit(1);
1106 }
1107 _sysInstrumentorClassName = args[argIndex++];
1108 if (_sysInstrumentorClassName.indexOf('.') < 0) {
1109 _sysInstrumentorClassName = INSTRUMENTOR_PACKAGE_PREFIX + _sysInstrumentorClassName;
1110 }
1111 }
1112 else if (a.equals("-X")) {
1113 if (argIndex + 1 > args.length) {
1114 System.err.println("Error: <s> parameter missing.");
1115 help(System.err);
1116 System.exit(1);
1117 }
1118 _parameters.add(args[argIndex++]);
1119 }
1120 }
1121 return argIndex;
1122 }
1123
1124 /**
1125 * Print out a help message.
1126 * @param out stream to output to
1127 */
1128 protected void help(PrintStream out) {
1129 // ---------1---------2---------3---------4---------5---------6---------7---------8
1130 out.println("Flags : [-h] [-quiet] [-output <txt>] [-rt] [-rts <s>] [-rtm <m>] [-rtd <d>]");
1131 out.println(" [-nobackup] [-i <iclass>] [-s <sclass] [-X <s>] [-force]");
1132 out.println(" [-noannot] [<filename> ...]");
1133 out.println("-h Show this help");
1134 out.println("-quiet No output");
1135 out.println("-output <txt> Save output in the text file <txt>");
1136 out.println("-rt Instrument the rt.jar file <s> using the instrumentor,");
1137 out.println(" instrument the Concutest jar file <m> using the");
1138 out.println(" system instrumentor, and write to the jar file <d>");
1139 out.println("-rts <s> Specify the source rt.jar file <s> (rt.jar)");
1140 out.println("-rtm <m> Specify the Concutest jar file <m> (cunitrt.jar)");
1141 out.println("-rtmfilter <f> Specify filters for jar file <m>");
1142 out.println("-rtd <d> Specify the destination jar file <d> (rt_i.jar)");
1143 out.println("-nobackup Do not create backup files (*.jar~ or *.class~)");
1144 out.println("-i <iclass> Specify <iclass> as instrumentor class name");
1145 out.println("-s <sclass> Specify <sclass> as system instrumentor class name");
1146 out.println("-X <s> Pass <s> as parameter to instrumentors; may be repeated");
1147 out.println("-force Force the annotation, even if already marked instrumented");
1148 out.println("-noannot Do not annotate class files with the instrumentors used");
1149 out.println("-unpackjars Unpack/repack all jar files into temporary directories");
1150 out.println("<filename> ... List of class or jar file names to instrument using");
1151 out.println(" the instrumentor");
1152 out.println();
1153 out.println("Instrumentors must have a unary constructor (favored) accepting a");
1154 out.println("List<String> with parameters, or a zeroary constructor.");
1155 out.println();
1156 out.println("If instrumentor class names do not explicitly specify a package, the package");
1157 out.println("edu.rice.cs.cunit.instrumentors is assumed.");
1158 out.println();
1159 out.println("Default instrumentor: CompoundCompactRecordStrategy");
1160 out.println("Default system instrumentor: CompoundCompactSystemStrategy");
1161 out.println();
1162 out.println("If -unpackjars is used and -X class-path= has been specified, then the");
1163 out.println("jar files on the class path will be replaced by their temporary directories.");
1164 out.println("Jar files inside jar files will not be unpacked.");
1165 }
1166 }
1167 }