/* * Copyright 2012 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.devtools.j2objc.util; import com.google.common.io.CharSource; import com.google.common.io.Files; import com.google.common.io.LineProcessor; import java.io.File; import java.io.IOException; import java.nio.charset.Charset; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Parses dead code reports generated by ProGuard. * * Example ProGuard configuration file to generate an acceptable listing: * * <pre><code> * -injars <foo.jar> * -libraryjars <java.home>/lib/rt.jar * * -dontoptimize * -dontobfuscate * -dontpreverify * -printusage * -dontnote * * -keep public class com.google.foo.Bar { * public static void main(java.lang.String[]); * } * * -keepclassmembers class * { * static final % *; * static final java.lang.String *; * } * </code></pre> * * @author Daniel Connelly */ public class ProGuardUsageParser { private static final Pattern proGuardMethodPattern = Pattern.compile( " " + // leading indent "(\\d+:\\d+:)?" + // method line numbers (optional) "((public|private|protected|static|synchronized|varargs|bridge|" + "native|abstract|strictfp|final|synthetic)\\s)*" + // keywords "((\\S+)\\s)?(\\S+)\\((\\S*)\\)"); // return type, method name, arguments private ProGuardUsageParser() { // Don't instantiate. } private static String buildTypeSignature(String type) { if (type.equals("byte")) { return "B"; } if (type.equals("char")) { return "C"; } if (type.equals("double")) { return "D"; } if (type.equals("float")) { return "F"; } if (type.equals("int")) { return "I"; } if (type.equals("long")) { return "J"; } if (type.equals("short")) { return "S"; } if (type.equals("boolean")) { return "Z"; } if (type.equals("void")) { return "V"; } if (type.endsWith("[]")) { return "[" + buildTypeSignature(type.substring(0, type.length() - 2)); } if (type.length() == 0) { return ""; } return "L" + type.replace('.', '/') + ";"; } private static String buildMethodSignature(String returnType, String argumentList) { String[] arguments = argumentList.split(","); StringBuilder signature = new StringBuilder().append("("); for (String argument : arguments) { signature.append(buildTypeSignature(argument)); } signature.append(")"); signature.append(buildTypeSignature(returnType != null ? returnType : "void")); return signature.toString(); } public static CodeReferenceMap parseDeadCodeFile(File file) { if (file != null) { try { return ProGuardUsageParser.parse(Files.asCharSource(file, Charset.defaultCharset())); } catch (IOException e) { throw new AssertionError(e); } } return null; } public static CodeReferenceMap parse(CharSource listing) throws IOException { LineProcessor<CodeReferenceMap> processor = new LineProcessor<CodeReferenceMap>() { CodeReferenceMap.Builder dead = CodeReferenceMap.builder(); String lastClass; @Override public CodeReferenceMap getResult() { return dead.build(); } private void handleClass(String line) { if (line.endsWith(":")) { // Class, but not completely dead; save to read its dead methods lastClass = line.substring(0, line.length() - 1); } else { dead.addClass(line); } } private void handleMethod(String line) throws IOException { Matcher methodMatcher = proGuardMethodPattern.matcher(line); if (!methodMatcher.matches()) { throw new AssertionError("Line doesn't match expected ProGuard format!"); } if (lastClass == null) { throw new IOException("Bad listing format: method not attached to a class"); } String returnType = methodMatcher.group(5); String methodName = methodMatcher.group(6); String arguments = methodMatcher.group(7); String signature = buildMethodSignature(returnType, arguments); dead.addMethod(lastClass, methodName, signature); } private void handleField(String line) throws IOException { String name = line.substring(line.lastIndexOf(" ") + 1); dead.addField(lastClass, name); } @Override public boolean processLine(String line) throws IOException { if (line.startsWith("ProGuard, version") || line.startsWith("Reading ")) { // ignore output header } else if (!line.startsWith(" ")) { handleClass(line); } else if (line.startsWith(" ") && !line.contains("(")) { handleField(line); } else { handleMethod(line); } return true; } }; return listing.readLines(processor); } // Used for testing. public static void main(String[] args) throws IOException { ProGuardUsageParser.parse(Files.asCharSource(new File(args[0]), Charset.defaultCharset())); } }