1 /*
   2  * Copyright (c) 2018, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 package openjdk.jcov.filter.simplemethods;
  26 
  27 import com.sun.tdk.jcov.util.Utils;
  28 import org.objectweb.asm.ClassReader;
  29 import org.objectweb.asm.tree.ClassNode;
  30 import org.objectweb.asm.tree.MethodNode;
  31 
  32 import java.io.BufferedWriter;
  33 import java.io.IOException;
  34 import java.io.InputStream;
  35 import java.net.URI;
  36 import java.net.URISyntaxException;
  37 import java.nio.file.FileSystem;
  38 import java.nio.file.FileSystems;
  39 import java.nio.file.FileVisitResult;
  40 import java.nio.file.Files;
  41 import java.nio.file.Path;
  42 import java.nio.file.Paths;
  43 import java.nio.file.SimpleFileVisitor;
  44 import java.nio.file.attribute.BasicFileAttributes;
  45 import java.util.ArrayList;
  46 import java.util.Arrays;
  47 import java.util.HashMap;
  48 import java.util.Iterator;
  49 import java.util.List;
  50 import java.util.function.BiPredicate;
  51 
  52 import static java.util.stream.Collectors.joining;
  53 
  54 public class Scanner {
  55     private static String USAGE =
  56             "java -classpath jcov.jar:SimpleMethods.jar --usage\n" +
  57                     "\n" +
  58                     "java -classpath jcov.jar:simple_methods_anc.jar " + Scanner.class.getName() +
  59                     " [--include|-i <include patern>] [--exclude|-e <exclude pattern>]" +
  60                     " [--include-module|-im <include module>] [--exclude-module|-em <exclude module>] \\\n" +
  61                     "[--getters <output file name>] " +
  62                     "[--setters <output file name>] " +
  63                     "[--delegators <output file name>] " +
  64                     "[--throwers <output file name>] " +
  65                     "[--empty <output file name>] \\\n" +
  66                     "jrt:/ | jar:file:/<jar file> | file:/<class hierarchy>\n" +
  67                     "\n" +
  68                     "    Options\n" +
  69                     "        --include - what classes to scan for simple methods.\n" +
  70                     "        --exclude - what classes to exclude from scanning.\n" +
  71                     "        --include-module - what modules to scan for simple methods.\n" +
  72                     "        --exclude-module - what modules to exclude from scanning.\n" +
  73                     "    Next options specify file names where to collect this or that type of methods. " +
  74                     "Only those which specified are detected. At least one kind of methods should be requested. " +
  75                     "Please consult the source code for exact details.\n" +
  76                     "        --getters - methods which are just returning a value.\n" +
  77                     "        --setters - methods which are just setting a field.\n" +
  78                     "        --delegators - methods which are just calling another method.\n" +
  79                     "        --throwers - methods which are just throwing an exception.\n" +
  80                     "        --empty - methods with an empty body.\n" +
  81                     "\n" +
  82                     "    Parameters define where to look for classes which are to be scanned.\n" +
  83                     "        jrt:/ - scan JDK classes\n" +
  84                     "        jar:file:/ - scan a jar file\n" +
  85                     "        file:/ - scan a directory containing compiled classes.";
  86 
  87     private Utils.Pattern[] includes;
  88     private Utils.Pattern[] excludes;
  89     private final List<Filter> filters = new ArrayList<>();
  90     private final List<URI> filesystems = new ArrayList<>();
  91 
  92     public static void main(String[] args) throws IOException, URISyntaxException {
  93         if (args.length == 1 && args[0].equals("--usage")) {
  94             usage();
  95             return;
  96         }
  97         Scanner scanner = new Scanner();
  98         final List<Utils.Pattern> includes = new ArrayList<>();
  99         final List<Utils.Pattern> excludes = new ArrayList<>();
 100         for (int i = 0; i < args.length; i++) {
 101             switch (args[i]) {
 102                 case "--include":
 103                 case "-i":
 104                     i++;
 105                     includes.add(new Utils.Pattern(args[i], true, false));
 106                     break;
 107                 case "--exclude":
 108                 case "-e":
 109                     i++;
 110                     excludes.add(new Utils.Pattern(args[i], false, false));
 111                     break;
 112                 case "--include-module":
 113                 case "-im":
 114                     i++;
 115                     includes.add(new Utils.Pattern(args[i], true, true));
 116                     break;
 117                 case "--exclude-module":
 118                 case "-em":
 119                     i++;
 120                     excludes.add(new Utils.Pattern(args[i], false, true));
 121                     break;
 122                 default:
 123                     //the only other options allowed are -<filter name>
 124                     //see usage
 125                     if (args[i].startsWith("--")) {
 126                         Filter filter = Filter.get(args[i].substring(2));
 127                         scanner.filters.add(filter);
 128                         i++;
 129                         filter.setOutputFile(args[i]);
 130                     } else {
 131                         try {
 132                             scanner.filesystems.add(new URI(args[i]));
 133                         } catch (URISyntaxException e) {
 134                             usage();
 135                             throw e;
 136                         }
 137                     }
 138             }
 139         }
 140         if (scanner.filters.size() == 0) {
 141             usage();
 142             String filtersList =
 143                     Arrays.stream(Filter.values()).map(f -> "--" + f.description).collect(joining(","));
 144             throw new IllegalArgumentException("One or more of " + filtersList + " options must be specified");
 145         }
 146         scanner.includes = includes.toArray(new Utils.Pattern[0]);
 147         scanner.excludes = excludes.toArray(new Utils.Pattern[0]);
 148         scanner.run();
 149     }
 150 
 151     private static void usage() {
 152         System.out.println(USAGE);
 153     }
 154 
 155     public void run() throws IOException {
 156         try {
 157             for (Filter f : filters) {
 158                 f.openFile();
 159             }
 160             for (URI uri : filesystems) {
 161                 FileSystem fs;
 162                 Iterator<Path> roots;
 163                 switch (uri.getScheme()) {
 164                     case "jrt":
 165                         fs = FileSystems.getFileSystem(uri);
 166                         roots = Files.newDirectoryStream(fs.getPath("./modules")).iterator();
 167                         break;
 168                     case "jar":
 169                         fs = FileSystems.newFileSystem(uri, new HashMap<>());
 170                         roots = fs.getRootDirectories().iterator();
 171                         break;
 172                     case "file":
 173                         fs = FileSystems.getDefault();
 174                         roots = List.of(fs.getPath(uri.getPath())).iterator();
 175                         break;
 176                     default:
 177                         throw new RuntimeException("TRI not supported: " + uri.toString());
 178                 }
 179                 while (roots.hasNext()) {
 180                     Path root = roots.next();
 181                     Files.walkFileTree(root, new SimpleFileVisitor<Path>() {
 182                         @Override
 183                         public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
 184                             if (file.toString().endsWith(".class")) {
 185                                 visitClass(root, file);
 186                             }
 187                             return FileVisitResult.CONTINUE;
 188                         }
 189                     });
 190                 }
 191             }
 192         } finally {
 193             for (Filter f : filters) {
 194                 f.closeFile();
 195             }
 196         }
 197     }
 198 
 199     private void visitClass(Path root, Path file) throws IOException {
 200         try (InputStream in = Files.newInputStream(file)) {
 201             ClassReader reader;
 202             reader = new ClassReader(in);
 203             String className = reader.getClassName();//.replace('/', '.');
 204             int lastDot = className.lastIndexOf('.');
 205             boolean included =
 206                     Utils.accept(includes, null, "/" + className, null);
 207             boolean not_excluded =
 208                     Utils.accept(excludes, null, "/" + className, null);
 209             if (included && not_excluded) {
 210                 ClassNode clazz = new ClassNode();
 211                 reader.accept(clazz, 0);
 212                 for (Object methodObject : clazz.methods) {
 213                     MethodNode method = (MethodNode) methodObject;
 214                     for (Filter f : filters) {
 215                         if (f.filter.test(clazz, method)) {
 216                             f.add(clazz.name + "#" + method.name + method.desc);
 217                         }
 218                     }
 219                 }
 220             }
 221         } catch (IOException e) {
 222             throw e;
 223         } catch (Exception e) {
 224             throw new RuntimeException("Exception while parsing file " + file + " from " + root, e);
 225         }
 226     }
 227 
 228     private enum Filter {
 229         getters("simple getter", new Getters()),
 230         setters("simple setter", new Setters()),
 231         delegators("simple delegator", new Delegators()),
 232         throwers("simple thrower", new Throwers()),
 233         empty("empty methods", new EmptyMethods());
 234         private String description;
 235         private BiPredicate<ClassNode, MethodNode> filter;
 236         private String outputFile;
 237         private BufferedWriter output;
 238 
 239         Filter(String description, BiPredicate<ClassNode, MethodNode> filter) {
 240             this.description = description;
 241             this.filter = filter;
 242         }
 243 
 244         public void setOutputFile(String outputFile) {
 245             this.outputFile = outputFile;
 246         }
 247 
 248         public void openFile() throws IOException {
 249             output = Files.newBufferedWriter(Paths.get(outputFile));
 250             output.write("#" + description);
 251             output.newLine();
 252             output.flush();
 253         }
 254 
 255         public void closeFile() throws IOException {
 256             if (outputFile != null) {
 257                 output.flush();
 258                 output.close();
 259             }
 260         }
 261 
 262         public void add(String s) throws IOException {
 263             output.write(s);
 264             output.newLine();
 265             output.flush();
 266         }
 267 
 268         static Filter get(String name) {
 269             for(Filter f : values()) {
 270                 if(f.name().equals(name)) {
 271                     return f;
 272                 }
 273             }
 274             throw new RuntimeException("Unknown filter: " + name);
 275         }
 276     }
 277 }