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 }