Merge "Fix bug when renaming masked members with the same name" into ub-jack
diff --git a/jack-tests/tests/com/android/jack/shrob/ObfuscationWithoutMappingTests.java b/jack-tests/tests/com/android/jack/shrob/ObfuscationWithoutMappingTests.java
index 94ca2e2..c80e559 100644
--- a/jack-tests/tests/com/android/jack/shrob/ObfuscationWithoutMappingTests.java
+++ b/jack-tests/tests/com/android/jack/shrob/ObfuscationWithoutMappingTests.java
@@ -216,4 +216,9 @@
 
     env.runTest(new ComparatorMapping(refOutputMapping, candidateOutputMapping));
   }
+
+  @Test
+  public void test56_001() throws Exception {
+    runTest("056", "001", "");
+  }
 }
diff --git a/jack-tests/tests/com/android/jack/shrob/test056/info.txt b/jack-tests/tests/com/android/jack/shrob/test056/info.txt
new file mode 100644
index 0000000..cbc8c94
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/shrob/test056/info.txt
@@ -0,0 +1,2 @@
+This contains a class which masks its superclass field
+and uses -useuniqueclassmembernames.
\ No newline at end of file
diff --git a/jack-tests/tests/com/android/jack/shrob/test056/jack/A.java b/jack-tests/tests/com/android/jack/shrob/test056/jack/A.java
new file mode 100644
index 0000000..36b97f1
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/shrob/test056/jack/A.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2016 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.test056.jack;
+
+public class A {
+  int f;
+}
diff --git a/jack-tests/tests/com/android/jack/shrob/test056/jack/B.java b/jack-tests/tests/com/android/jack/shrob/test056/jack/B.java
new file mode 100644
index 0000000..9c1f306
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/shrob/test056/jack/B.java
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2015 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.test056.jack;
+
+public class B extends A {
+  int f;
+}
diff --git a/jack-tests/tests/com/android/jack/shrob/test056/proguard.flags001 b/jack-tests/tests/com/android/jack/shrob/test056/proguard.flags001
new file mode 100644
index 0000000..7dd76d0
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/shrob/test056/proguard.flags001
@@ -0,0 +1,2 @@
+-dontshrink
+-useuniqueclassmembernames
\ No newline at end of file
diff --git a/jack-tests/tests/com/android/jack/shrob/test056/refsObfuscationWithoutMapping/expected-001.txt b/jack-tests/tests/com/android/jack/shrob/test056/refsObfuscationWithoutMapping/expected-001.txt
new file mode 100644
index 0000000..f195ec5
--- /dev/null
+++ b/jack-tests/tests/com/android/jack/shrob/test056/refsObfuscationWithoutMapping/expected-001.txt
@@ -0,0 +1,6 @@
+com.android.jack.shrob.test056.jack.A -> pcz.nbqfcvq.wnpx.gufco.hrgh056.wnpx.N:
+    int f -> s:V
+    void <init>() -> <init>
+com.android.jack.shrob.test056.jack.B -> pcz.nbqfcvq.wnpx.gufco.hrgh056.wnpx.O:
+    int f -> s:V
+    void <init>() -> <init>
diff --git a/jack/src/com/android/jack/shrob/obfuscation/MaskedHierarchy.java b/jack/src/com/android/jack/shrob/obfuscation/MaskedHierarchy.java
new file mode 100644
index 0000000..5f9a323
--- /dev/null
+++ b/jack/src/com/android/jack/shrob/obfuscation/MaskedHierarchy.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 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.obfuscation;
+
+import com.android.jack.ir.ast.JClassOrInterface;
+import com.android.jack.ir.formatter.UserFriendlyFormatter;
+
+import javax.annotation.Nonnull;
+
+/**
+ * An {@link Exception} that occurs when we rename a field or method with a name already used in
+ * the hierarchy.
+ */
+public class MaskedHierarchy extends Exception {
+
+  private static final long serialVersionUID = 1L;
+
+  @Nonnull
+  private final String oldName;
+
+  @Nonnull
+  private final String newName;
+
+  @Nonnull
+  private final JClassOrInterface enclosingType;
+
+  public MaskedHierarchy(
+      @Nonnull String oldName,
+      @Nonnull JClassOrInterface enclosingType,
+      @Nonnull String newName) {
+    this.oldName = oldName;
+    this.newName = newName;
+    this.enclosingType = enclosingType;
+  }
+
+  @Override
+  public String getMessage() {
+    return oldName
+        + " in "
+        + UserFriendlyFormatter.getFormatter().getName(enclosingType)
+        + " was renamed to "
+        + newName
+        + " while the name already exists in the hierarchy.";
+  }
+}
+
diff --git a/jack/src/com/android/jack/shrob/obfuscation/MethodInHierarchyFinder.java b/jack/src/com/android/jack/shrob/obfuscation/MethodInHierarchyFinder.java
index af330ae..8d5b3f5 100644
--- a/jack/src/com/android/jack/shrob/obfuscation/MethodInHierarchyFinder.java
+++ b/jack/src/com/android/jack/shrob/obfuscation/MethodInHierarchyFinder.java
@@ -47,6 +47,7 @@
         }
       }
     }
+    visit(type);
     visitSuperTypes(type);
     visitSubTypes(type);
   }
diff --git a/jack/src/com/android/jack/shrob/obfuscation/ObfuscationContextInfo.java b/jack/src/com/android/jack/shrob/obfuscation/ObfuscationContextInfo.java
new file mode 100644
index 0000000..5c65803
--- /dev/null
+++ b/jack/src/com/android/jack/shrob/obfuscation/ObfuscationContextInfo.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 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.obfuscation;
+
+import com.android.jack.reporting.Reportable;
+import com.android.sched.util.location.Location;
+
+import javax.annotation.Nonnull;
+
+/**
+ * A {@link Reportable} information that occurs during the obfuscation phase.
+ */
+public class ObfuscationContextInfo implements Reportable {
+
+  @Nonnull
+  private final Location location;
+
+  @Nonnull
+  private final ProblemLevel level;
+
+  @Nonnull
+  private final Throwable cause;
+
+  public ObfuscationContextInfo(
+      @Nonnull Location location, @Nonnull ProblemLevel level, @Nonnull Throwable cause) {
+    this.location = location;
+    this.cause = cause;
+    this.level = level;
+  }
+
+  @Override
+  @Nonnull
+  public String getMessage() {
+    return location.getDescription() + ": Obfuscation: " + cause.getMessage();
+  }
+
+  @Override
+  @Nonnull
+  public ProblemLevel getDefaultProblemLevel() {
+    return level;
+  }
+
+}
diff --git a/jack/src/com/android/jack/shrob/obfuscation/Renamer.java b/jack/src/com/android/jack/shrob/obfuscation/Renamer.java
index 43271ab..5ba3059 100644
--- a/jack/src/com/android/jack/shrob/obfuscation/Renamer.java
+++ b/jack/src/com/android/jack/shrob/obfuscation/Renamer.java
@@ -29,8 +29,12 @@
 import com.android.jack.ir.ast.JMethodIdWide;
 import com.android.jack.ir.ast.JPackage;
 import com.android.jack.ir.ast.JSession;
+import com.android.jack.ir.ast.JType;
 import com.android.jack.ir.ast.JVisitor;
+import com.android.jack.ir.sourceinfo.SourceInfo;
 import com.android.jack.library.DumpInLibrary;
+import com.android.jack.reporting.Reportable.ProblemLevel;
+import com.android.jack.reporting.Reporter.Severity;
 import com.android.jack.shrob.obfuscation.nameprovider.NameProvider;
 import com.android.jack.shrob.proguard.GrammarActions;
 import com.android.jack.transformations.request.ChangeEnclosingPackage;
@@ -48,6 +52,8 @@
 import com.android.sched.util.config.ThreadConfig;
 import com.android.sched.util.config.id.BooleanPropertyId;
 import com.android.sched.util.config.id.PropertyId;
+import com.android.sched.util.location.FileLocation;
+import com.android.sched.util.location.LineLocation;
 
 import java.io.File;
 import java.util.Collection;
@@ -155,6 +161,16 @@
   }
 
   @Nonnull
+  static String getFieldKey(@Nonnull String name, @Nonnull JType type) {
+    return name + ':' + GrammarActions.getSignatureFormatter().getName(type);
+  }
+
+  @Nonnull
+  static String getMethodKey(@Nonnull String name, @Nonnull List<? extends JType> argumentTypes) {
+    return GrammarActions.getSignatureFormatter().getNameWithoutReturnType(name, argumentTypes);
+  }
+
+  @Nonnull
   static String getKey(@Nonnull HasName namedElement) {
     if (namedElement instanceof JFieldId) {
       return Renamer.getFieldKey((JFieldId) namedElement);
@@ -174,6 +190,14 @@
     }
   }
 
+  private static void rename(
+      @Nonnull CanBeRenamed node, @Nonnull String newName) {
+    if (mustBeRenamed((MarkerManager) node)) {
+      ((MarkerManager) node).addMarker(new OriginalNameMarker(node.getName()));
+      node.setName(newName);
+    }
+  }
+
   private class Visitor extends JVisitor {
 
     @Override
@@ -201,11 +225,33 @@
         for (JField field : type.getFields()) {
           JFieldId fieldId = field.getId();
           if (mustBeRenamed(fieldId)) {
-            String name;
-            do {
-              name = fieldNameProvider.getNewName(getKey(fieldId));
-            } while (FieldInHierarchyFinderVisitor.containsFieldKey(name, field));
-            rename(fieldId, fieldNameProvider);
+            String name = null;
+            boolean foundName;
+            try {
+              do {
+                String oldFieldKey = getKey(fieldId);
+                name = fieldNameProvider.getNewName(oldFieldKey);
+                foundName =
+                    FieldInHierarchyFinderVisitor.containsFieldKey(
+                        getFieldKey(name, field.getType()), field);
+                if (foundName && !fieldNameProvider.hasAlternativeName(oldFieldKey)) {
+                  throw new MaskedHierarchy(field.getName(), type, name);
+                }
+              } while (foundName);
+            } catch (MaskedHierarchy e) {
+              SourceInfo sourceInfo = field.getSourceInfo();
+              Jack.getSession()
+                  .getReporter()
+                  .report(
+                      Severity.NON_FATAL,
+                      new ObfuscationContextInfo(
+                          new LineLocation(
+                              new FileLocation(sourceInfo.getFileName()),
+                              sourceInfo.getStartLine()),
+                          ProblemLevel.INFO,
+                          e));
+            }
+            rename(fieldId, name);
           }
         }
 
@@ -213,11 +259,33 @@
         for (JMethod method : type.getMethods()) {
           JMethodIdWide methodId = method.getMethodId().getMethodIdWide();
           if (mustBeRenamed(methodId)) {
-            String name;
-            do {
-              name = methodNameProvider.getNewName(getKey(methodId));
-            } while (MethodInHierarchyFinder.containsMethodKey(name, methodId));
-            rename(methodId, methodNameProvider);
+            String name = null;
+            boolean foundName;
+            try {
+              do {
+                String oldMethodKey = getKey(methodId);
+                name = methodNameProvider.getNewName(oldMethodKey);
+                foundName =
+                    MethodInHierarchyFinder.containsMethodKey(
+                        getMethodKey(name, methodId.getParamTypes()), methodId);
+                if (foundName && !methodNameProvider.hasAlternativeName(oldMethodKey)) {
+                  throw new MaskedHierarchy(methodId.getName(), type, name);
+                }
+              } while (foundName);
+            } catch (MaskedHierarchy e) {
+              SourceInfo sourceInfo = method.getSourceInfo();
+              Jack.getSession()
+                  .getReporter()
+                  .report(
+                      Severity.NON_FATAL,
+                      new ObfuscationContextInfo(
+                          new LineLocation(
+                              new FileLocation(sourceInfo.getFileName()),
+                              sourceInfo.getStartLine()),
+                          ProblemLevel.INFO,
+                          e));
+            }
+            rename(methodId, name);
           }
         }
       }
diff --git a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/AlphabeticalNameProvider.java b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/AlphabeticalNameProvider.java
index aeaa6ac..5acc72a 100644
--- a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/AlphabeticalNameProvider.java
+++ b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/AlphabeticalNameProvider.java
@@ -46,4 +46,9 @@
   }
 
   protected abstract char getFirstChar();
+
+  @Override
+  public boolean hasAlternativeName(@Nonnull String oldName) {
+    return true;
+  }
 }
diff --git a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/DictionaryNameProvider.java b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/DictionaryNameProvider.java
index c0d58fc..b1b8635 100644
--- a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/DictionaryNameProvider.java
+++ b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/DictionaryNameProvider.java
@@ -106,4 +106,9 @@
       br = null;
     }
   }
+
+  @Override
+  public boolean hasAlternativeName(@Nonnull String oldName) {
+    return br != null && defaultNameProvider.hasAlternativeName(oldName);
+  }
 }
diff --git a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/MappingNameProvider.java b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/MappingNameProvider.java
index f390659..2570645 100644
--- a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/MappingNameProvider.java
+++ b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/MappingNameProvider.java
@@ -46,4 +46,9 @@
     }
     return defaultNameProvider.getNewName(oldName);
   }
+
+  @Override
+  public boolean hasAlternativeName(@Nonnull String oldName) {
+    return !names.containsKey(oldName) && defaultNameProvider.hasAlternativeName(oldName);
+  }
 }
\ No newline at end of file
diff --git a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/NameProvider.java b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/NameProvider.java
index 4b85eee..1890453 100644
--- a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/NameProvider.java
+++ b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/NameProvider.java
@@ -27,4 +27,6 @@
 public interface NameProvider {
   @Nonnull
   public String getNewName(@Nonnull String oldName);
+
+  public boolean hasAlternativeName(@Nonnull String oldName);
 }
diff --git a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/UniqueNameProvider.java b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/UniqueNameProvider.java
index a758b8e..6d238a8 100644
--- a/jack/src/com/android/jack/shrob/obfuscation/nameprovider/UniqueNameProvider.java
+++ b/jack/src/com/android/jack/shrob/obfuscation/nameprovider/UniqueNameProvider.java
@@ -49,4 +49,9 @@
     names.add(newName);
     return newName;
   }
+
+  @Override
+  public boolean hasAlternativeName(@Nonnull String oldName) {
+    return nameProvider.hasAlternativeName(oldName);
+  }
 }
diff --git a/jack/tests/com/android/jack/shrob/obfuscation/nameprovider/Rot13NameProvider.java b/jack/tests/com/android/jack/shrob/obfuscation/nameprovider/Rot13NameProvider.java
index 7a989e8..8b91db9 100644
--- a/jack/tests/com/android/jack/shrob/obfuscation/nameprovider/Rot13NameProvider.java
+++ b/jack/tests/com/android/jack/shrob/obfuscation/nameprovider/Rot13NameProvider.java
@@ -76,4 +76,9 @@
     return sb.toString();
   }
 
+  @Override
+  public boolean hasAlternativeName(@Nonnull String oldName) {
+    return false;
+  }
+
 }