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 }