blob: 0382273848fd6d68a2981736f3751abc0c3556f9 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* 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.android.jack.shrob.proguard;
import com.android.jack.ir.formatter.BinarySignatureFormatter;
import com.android.jack.ir.formatter.SourceFormatter;
import com.android.jack.ir.formatter.TypeAndMethodFormatter;
import com.android.jack.ir.formatter.TypePackageAndMethodFormatter;
import com.android.jack.shrob.spec.AnnotationSpecification;
import com.android.jack.shrob.spec.ClassSpecification;
import com.android.jack.shrob.spec.ClassTypeSpecification;
import com.android.jack.shrob.spec.ClassTypeSpecification.TypeEnum;
import com.android.jack.shrob.spec.FieldSpecification;
import com.android.jack.shrob.spec.FilterSpecification;
import com.android.jack.shrob.spec.Flags;
import com.android.jack.shrob.spec.InheritanceSpecification;
import com.android.jack.shrob.spec.KeepModifier;
import com.android.jack.shrob.spec.MethodSpecification;
import com.android.jack.shrob.spec.ModifierSpecification;
import com.android.jack.shrob.spec.ModifierSpecification.AccessFlags;
import com.android.jack.shrob.spec.ModifierSpecification.Modifier;
import com.android.jack.shrob.spec.NameSpecification;
import com.android.jack.util.NamingTools;
import com.android.sched.util.file.AbstractStreamFile;
import com.android.sched.util.file.NoSuchFileException;
import com.android.sched.util.file.NotFileException;
import com.android.sched.util.location.FileLocation;
import com.android.sched.util.log.LoggerFactory;
import org.antlr.runtime.ANTLRFileStream;
import org.antlr.runtime.CharStream;
import org.antlr.runtime.CommonTokenStream;
import org.antlr.runtime.RecognitionException;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
/**
* Factory creating the specification using the output of the parser.
*/
// JSR305 annotation are commented out when there should be an annotation but
// the generated grammar code does not allow the code analyzer to validate it.
public class GrammarActions {
public static final char SHROB_REGEX_PATH_SEPARATOR = '/';
@Nonnull
private static final Logger logger = LoggerFactory.getLogger();
@Nonnull
private static final TypeAndMethodFormatter signatureFormatter =
BinarySignatureFormatter.getFormatter();
@Nonnull
private static final TypePackageAndMethodFormatter sourceFormatter =
SourceFormatter.getFormatter();
enum FilterSeparator {
GENERAL(".", "[^./]*"),
FILE(".", "[^/]*"),
CLASS("[^.]", "[^.]*"),
ATTRIBUTE(".", ".*");
/**
* Represents the pattern equivalent to Proguard's "?"
*/
@Nonnull
private final String singleCharWilcard;
/**
* Represents the pattern equivalent to Proguard's "*"
*/
@Nonnull
private final String multipleCharWildcard;
FilterSeparator(@Nonnull String singleCharWilcard, @Nonnull String multipleCharWildcard) {
this.singleCharWilcard = singleCharWilcard;
this.multipleCharWildcard = multipleCharWildcard;
}
}
private GrammarActions() {
}
@Nonnull
public static TypeAndMethodFormatter getSignatureFormatter() {
return signatureFormatter;
}
@Nonnull
public static TypePackageAndMethodFormatter getSourceFormatter() {
return sourceFormatter;
}
@Nonnull
public static String getSignature(@Nonnull String name) {
assert name != null;
if (name.contains("[")) {
String nameWithoutArray = name.substring(0, name.lastIndexOf('['));
return '[' + getSignature(nameWithoutArray);
}
StringBuilder sig = new StringBuilder();
if (name.equals("boolean")) {
sig.append("Z");
} else if (name.equals("byte")) {
sig.append("B");
} else if (name.equals("char")) {
sig.append("C");
} else if (name.equals("short")) {
sig.append("S");
} else if (name.equals("int")) {
sig.append("I");
} else if (name.equals("float")) {
sig.append("F");
} else if (name.equals("double")) {
sig.append("D");
} else if (name.equals("long")) {
sig.append("J");
} else if (name.equals("void")) {
sig.append("V");
} else {
sig.append(NamingTools.getTypeSignatureName(name));
}
return sig.toString();
}
@Nonnull
static String getSignature(@Nonnull String name, int dim) {
assert name != null;
StringBuilder sig = new StringBuilder();
for (int i = 0; i < dim; i++) {
sig.append("\\[");
}
// ... matches any number of arguments of any type
if (name.equals("...")) {
sig.append(".*");
// *** matches any type (primitive or non-primitive, array or non-array)
} else if (name.equals("***")) {
sig.append(".*");
// % matches any primitive type ("boolean", "int", etc, but not "void")
} else if (name.equals("%")) {
sig.append("(B|C|D|F|I|J|S|Z)");
} else if (name.equals("boolean")) {
sig.append("Z");
} else if (name.equals("byte")) {
sig.append("B");
} else if (name.equals("char")) {
sig.append("C");
} else if (name.equals("short")) {
sig.append("S");
} else if (name.equals("int")) {
sig.append("I");
} else if (name.equals("float")) {
sig.append("F");
} else if (name.equals("double")) {
sig.append("D");
} else if (name.equals("long")) {
sig.append("J");
} else if (name.equals("void")) {
sig.append("V");
} else {
sig.append(
convertNameToPattern(NamingTools.getTypeSignatureName(name), FilterSeparator.CLASS));
}
return sig.toString();
}
@Nonnull
private static String convertNameToPattern(
@Nonnull String name, @Nonnull FilterSeparator separator) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
switch (c) {
case '?':
sb.append(separator.singleCharWilcard);
break;
case '*':
int j = i + 1;
if (j < name.length() && name.charAt(j) == '*') {
// ** matches any part of a name, possibly containing
// any number of package separators or directory separators
sb.append(".*");
i++;
} else {
// * matches any part of a name not containing
// the package separator or directory separator
sb.append(separator.multipleCharWildcard);
}
break;
case '$':
sb.append("\\$");
break;
default:
sb.append(c);
break;
}
}
return sb.toString();
}
@Nonnull
static NameSpecification name(/*@Nonnull*/ String name, @Nonnull FilterSeparator separator) {
assert name != null;
String transformedName = "^" +
convertNameToPattern(name, separator) + "$";
Pattern pattern = Pattern.compile(transformedName);
return new NameSpecification(pattern);
}
static void addModifier(@Nonnull ModifierSpecification modSpec,
@Nonnull Modifier modifier, boolean hasNegator) {
modSpec.addModifier(modifier, hasNegator);
}
static void addAccessFlag(@Nonnull ModifierSpecification modSpec,
@Nonnull AccessFlags accessFlag, boolean hasNegator) {
modSpec.addAccessFlag(accessFlag, hasNegator);
}
@Nonnull
static ClassTypeSpecification classType(/*@Nonnull*/ TypeEnum type, boolean hasNegator) {
assert type != null;
ClassTypeSpecification classSpec = new ClassTypeSpecification(type);
classSpec.setNegator(hasNegator);
return classSpec;
}
@Nonnull
static InheritanceSpecification createInheritance(
/*@Nonnull*/ String className, boolean hasNameNegator,
@CheckForNull AnnotationSpecification annotationType) {
NameSpecification nameSpec = name(className, FilterSeparator.CLASS);
nameSpec.setNegator(hasNameNegator);
return new InheritanceSpecification(nameSpec, annotationType);
}
@Nonnull
static AnnotationSpecification annotation(/*@Nonnull*/ String annotationName,
boolean hasNameNegator) {
NameSpecification name = name(annotationName, FilterSeparator.CLASS);
name.setNegator(hasNameNegator);
return new AnnotationSpecification(name);
}
@Nonnull
static ClassSpecification classSpec(/* @Nonnull */
List<NameSpecification> classNames,
@Nonnull ClassTypeSpecification classType, /* @Nonnull */
AnnotationSpecification annotation, @Nonnull ModifierSpecification modifier) {
ClassSpecification classSpec = new ClassSpecification(classNames, classType, annotation);
classSpec.setModifier(modifier);
return classSpec;
}
static NameSpecification className(/* @Nonnull */ String name, boolean hasNameNegator) {
NameSpecification nameSpec;
if (name.equals("*")) {
nameSpec = name("**", FilterSeparator.CLASS);
} else {
nameSpec = name(name, FilterSeparator.CLASS);
}
nameSpec.setNegator(hasNameNegator);
return nameSpec;
}
static void method(@Nonnull ClassSpecification classSpec,
@CheckForNull AnnotationSpecification annotationType,
@CheckForNull String typeSig, /*@Nonnull*/ String name, @Nonnull String signature,
@CheckForNull ModifierSpecification modifier) {
assert name != null;
String fullName = "^" + convertNameToPattern(name, FilterSeparator.CLASS);
fullName += signature;
if (typeSig != null) {
fullName += typeSig;
} else {
fullName += "V";
}
fullName += "$";
Pattern pattern = Pattern.compile(fullName);
classSpec.add(new MethodSpecification(new NameSpecification(pattern),
modifier, annotationType));
}
static void fieldOrAnyMember(@Nonnull ClassSpecification classSpec,
@CheckForNull AnnotationSpecification annotationType, @CheckForNull String typeSig,
/*@Nonnull*/ String name, @Nonnull ModifierSpecification modifier) {
assert name != null;
if (typeSig == null) {
assert name.equals("*");
// This is the "any member" case, we have to handle methods as well.
method(classSpec,
annotationType,
getSignature("***", 0),
"*",
"\\(" + getSignature("...", 0) + "\\)",
modifier);
}
field(classSpec, annotationType, typeSig, name, modifier);
}
static void field(@Nonnull ClassSpecification classSpec,
@CheckForNull AnnotationSpecification annotationType, @CheckForNull String typeSig,
/*@Nonnull*/ String name, @Nonnull ModifierSpecification modifier) {
assert name != null;
NameSpecification typeSignature = null;
if (typeSig != null) {
typeSignature = name(typeSig, FilterSeparator.CLASS);
} else {
assert name.equals("*");
}
classSpec.add(
new FieldSpecification(
name(name, FilterSeparator.GENERAL), modifier, typeSignature, annotationType));
}
@CheckForNull
private static ProguardParser createParserFromFile(@Nonnull File file)
throws ProguardFileParsingException {
try {
AbstractStreamFile.check(file, new FileLocation(file.getPath()));
ProguardParser parser = createParserCommon(new ANTLRFileStream(file.getPath()));
return parser;
} catch (NoSuchFileException e) {
throw new ProguardFileParsingException(e);
} catch (NotFileException e) {
throw new ProguardFileParsingException(e);
} catch (IOException e) {
throw new ProguardFileParsingException(e);
}
}
@Nonnull
private static ProguardParser createParserCommon(@Nonnull CharStream stream) {
ProguardLexer lexer = new ProguardLexer(stream);
CommonTokenStream tokens = new CommonTokenStream(lexer);
ProguardParser parser = new ProguardParser(tokens);
return parser;
}
public static void parse(/*@Nonnull*/ String proguardFileName,
/*@Nonnull*/ String baseDir, @Nonnull Flags flags) throws RecognitionException,
ProguardFileParsingException {
assert proguardFileName != null;
assert baseDir != null;
File proguardFile = getFileFromBaseDir(baseDir, proguardFileName);
ProguardParser parser = createParserFromFile(proguardFile);
if (parser != null) {
try {
parser.prog(flags, proguardFile.getParentFile().getPath());
} catch (RecoverableRecognitionException e) {
// A recoverable error happened, but we do not want to ignore it.
throw e.getCause();
}
}
}
static void addKeepClassMembers(
@Nonnull Flags flags,
/*@Nonnull*/ ClassSpecification classSpecification,
@Nonnull KeepModifier keepModifier) {
assert classSpecification != null;
classSpecification.setKeepModifier(keepModifier);
flags.addKeepClassMembers(classSpecification);
}
static void addKeepClassSpecification(
@Nonnull Flags flags,
/*@Nonnull*/ ClassSpecification classSpecification,
@Nonnull KeepModifier keepModifier) {
assert classSpecification != null;
classSpecification.setKeepModifier(keepModifier);
flags.addKeepClassSpecification(classSpecification);
}
static void addKeepClassesWithMembers(
@Nonnull Flags flags,
/*@Nonnull*/ ClassSpecification classSpecification,
@Nonnull KeepModifier keepModifier) {
assert classSpecification != null;
classSpecification.setKeepModifier(keepModifier);
flags.addKeepClassesWithMembers(classSpecification);
}
static void mapping(@Nonnull Flags flags, /*@Nonnull*/ String baseDir,
/*@Nonnull*/ String mappingFilename) {
assert mappingFilename != null;
assert baseDir != null;
File mappingFile = getFileFromBaseDir(baseDir, mappingFilename);
if (!mappingFile.exists()) {
logger.log(Level.WARNING, "Mapping file {0} not found", mappingFile.getPath());
} else {
flags.setObfuscationMapping(mappingFile);
}
}
static void filter(
@Nonnull List<FilterSpecification> filter,
boolean negator, /*@Nonnull*/
String filterName,
@Nonnull FilterSeparator separator) {
assert filterName != null;
filter.add(new FilterSpecification(name(filterName, separator), negator));
}
static void attributeFilter(
@Nonnull Flags flags, @Nonnull List<FilterSpecification> attributeSpec) {
flags.addAllKeepAttribute(attributeSpec);
}
static void packageFilter(
@Nonnull Flags flags, @Nonnull List<FilterSpecification> packageSpec) {
flags.addAllKeepPackageName(packageSpec);
}
static void obfuscationDictionary(@Nonnull Flags flags, /*@Nonnull*/ String baseDir,
/*@Nonnull*/ String fileName) {
assert fileName != null;
assert baseDir != null;
File dictionary = getFileFromBaseDir(baseDir, fileName);
if (!dictionary.exists()) {
throw new AssertionError(dictionary.getPath() + " not found");
}
flags.setObfuscationDictionary(dictionary);
}
static void classObfuscationDictionary(
@Nonnull Flags flags, /*@Nonnull*/ String baseDir, /*@Nonnull*/ String fileName) {
assert fileName != null;
assert baseDir != null;
File dictionary = getFileFromBaseDir(baseDir, fileName);
if (!dictionary.exists()) {
throw new AssertionError(dictionary.getPath() + " not found");
}
flags.setClassObfuscationDictionary(dictionary);
}
static void packageObfuscationDictionary(
@Nonnull Flags flags, /*@Nonnull*/ String baseDir, /*@Nonnull*/ String fileName) {
assert fileName != null;
assert baseDir != null;
File dictionary = getFileFromBaseDir(baseDir, fileName);
if (!dictionary.exists()) {
throw new AssertionError(dictionary.getPath() + " not found");
}
flags.setPackageObfuscationDictionary(dictionary);
}
static void inJars(
@Nonnull Flags flags, /*@Nonnull*/ String baseDir, /*@Nonnull*/ String inJars) {
assert inJars != null;
assert baseDir != null;
List<File> pathList = getPathFromBaseDirAsList(baseDir, inJars);
flags.addInJars(pathList);
}
static void outJars(
@Nonnull Flags flags, /*@Nonnull*/ String baseDir, /*@Nonnull*/ String outJars) {
assert outJars != null;
assert baseDir != null;
List<File> pathList = getPathFromBaseDirAsList(baseDir, outJars);
flags.addOutJars(pathList);
if (flags.getOutJars().size() != 1) {
throw new AssertionError("Only one archive supported with -outjars for now");
}
}
static void libraryJars(
@Nonnull Flags flags, /*@Nonnull*/ String baseDir, /*@Nonnull*/ String libraryJars) {
assert libraryJars != null;
assert baseDir != null;
flags.addLibraryJars(getPathFromBaseDir(baseDir, libraryJars));
}
static void outputMapping(@Nonnull Flags flags, /*@Nonnull*/ String baseDir,
@CheckForNull String outputMapping) {
assert baseDir != null;
flags.setPrintMapping(true);
if (outputMapping != null) {
File mappingFile = getFileFromBaseDir(baseDir, outputMapping);
flags.setOutputMapping(mappingFile);
}
}
static void repackageClasses(@Nonnull Flags flags, @CheckForNull String newPackage) {
// TODO(delphinemartin): error when newPackage contains '*' or '?'
if (newPackage == null) {
newPackage = "";
}
flags.setPackageForRenamedClasses(newPackage);
flags.addKeepPackageNames(name(newPackage, FilterSeparator.GENERAL), false);
}
static void flattenPackageHierarchy(
@Nonnull Flags flags, @CheckForNull String newPackage) {
// TODO(delphinemartin): error when newPackage contains '*' or '?'
if (newPackage == null) {
newPackage = "";
}
flags.setPackageForFlatHierarchy(newPackage);
flags.addKeepPackageNames(name(newPackage, FilterSeparator.GENERAL), false);
}
static void dontUseMixedCaseClassnames(@Nonnull Flags flags) {
flags.setUseMixedCaseClassName(false);
}
static void useUniqueClassMemberNames(@Nonnull Flags flags) {
flags.setUseUniqueClassMemberNames(true);
}
@Nonnull
private static File getFileFromBaseDir(@Nonnull String baseDir, @Nonnull String path) {
File file = new File(path);
if (!file.isAbsolute()) {
file = new File(baseDir, path);
}
return file;
}
@Nonnull
private static String getPathFromBaseDir(@Nonnull String baseDir, @Nonnull String path) {
List<File> pathList = getPathFromBaseDirAsList(baseDir, path);
StringBuilder sb = new StringBuilder();
for (Iterator<File> iter = pathList.iterator(); iter.hasNext(); ) {
sb.append(iter.next().getPath());
if (iter.hasNext()) {
sb.append(File.pathSeparatorChar);
}
}
return sb.toString();
}
@Nonnull
private static List<File> getPathFromBaseDirAsList(
@Nonnull String baseDir, @Nonnull String path) {
String[] pathElements = path.split(File.pathSeparator);
List<File> pathList = new ArrayList<File>(pathElements.length);
for (String pathElement : pathElements) {
pathList.add(getFileFromBaseDir(baseDir, pathElement));
}
return pathList;
}
static void adaptClassStrings(@Nonnull Flags flags, @Nonnull List<FilterSpecification> filter) {
flags.addAdaptClassStrings(filter);
}
static void printUnsupportedFlag(/*@Nonnull*/ @SuppressWarnings("unused") String flag) {
}
static void printseeds(@Nonnull Flags flags, /* @Nonnull */
String baseDir, @CheckForNull String fileName) {
assert baseDir != null;
flags.setPrintSeeds(true);
if (fileName != null) {
flags.setSeedsFile(getFileFromBaseDir(baseDir, fileName));
}
}
static void adaptResourceFileNames(@Nonnull Flags flags,
@Nonnull List<FilterSpecification> filter) {
flags.adaptResourceFileNames(filter);
}
static void renameSourcefileAttribute(@Nonnull Flags flags,
@CheckForNull String newSourceFileName) {
if (newSourceFileName == null) {
newSourceFileName = "";
}
flags.setRenameSourceFileAttribute(newSourceFileName);
}
static void adaptResourceFileContents(@Nonnull Flags flags,
@Nonnull List<FilterSpecification> filter) {
flags.adaptResourceFileContents(filter);
}
}