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    }