| // Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file | 
 | // for details. All rights reserved. Use of this source code is governed by a | 
 | // BSD-style license that can be found in the LICENSE file. | 
 | package com.android.tools.r8.shaking; | 
 |  | 
 | import com.android.tools.r8.errors.CompilationError; | 
 | import com.google.common.collect.ImmutableList; | 
 | import java.util.ArrayList; | 
 | import java.util.List; | 
 |  | 
 | public class ProguardKeepAttributes { | 
 |  | 
 |   public static final String SOURCE_FILE = "SourceFile"; | 
 |   public static final String SOURCE_DIR = "SourceDir"; | 
 |   public static final String INNER_CLASSES = "InnerClasses"; | 
 |   public static final String ENCLOSING_METHOD = "EnclosingMethod"; | 
 |   public static final String SIGNATURE = "Signature"; | 
 |   public static final String EXCEPTIONS = "Exceptions"; | 
 |   public static final String LINE_NUMBER_TABLE = "LineNumberTable"; | 
 |   public static final String LOCAL_VARIABLE_TABLE = "LocalVariableTable"; | 
 |   public static final String LOCAL_VARIABLE_TYPE_TABLE = "LocalVariableTypeTable"; | 
 |   public static final String METHOD_PARAMETERS = "MethodParameters"; | 
 |   public static final String SOURCE_DEBUG_EXTENSION = "SourceDebugExtension"; | 
 |   public static final String RUNTIME_VISIBLE_ANNOTATIONS = "RuntimeVisibleAnnotations"; | 
 |   public static final String RUNTIME_INVISIBLE_ANNOTATIONS = "RuntimeInvisibleAnnotations"; | 
 |   public static final String RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS = | 
 |       "RuntimeVisibleParameterAnnotations"; | 
 |   public static final String RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS = | 
 |       "RuntimeInvisibleParameterAnnotations"; | 
 |   public static final String RUNTIME_VISIBLE_TYPE_ANNOTATIONS = "RuntimeVisibleTypeAnnotations"; | 
 |   public static final String RUNTIME_INVISIBLE_TYPE_ANNOTATIONS = | 
 |       "RuntimeInvisibleTypeAnnotations"; | 
 |   public static final String ANNOTATION_DEFAULT = "AnnotationDefault"; | 
 |   public static final String STACK_MAP_TABLE = "StackMapTable"; | 
 |  | 
 |   public static final List<String> KEEP_ALL = ImmutableList.of("*"); | 
 |  | 
 |   public boolean sourceFile = false; | 
 |   public boolean sourceDir = false; | 
 |   public boolean innerClasses = false; | 
 |   public boolean enclosingMethod = false; | 
 |   public boolean signature = false; | 
 |   public boolean exceptions = false; | 
 |   public boolean lineNumberTable = false; | 
 |   public boolean localVariableTable = false; | 
 |   public boolean localVariableTypeTable = false; | 
 |   public boolean methodParameters = false; | 
 |   public boolean sourceDebugExtension = false; | 
 |   public boolean runtimeVisibleAnnotations = false; | 
 |   public boolean runtimeInvisibleAnnotations = false; | 
 |   public boolean runtimeVisibleParameterAnnotations = false; | 
 |   public boolean runtimeInvisibleParameterAnnotations = false; | 
 |   public boolean runtimeVisibleTypeAnnotations = false; | 
 |   public boolean runtimeInvisibleTypeAnnotations = false; | 
 |   public boolean annotationDefault = false; | 
 |   public boolean stackMapTable = false; | 
 |  | 
 |   private ProguardKeepAttributes() { | 
 |   } | 
 |  | 
 |   public static ProguardKeepAttributes filterOnlySignatures() { | 
 |     ProguardKeepAttributes result = new ProguardKeepAttributes(); | 
 |     result.applyPatterns(KEEP_ALL); | 
 |     result.signature = false; | 
 |     return result; | 
 |   } | 
 |  | 
 |   /** | 
 |    * Implements ProGuards attribute matching rules. | 
 |    * | 
 |    * @see <a href="https://www.guardsquare.com/en/proguard/manual/attributes">ProGuard manual</a>. | 
 |    */ | 
 |   private boolean update(boolean previous, String text, List<String> patterns) { | 
 |     for (String pattern : patterns) { | 
 |       if (previous) { | 
 |         return true; | 
 |       } | 
 |       if (pattern.length() > 0 && pattern.charAt(0) == '!') { | 
 |         if (matches(pattern, 1, text, 0)) { | 
 |           break; | 
 |         } | 
 |       } else { | 
 |         previous = matches(pattern, 0, text, 0); | 
 |       } | 
 |     } | 
 |     return previous; | 
 |   } | 
 |  | 
 |   private boolean matches(String pattern, int patternPos, String text, int textPos) { | 
 |     while (patternPos < pattern.length()) { | 
 |       char next = pattern.charAt(patternPos++); | 
 |       if (next == '*') { | 
 |         while (textPos < text.length()) { | 
 |           if (matches(pattern, patternPos, text, textPos++)) { | 
 |             return true; | 
 |           } | 
 |         } | 
 |         return patternPos >= pattern.length(); | 
 |       } else { | 
 |         if (textPos >= text.length() || text.charAt(textPos) != next) { | 
 |           return false; | 
 |         } | 
 |         textPos++; | 
 |       } | 
 |     } | 
 |     return textPos == text.length(); | 
 |   } | 
 |  | 
 |   public static ProguardKeepAttributes fromPatterns(List<String> patterns) { | 
 |     ProguardKeepAttributes keepAttributes = new ProguardKeepAttributes(); | 
 |     keepAttributes.applyPatterns(patterns); | 
 |     return keepAttributes; | 
 |   } | 
 |  | 
 |   public void applyPatterns(List<String> patterns) { | 
 |     sourceFile = update(sourceFile, SOURCE_FILE, patterns); | 
 |     sourceDir = update(sourceDir, SOURCE_DIR, patterns); | 
 |     innerClasses = update(innerClasses, INNER_CLASSES, patterns); | 
 |     enclosingMethod = update(enclosingMethod, ENCLOSING_METHOD, patterns); | 
 |     lineNumberTable = update(lineNumberTable, LINE_NUMBER_TABLE, patterns); | 
 |     localVariableTable = update(localVariableTable, LOCAL_VARIABLE_TABLE, patterns); | 
 |     localVariableTypeTable = update(localVariableTypeTable, LOCAL_VARIABLE_TYPE_TABLE, patterns); | 
 |     exceptions = update(exceptions, EXCEPTIONS, patterns); | 
 |     methodParameters = update(methodParameters, METHOD_PARAMETERS, patterns); | 
 |     signature = update(signature, SIGNATURE, patterns); | 
 |     sourceDebugExtension = update(sourceDebugExtension, SOURCE_DEBUG_EXTENSION, patterns); | 
 |     runtimeVisibleAnnotations = update(runtimeVisibleAnnotations, RUNTIME_VISIBLE_ANNOTATIONS, | 
 |         patterns); | 
 |     runtimeInvisibleAnnotations = update(runtimeInvisibleAnnotations, | 
 |         RUNTIME_INVISIBLE_ANNOTATIONS, patterns); | 
 |     runtimeVisibleParameterAnnotations = update(runtimeVisibleParameterAnnotations, | 
 |         RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS, patterns); | 
 |     runtimeInvisibleParameterAnnotations = update(runtimeInvisibleParameterAnnotations, | 
 |         RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS, patterns); | 
 |     runtimeVisibleTypeAnnotations = update(runtimeVisibleTypeAnnotations, | 
 |         RUNTIME_VISIBLE_TYPE_ANNOTATIONS, patterns); | 
 |     runtimeInvisibleTypeAnnotations = update(runtimeInvisibleTypeAnnotations, | 
 |         RUNTIME_INVISIBLE_TYPE_ANNOTATIONS, patterns); | 
 |     annotationDefault = update(annotationDefault, ANNOTATION_DEFAULT, patterns); | 
 |     stackMapTable = update(stackMapTable, STACK_MAP_TABLE, patterns); | 
 |   } | 
 |  | 
 |   public void ensureValid(boolean forceProguardCompatibility) { | 
 |     if (forceProguardCompatibility && innerClasses != enclosingMethod) { | 
 |       // If only one is true set both to true in Proguard compatibility mode. | 
 |       enclosingMethod = true; | 
 |       innerClasses = true; | 
 |     } | 
 |     if (innerClasses && !enclosingMethod) { | 
 |       throw new CompilationError("Attribute InnerClasses requires EnclosingMethod attribute. " | 
 |           + "Check -keepattributes directive."); | 
 |     } else if (!innerClasses && enclosingMethod) { | 
 |       throw new CompilationError("Attribute EnclosingMethod requires InnerClasses attribute. " | 
 |           + "Check -keepattributes directive."); | 
 |     } else if (signature && !innerClasses) { | 
 |       throw new CompilationError("Attribute Signature requires InnerClasses attribute. Check " | 
 |           + "-keepattributes directive."); | 
 |     } | 
 |     if (forceProguardCompatibility && localVariableTable && !lineNumberTable) { | 
 |       // If locals are kept, assume line numbers should be kept too. | 
 |       lineNumberTable = true; | 
 |     } | 
 |     if (localVariableTable && !lineNumberTable) { | 
 |       throw new CompilationError( | 
 |           "Attribute " + LOCAL_VARIABLE_TABLE | 
 |               + " requires " + LINE_NUMBER_TABLE | 
 |               + ". Check -keepattributes directive."); | 
 |     } | 
 |   } | 
 |  | 
 |   @Override | 
 |   public boolean equals(Object o) { | 
 |     if (this == o) { | 
 |       return true; | 
 |     } | 
 |     if (!(o instanceof ProguardKeepAttributes)) { | 
 |       return false; | 
 |     } | 
 |     ProguardKeepAttributes other = (ProguardKeepAttributes) o; | 
 |     return this.sourceFile == other.sourceFile | 
 |         && this.sourceDir == other.sourceDir | 
 |         && this.innerClasses == other.innerClasses | 
 |         && this.enclosingMethod == other.enclosingMethod | 
 |         && this.signature == other.signature | 
 |         && this.exceptions == other.exceptions | 
 |         && this.methodParameters == other.methodParameters | 
 |         && this.sourceDebugExtension == other.sourceDebugExtension | 
 |         && this.runtimeVisibleAnnotations == other.runtimeVisibleAnnotations | 
 |         && this.runtimeInvisibleAnnotations == other.runtimeInvisibleAnnotations | 
 |         && this.runtimeVisibleParameterAnnotations == other.runtimeVisibleParameterAnnotations | 
 |         && this.runtimeInvisibleParameterAnnotations == other.runtimeInvisibleParameterAnnotations | 
 |         && this.runtimeVisibleTypeAnnotations == other.runtimeVisibleTypeAnnotations | 
 |         && this.runtimeInvisibleTypeAnnotations == other.runtimeInvisibleTypeAnnotations | 
 |         && this.annotationDefault == other.annotationDefault | 
 |         && this.stackMapTable == other.stackMapTable; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public int hashCode() { | 
 |     return (this.sourceFile ? 1 : 0) | 
 |         + (this.sourceDir ? 1 << 1 : 0) | 
 |         + (this.innerClasses ? 1 << 2 : 0) | 
 |         + (this.enclosingMethod ? 1 << 3 : 0) | 
 |         + (this.signature ? 1 << 4 : 0) | 
 |         + (this.exceptions ? 1 << 5 : 0) | 
 |         + (this.sourceDebugExtension ? 1 << 6 : 0) | 
 |         + (this.runtimeVisibleAnnotations ? 1 << 7 : 0) | 
 |         + (this.runtimeInvisibleAnnotations ? 1 << 8 : 0) | 
 |         + (this.runtimeVisibleParameterAnnotations ? 1 << 9 : 0) | 
 |         + (this.runtimeInvisibleParameterAnnotations ? 1 << 10 : 0) | 
 |         + (this.runtimeVisibleTypeAnnotations ? 1 << 11 : 0) | 
 |         + (this.runtimeInvisibleTypeAnnotations ? 1 << 12 : 0) | 
 |         + (this.annotationDefault ? 1 << 13 : 0) | 
 |         + (this.stackMapTable ? 1 << 14 : 0) | 
 |         + (this.methodParameters ? 1 << 15 : 0); | 
 |   } | 
 |  | 
 |   public boolean isEmpty() { | 
 |     return !sourceFile | 
 |         && !sourceDir | 
 |         && !innerClasses | 
 |         && !enclosingMethod | 
 |         && !signature | 
 |         && !exceptions | 
 |         && !methodParameters | 
 |         && !sourceDebugExtension | 
 |         && !runtimeVisibleAnnotations | 
 |         && !runtimeInvisibleAnnotations | 
 |         && !runtimeVisibleParameterAnnotations | 
 |         && !runtimeInvisibleParameterAnnotations | 
 |         && !runtimeVisibleTypeAnnotations | 
 |         && !runtimeInvisibleTypeAnnotations | 
 |         && !annotationDefault | 
 |         && !stackMapTable; | 
 |   } | 
 |  | 
 |   public StringBuilder append(StringBuilder builder) { | 
 |     List<String> attributes = new ArrayList<>(); | 
 |     if (sourceFile) { | 
 |       attributes.add(SOURCE_FILE); | 
 |     } | 
 |     if (sourceDir) { | 
 |       attributes.add(SOURCE_DIR); | 
 |     } | 
 |     if (innerClasses) { | 
 |       attributes.add(INNER_CLASSES); | 
 |     } | 
 |     if (enclosingMethod) { | 
 |       attributes.add(ENCLOSING_METHOD); | 
 |     } | 
 |     if (signature) { | 
 |       attributes.add(SIGNATURE); | 
 |     } | 
 |     if (exceptions) { | 
 |       attributes.add(EXCEPTIONS); | 
 |     } | 
 |     if (methodParameters) { | 
 |       attributes.add(METHOD_PARAMETERS); | 
 |     } | 
 |     if (sourceDebugExtension) { | 
 |       attributes.add(SOURCE_DEBUG_EXTENSION); | 
 |     } | 
 |     if (runtimeVisibleAnnotations) { | 
 |       attributes.add(RUNTIME_VISIBLE_ANNOTATIONS); | 
 |     } | 
 |     if (runtimeInvisibleAnnotations) { | 
 |       attributes.add(RUNTIME_INVISIBLE_ANNOTATIONS); | 
 |     } | 
 |     if (runtimeVisibleParameterAnnotations) { | 
 |       attributes.add(RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS); | 
 |     } | 
 |     if (runtimeInvisibleParameterAnnotations) { | 
 |       attributes.add(RUNTIME_INVISIBLE_PARAMETER_ANNOTATIONS); | 
 |     } | 
 |     if (runtimeVisibleTypeAnnotations) { | 
 |       attributes.add(RUNTIME_VISIBLE_TYPE_ANNOTATIONS); | 
 |     } | 
 |     if (runtimeInvisibleTypeAnnotations) { | 
 |       attributes.add(RUNTIME_INVISIBLE_TYPE_ANNOTATIONS); | 
 |     } | 
 |     if (annotationDefault) { | 
 |       attributes.add(ANNOTATION_DEFAULT); | 
 |     } | 
 |     if (stackMapTable) { | 
 |       attributes.add(STACK_MAP_TABLE); | 
 |     } | 
 |  | 
 |     if (attributes.size() > 0) { | 
 |       builder.append("-keepattributes "); | 
 |       builder.append(String.join(",", attributes)); | 
 |     } | 
 |  | 
 |     return builder; | 
 |   } | 
 |  | 
 |   @Override | 
 |   public String toString() { | 
 |     return append(new StringBuilder()).toString(); | 
 |   } | 
 | } |