Add support for tracking original file names in proguard map

Bug: 143337111
Change-Id: I23db313fa7c3c0ef63b1fb2346628e88b46ca44d
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 07948a1..bb772d8 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -609,11 +609,6 @@
         }
       }
 
-      // Overwrite SourceFile if specified. This step should be done after IR conversion.
-      timing.begin("Rename SourceFile");
-      new SourceFileRewriter(appViewWithLiveness).run();
-      timing.end();
-
       // Collect the already pruned types before creating a new app info without liveness.
       Set<DexType> prunedTypes = appView.withLiveness().appInfo().getPrunedTypes();
 
@@ -823,6 +818,11 @@
           LineNumberOptimizer.run(appView, application, inputApp, namingLens);
       timing.end();
 
+      // Overwrite SourceFile if specified. This step should be done after IR conversion.
+      timing.begin("Rename SourceFile");
+      new SourceFileRewriter(appView, application).run();
+      timing.end();
+
       // If a method filter is present don't produce output since the application is likely partial.
       if (options.hasMethodsFilter()) {
         System.out.println("Finished compilation with method filter: ");
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
index 8e6d95c..43a0511 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMapper.java
@@ -6,6 +6,7 @@
 import static com.android.tools.r8.naming.ClassNameMapper.MissingFileAction.MISSING_FILE_IS_ERROR;
 import static com.android.tools.r8.utils.DescriptorUtils.descriptorToJavaType;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -91,15 +92,17 @@
     return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream(), null);
   }
 
-  public static ClassNameMapper mapperFromString(String contents, Reporter reporter)
-      throws IOException {
-    return mapperFromBufferedReader(CharSource.wrap(contents).openBufferedStream(), reporter);
+  public static ClassNameMapper mapperFromString(
+      String contents, DiagnosticsHandler diagnosticsHandler) throws IOException {
+    return mapperFromBufferedReader(
+        CharSource.wrap(contents).openBufferedStream(), diagnosticsHandler);
   }
 
-  private static ClassNameMapper mapperFromBufferedReader(BufferedReader reader, Reporter reporter)
-      throws IOException {
+  private static ClassNameMapper mapperFromBufferedReader(
+      BufferedReader reader, DiagnosticsHandler diagnosticsHandler) throws IOException {
     try (ProguardMapReader proguardReader =
-        new ProguardMapReader(reader, reporter != null ? reporter : new Reporter())) {
+        new ProguardMapReader(
+            reader, diagnosticsHandler != null ? diagnosticsHandler : new Reporter())) {
       ClassNameMapper.Builder builder = ClassNameMapper.builder();
       proguardReader.parse(builder);
       return builder.build();
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNaming.java b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
index d309e78..5a282d0 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNaming.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.utils.ThrowingConsumer;
@@ -20,6 +21,11 @@
 
     public abstract Builder addMappingInformation(MappingInformation mappingInformation);
 
+    public abstract Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber);
+
     public abstract ClassNaming build();
 
     /** This is an optional method, may be implemented as no-op */
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
index a93c04a..acbb577 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForMapApplier.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
@@ -83,6 +84,15 @@
     }
 
     @Override
+    public ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber) {
+      // Intentionally kept empty until we support additional information with -applymapping.
+      return this;
+    }
+
+    @Override
     public ClassNamingForMapApplier build() {
       return new ClassNamingForMapApplier(
           renamedName, originalName, position, qualifiedMethodMembers, methodMembers, fieldMembers);
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
index 1d52720..98293ab 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNamingForNameMapper.java
@@ -3,11 +3,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
+
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
 import com.android.tools.r8.naming.MemberNaming.Signature.SignatureKind;
 import com.android.tools.r8.naming.mappinginformation.MappingInformation;
+import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.utils.ChainableStringConsumer;
 import com.android.tools.r8.utils.ThrowingConsumer;
 import com.google.common.collect.ImmutableMap;
@@ -20,6 +24,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 /**
  * Stores name information for a class.
@@ -58,13 +63,39 @@
 
     @Override
     public ClassNaming.Builder addMappingInformation(MappingInformation mappingInformation) {
+      return addMappingInformation(
+          mappingInformation,
+          other -> {
+            assert false;
+          });
+    }
+
+    @Override
+    public ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation,
+        DiagnosticsHandler diagnosticsHandler,
+        int lineNumber) {
+      return addMappingInformation(
+          mappingInformation,
+          other ->
+              diagnosticsHandler.warning(
+                  MappingInformationDiagnostics.notAllowedCombination(
+                      originalName, renamedName, mappingInformation, other, lineNumber)));
+    }
+
+    private ClassNaming.Builder addMappingInformation(
+        MappingInformation mappingInformation, Consumer<MappingInformation> notAllowedCombination) {
       Signature signature =
           mappingInformation.isSignatureMappingInformation()
               ? mappingInformation.asSignatureMappingInformation().getSignature()
-              : null;
+              : NO_SIGNATURE;
       List<MappingInformation> additionalMappingForSignature =
           additionalMappings.computeIfAbsent(signature, ignored -> new ArrayList<>());
-      assert signature == null || additionalMappingForSignature.isEmpty();
+      for (MappingInformation information : additionalMappingForSignature) {
+        if (!information.allowOther(mappingInformation)) {
+          notAllowedCombination.accept(information);
+        }
+      }
       additionalMappingForSignature.add(mappingInformation);
       return this;
     }
@@ -348,6 +379,10 @@
     }
   }
 
+  public Map<Signature, List<MappingInformation>> getAdditionalMappings() {
+    return additionalMappings;
+  }
+
   @Override
   public String toString() {
     StringBuilder builder = new StringBuilder();
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNaming.java b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
index bf0a8dd..51c3272 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNaming.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNaming.java
@@ -168,6 +168,40 @@
     }
   }
 
+  public static class NoSignature extends Signature {
+
+    public static final NoSignature NO_SIGNATURE = new NoSignature();
+
+    public NoSignature() {
+      super("NO SIGNATURE");
+    }
+
+    @Override
+    Signature asRenamed(String renamedName) {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+
+    @Override
+    public SignatureKind kind() {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+
+    @Override
+    public boolean equals(Object o) {
+      return o == this;
+    }
+
+    @Override
+    public int hashCode() {
+      return 7;
+    }
+
+    @Override
+    void write(Writer builder) throws IOException {
+      throw new Unreachable("Should not be called on NoSignature");
+    }
+  }
+
   public static class FieldSignature extends Signature {
 
     public final String type;
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
index 2a67342..23a9a06 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapReader.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.FieldSignature;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
@@ -10,7 +11,6 @@
 import com.android.tools.r8.naming.mappinginformation.SignatureMappingInformation;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.utils.IdentifierUtils;
-import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.Maps;
 import com.google.gson.JsonObject;
@@ -62,20 +62,18 @@
 
   private final BufferedReader reader;
   private final JsonParser jsonParser = new JsonParser();
-  private final Reporter reporter;
+  private final DiagnosticsHandler diagnosticsHandler;
 
   @Override
   public void close() throws IOException {
-    if (reader != null) {
-      reader.close();
-    }
+    reader.close();
   }
 
-  ProguardMapReader(BufferedReader reader, Reporter reporter) {
+  ProguardMapReader(BufferedReader reader, DiagnosticsHandler diagnosticsHandler) {
     this.reader = reader;
-    this.reporter = reporter;
+    this.diagnosticsHandler = diagnosticsHandler;
     assert reader != null;
-    assert reporter != null;
+    assert diagnosticsHandler != null;
   }
 
   // Internal parser state
@@ -259,10 +257,14 @@
       // Try to parse any information added in comments above member namings
       if (isCommentLineWithJsonBrace()) {
         MappingInformation mappingInfo =
-            MappingInformation.fromJsonObject(parseJsonInComment(), reporter, lineNo);
-        if (mappingInfo != null && mappingInfo.isSignatureMappingInformation()) {
-          SignatureMappingInformation sigMapInfo = mappingInfo.asSignatureMappingInformation();
-          mappingInformation.put(sigMapInfo.getSignature(), sigMapInfo);
+            MappingInformation.fromJsonObject(parseJsonInComment(), diagnosticsHandler, lineNo);
+        if (mappingInfo != null) {
+          if (mappingInfo.isSignatureMappingInformation()) {
+            SignatureMappingInformation sigMapInfo = mappingInfo.asSignatureMappingInformation();
+            mappingInformation.put(sigMapInfo.getSignature(), sigMapInfo);
+          } else {
+            classNamingBuilder.addMappingInformation(mappingInfo, diagnosticsHandler, lineNo);
+          }
         }
         // Skip reading the rest of the line.
         lineOffset = line.length();
@@ -329,7 +331,7 @@
         activeMemberNaming =
             new MemberNaming(
                 signature,
-                mappingInformation.get(signature).apply(signature, renamedName, reporter),
+                mappingInformation.get(signature).apply(signature, renamedName, diagnosticsHandler),
                 getPosition());
       } else {
         activeMemberNaming =
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 1e57331..e2bf357 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -5,12 +5,12 @@
 
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardConfiguration;
 import java.util.Arrays;
 
@@ -21,10 +21,12 @@
  */
 public class SourceFileRewriter {
 
-  private final AppView<AppInfoWithLiveness> appView;
+  private final AppView<?> appView;
+  private final DexApplication application;
 
-  public SourceFileRewriter(AppView<AppInfoWithLiveness> appView) {
+  public SourceFileRewriter(AppView<?> appView, DexApplication application) {
     this.appView = appView;
+    this.application = application;
   }
 
   public void run() {
@@ -37,16 +39,18 @@
         && appView.options().forceProguardCompatibility) {
       return;
     }
+    boolean isMinifying = appView.options().isMinifying();
+    assert !isMinifying || appView.appInfo().hasLiveness();
     // Now, the user wants either to remove source file attribute or to rename it for non-kept
     // classes.
     DexString defaultRenaming = getSourceFileRenaming(proguardConfiguration);
-    for (DexClass clazz : appView.appInfo().classes()) {
+    for (DexClass clazz : application.classes()) {
       // We only parse sourceFile if -keepattributes SourceFile, but for compat we should add
       // a source file name, otherwise line positions will not be printed on the JVM or old version
       // of ART.
       if (!hasRenameSourceFileAttribute
           && proguardConfiguration.getKeepAttributes().sourceFile
-          && !appView.appInfo().isMinificationAllowed(clazz.type)) {
+          && !(isMinifying && appView.withLiveness().appInfo().isMinificationAllowed(clazz.type))) {
         continue;
       }
       clazz.sourceFile = defaultRenaming;
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
new file mode 100644
index 0000000..87b3e7b
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/FileNameInformation.java
@@ -0,0 +1,70 @@
+// Copyright (c) 2020, 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.naming.mappinginformation;
+
+import com.android.tools.r8.DiagnosticsHandler;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonPrimitive;
+
+public class FileNameInformation extends MappingInformation {
+
+  private final String fileName;
+
+  public static final String ID = "sourceFile";
+  static final String FILE_NAME_KEY = "fileName";
+
+  private FileNameInformation(String fileName) {
+    super(NO_LINE_NUMBER);
+    this.fileName = fileName;
+  }
+
+  public String getFileName() {
+    return fileName;
+  }
+
+  @Override
+  public String serialize() {
+    JsonObject result = new JsonObject();
+    result.add(MAPPING_ID_KEY, new JsonPrimitive(ID));
+    result.add(FILE_NAME_KEY, new JsonPrimitive(fileName));
+    return result.toString();
+  }
+
+  @Override
+  public boolean isFileNameInformation() {
+    return true;
+  }
+
+  @Override
+  public FileNameInformation asFileNameInformation() {
+    return this;
+  }
+
+  @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isFileNameInformation();
+  }
+
+  public static FileNameInformation build(String fileName) {
+    return new FileNameInformation(fileName);
+  }
+
+  public static FileNameInformation build(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
+    try {
+      JsonElement fileName =
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, FILE_NAME_KEY, ID);
+      if (fileName == null) {
+        return null;
+      }
+      return new FileNameInformation(fileName.getAsString());
+    } catch (UnsupportedOperationException | IllegalStateException ignored) {
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.invalidValueForObjectWithId(lineNumber, FILE_NAME_KEY, ID));
+      return null;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/InformationParsingError.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/InformationParsingError.java
deleted file mode 100644
index 00e6dc5..0000000
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/InformationParsingError.java
+++ /dev/null
@@ -1,95 +0,0 @@
-// Copyright (c) 2019, 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.naming.mappinginformation;
-
-import com.android.tools.r8.Diagnostic;
-import com.android.tools.r8.Keep;
-import com.android.tools.r8.origin.Origin;
-import com.android.tools.r8.position.Position;
-import com.android.tools.r8.position.TextPosition;
-
-@Keep
-public class InformationParsingError implements Diagnostic {
-
-  private final String message;
-  private final Position position;
-
-  @Override
-  public Origin getOrigin() {
-    return Origin.unknown();
-  }
-
-  @Override
-  public Position getPosition() {
-    return position;
-  }
-
-  @Override
-  public String getDiagnosticMessage() {
-    return message;
-  }
-
-  private InformationParsingError(String message, Position position) {
-    this.message = message;
-    this.position = position;
-  }
-
-  static InformationParsingError noHandlerFor(int lineNumber, String value) {
-    return new InformationParsingError(
-        String.format("Could not find a handler for %s", value),
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError noKeyInJson(int lineNumber, String key) {
-    return new InformationParsingError(
-        String.format("Could not locate '%s' in the JSON object", key),
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError notValidJson(int lineNumber) {
-    return new InformationParsingError(
-        "Not valid JSON", new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError notValidString(int lineNumber, String key) {
-    return new InformationParsingError(
-        String.format("The value of '%s' is not a valid string in the JSON object", key),
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError tooManyInformationalParameters(int lineNumber) {
-    return new InformationParsingError(
-        "More informational parameters than actual parameters for method signature",
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError noKeyForObjectWithId(
-      int lineNumber, String key, String mappingKey, String mappingValue) {
-    return new InformationParsingError(
-        String.format("Could not find '%s' for object with %s '%s'", key, mappingKey, mappingValue),
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError invalidValueForObjectWithId(
-      int lineNumber, String mappingKey, String mappingValue) {
-    return new InformationParsingError(
-        String.format(
-            "Could not decode the information for the object with %s '%s'",
-            mappingKey, mappingValue),
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError tooManyEntriesForParameterInformation(int lineNumber) {
-    return new InformationParsingError(
-        "Parameter information do not have 1 or 2 entries",
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-
-  static InformationParsingError invalidParameterInformationObject(int lineNumber) {
-    return new InformationParsingError(
-        "Parameter information is not an index and a string representation of a type",
-        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
-  }
-}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
index f1f3296..c1c5105 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformation.java
@@ -4,7 +4,7 @@
 
 package com.android.tools.r8.naming.mappinginformation;
 
-import com.android.tools.r8.utils.Reporter;
+import com.android.tools.r8.DiagnosticsHandler;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
 
@@ -34,35 +34,63 @@
     return null;
   }
 
+  public boolean isFileNameInformation() {
+    return false;
+  }
+
+  public FileNameInformation asFileNameInformation() {
+    return null;
+  }
+
+  public boolean isMethodSignatureChangedInformation() {
+    return false;
+  }
+
+  public MethodSignatureChangedInformation asMethodSignatureChangedInformation() {
+    return null;
+  }
+
+  public abstract boolean allowOther(MappingInformation information);
+
   public static MappingInformation fromJsonObject(
-      JsonObject object, Reporter reporter, int lineNumber) {
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
     if (object == null) {
-      reporter.info(InformationParsingError.notValidJson(lineNumber));
+      diagnosticsHandler.info(MappingInformationDiagnostics.notValidJson(lineNumber));
       return null;
     }
     JsonElement id = object.get(MAPPING_ID_KEY);
     if (id == null) {
-      reporter.info(InformationParsingError.noKeyInJson(lineNumber, MAPPING_ID_KEY));
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.noKeyInJson(lineNumber, MAPPING_ID_KEY));
       return null;
     }
     String idString = id.getAsString();
     if (idString == null) {
-      reporter.info(InformationParsingError.notValidString(lineNumber, MAPPING_ID_KEY));
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.notValidString(lineNumber, MAPPING_ID_KEY));
       return null;
     }
-    if (idString.equals(MethodSignatureChangedInformation.ID)) {
-      return MethodSignatureChangedInformation.build(object, reporter, lineNumber);
+    switch (idString) {
+      case MethodSignatureChangedInformation.ID:
+        return MethodSignatureChangedInformation.build(object, diagnosticsHandler, lineNumber);
+      case FileNameInformation.ID:
+        return FileNameInformation.build(object, diagnosticsHandler, lineNumber);
+      default:
+        diagnosticsHandler.info(MappingInformationDiagnostics.noHandlerFor(lineNumber, idString));
+        return null;
     }
-    reporter.info(InformationParsingError.noHandlerFor(lineNumber, idString));
-    return null;
   }
 
   static JsonElement getJsonElementFromObject(
-      JsonObject object, Reporter reporter, int lineNumber, String key, String id) {
+      JsonObject object,
+      DiagnosticsHandler diagnosticsHandler,
+      int lineNumber,
+      String key,
+      String id) {
     JsonElement element = object.get(key);
     if (element == null) {
-      reporter.info(
-          InformationParsingError.noKeyForObjectWithId(lineNumber, key, MAPPING_ID_KEY, id));
+      diagnosticsHandler.info(
+          MappingInformationDiagnostics.noKeyForObjectWithId(lineNumber, key, MAPPING_ID_KEY, id));
     }
     return element;
   }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
new file mode 100644
index 0000000..6cc0b28
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MappingInformationDiagnostics.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2019, 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.naming.mappinginformation;
+
+import com.android.tools.r8.Diagnostic;
+import com.android.tools.r8.Keep;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.position.Position;
+import com.android.tools.r8.position.TextPosition;
+
+@Keep
+public class MappingInformationDiagnostics implements Diagnostic {
+
+  private final String message;
+  private final Position position;
+
+  @Override
+  public Origin getOrigin() {
+    return Origin.unknown();
+  }
+
+  @Override
+  public Position getPosition() {
+    return position;
+  }
+
+  @Override
+  public String getDiagnosticMessage() {
+    return message;
+  }
+
+  private MappingInformationDiagnostics(String message, Position position) {
+    this.message = message;
+    this.position = position;
+  }
+
+  static MappingInformationDiagnostics noHandlerFor(int lineNumber, String value) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not find a handler for %s", value),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics noKeyInJson(int lineNumber, String key) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not locate '%s' in the JSON object", key),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics notValidJson(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Not valid JSON", new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics notValidString(int lineNumber, String key) {
+    return new MappingInformationDiagnostics(
+        String.format("The value of '%s' is not a valid string in the JSON object", key),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics tooManyInformationalParameters(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "More informational parameters than actual parameters for method signature",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics noKeyForObjectWithId(
+      int lineNumber, String key, String mappingKey, String mappingValue) {
+    return new MappingInformationDiagnostics(
+        String.format("Could not find '%s' for object with %s '%s'", key, mappingKey, mappingValue),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics invalidValueForObjectWithId(
+      int lineNumber, String mappingKey, String mappingValue) {
+    return new MappingInformationDiagnostics(
+        String.format(
+            "Could not decode the information for the object with %s '%s'",
+            mappingKey, mappingValue),
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics tooManyEntriesForParameterInformation(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Parameter information do not have 1 or 2 entries",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  static MappingInformationDiagnostics invalidParameterInformationObject(int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "Parameter information is not an index and a string representation of a type",
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+
+  public static MappingInformationDiagnostics notAllowedCombination(
+      String className,
+      String renamedClassName,
+      MappingInformation one,
+      MappingInformation other,
+      int lineNumber) {
+    return new MappingInformationDiagnostics(
+        "The mapping '"
+            + one.serialize()
+            + "' is not allowed in combination with '"
+            + other.serialize()
+            + "' in the mapping for "
+            + className
+            + " -> "
+            + renamedClassName,
+        new TextPosition(1, lineNumber, TextPosition.UNKNOWN_COLUMN));
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java
index 32978ae..8bb023d 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/MethodSignatureChangedInformation.java
@@ -4,9 +4,14 @@
 
 package com.android.tools.r8.naming.mappinginformation;
 
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.invalidParameterInformationObject;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.invalidValueForObjectWithId;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.tooManyEntriesForParameterInformation;
+import static com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics.tooManyInformationalParameters;
+
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.utils.Reporter;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
@@ -58,12 +63,18 @@
   }
 
   @Override
+  public boolean allowOther(MappingInformation information) {
+    return !information.isMethodSignatureChangedInformation();
+  }
+
+  @Override
   public Signature getSignature() {
     return signature;
   }
 
   @Override
-  public Signature apply(Signature originalSignature, String renamedName, Reporter reporter) {
+  public Signature apply(
+      Signature originalSignature, String renamedName, DiagnosticsHandler diagnosticsHandler) {
     if (originalSignature == null || !originalSignature.isMethodSignature()) {
       assert false : "Should only call apply for method signature";
       return originalSignature;
@@ -74,7 +85,7 @@
     int numberOfArgumentsRemoved = getNumberOfArgumentsRemoved();
     if (numberOfArgumentsRemoved > parameters.length) {
       // The mapping information is not up to date with the current signature.
-      reporter.warning(InformationParsingError.tooManyInformationalParameters(getLineNumber()));
+      diagnosticsHandler.warning(tooManyInformationalParameters(getLineNumber()));
       return new MethodSignature(renamedName, type, parameters);
     }
     String[] newParameters = new String[parameters.length - numberOfArgumentsRemoved];
@@ -86,7 +97,7 @@
       } else {
         if (insertIndex >= newParameters.length) {
           // The mapping information is not up to date with the current signature.
-          reporter.warning(InformationParsingError.tooManyInformationalParameters(getLineNumber()));
+          diagnosticsHandler.warning(tooManyInformationalParameters(getLineNumber()));
           return new MethodSignature(renamedName, type, parameters);
         } else if (argInfo == null) {
           // Unchanged, take current parameter.
@@ -101,7 +112,7 @@
   }
 
   @Override
-  public boolean hasChangedArguments() {
+  public boolean isMethodSignatureChangedInformation() {
     return true;
   }
 
@@ -134,7 +145,7 @@
   }
 
   @Override
-  public MethodSignatureChangedInformation asArgumentsChangedInformation() {
+  public MethodSignatureChangedInformation asMethodSignatureChangedInformation() {
     return this;
   }
 
@@ -151,15 +162,16 @@
     this.receiver = hasReceiver;
   }
 
-  public static MappingInformation build(JsonObject object, Reporter reporter, int lineNumber) {
+  public static MappingInformation build(
+      JsonObject object, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
     try {
       JsonElement returnTypeElement =
-          getJsonElementFromObject(object, reporter, lineNumber, RETURN_TYPE_KEY, ID);
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, RETURN_TYPE_KEY, ID);
       JsonElement receiverElement =
-          getJsonElementFromObject(object, reporter, lineNumber, RECEIVER_KEY, ID);
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, RECEIVER_KEY, ID);
       JsonElement argsElement =
-          getJsonElementFromObject(object, reporter, lineNumber, PARAMS_KEY, ID);
-      MethodSignature signature = getMethodSignature(object, ID, reporter, lineNumber);
+          getJsonElementFromObject(object, diagnosticsHandler, lineNumber, PARAMS_KEY, ID);
+      MethodSignature signature = getMethodSignature(object, ID, diagnosticsHandler, lineNumber);
       if (signature == null
           || returnTypeElement == null
           || receiverElement == null
@@ -174,7 +186,7 @@
       for (int i = 0; i < argumentsArray.size(); i++) {
         args[i] =
             ParameterInformation.fromJsonArray(
-                argumentsArray.get(i).getAsJsonArray(), reporter, lineNumber);
+                argumentsArray.get(i).getAsJsonArray(), diagnosticsHandler, lineNumber);
       }
       return new MethodSignatureChangedInformation(
           signature,
@@ -183,8 +195,7 @@
           args,
           lineNumber);
     } catch (UnsupportedOperationException | IllegalStateException ignored) {
-      reporter.info(
-          InformationParsingError.invalidValueForObjectWithId(lineNumber, MAPPING_ID_KEY, ID));
+      diagnosticsHandler.info(invalidValueForObjectWithId(lineNumber, MAPPING_ID_KEY, ID));
       return null;
     }
   }
@@ -207,11 +218,11 @@
     }
 
     static ParameterInformation fromJsonArray(
-        JsonArray argumentInfo, Reporter reporter, int lineNumber) {
+        JsonArray argumentInfo, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
       assert argumentInfo != null;
       try {
         if (argumentInfo.size() > 2) {
-          reporter.info(InformationParsingError.tooManyEntriesForParameterInformation(lineNumber));
+          diagnosticsHandler.info(tooManyEntriesForParameterInformation(lineNumber));
           return null;
         }
         int index = argumentInfo.get(0).getAsInt();
@@ -222,7 +233,7 @@
           return new ParameterInformation(index, argumentInfo.get(1).getAsString());
         }
       } catch (UnsupportedOperationException | IllegalStateException ignored) {
-        reporter.info(InformationParsingError.invalidParameterInformationObject(lineNumber));
+        diagnosticsHandler.info(invalidParameterInformationObject(lineNumber));
         return null;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java b/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java
index 3659a6f..aba33bb 100644
--- a/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java
+++ b/src/main/java/com/android/tools/r8/naming/mappinginformation/SignatureMappingInformation.java
@@ -4,9 +4,9 @@
 
 package com.android.tools.r8.naming.mappinginformation;
 
+import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.MemberNaming.Signature;
-import com.android.tools.r8.utils.Reporter;
 import com.google.gson.JsonArray;
 import com.google.gson.JsonElement;
 import com.google.gson.JsonObject;
@@ -31,16 +31,8 @@
 
   public abstract Signature getSignature();
 
-  public boolean hasChangedArguments() {
-    return false;
-  }
-
-  public MethodSignatureChangedInformation asArgumentsChangedInformation() {
-    return null;
-  }
-
   public abstract Signature apply(
-      Signature originalSignature, String renamedName, Reporter reporter);
+      Signature originalSignature, String renamedName, DiagnosticsHandler diagnosticsHandler);
 
   JsonObject serializeMethodSignature(JsonObject object, MethodSignature signature) {
     JsonArray signatureArr = new JsonArray();
@@ -54,9 +46,9 @@
   }
 
   static MethodSignature getMethodSignature(
-      JsonObject object, String id, Reporter reporter, int lineNumber) {
+      JsonObject object, String id, DiagnosticsHandler diagnosticsHandler, int lineNumber) {
     JsonElement signatureElement =
-        getJsonElementFromObject(object, reporter, lineNumber, SIGNATURE_KEY, id);
+        getJsonElementFromObject(object, diagnosticsHandler, lineNumber, SIGNATURE_KEY, id);
     if (signatureElement == null || !signatureElement.isJsonArray()) {
       return null;
     }
diff --git a/src/main/java/com/android/tools/r8/retrace/Retrace.java b/src/main/java/com/android/tools/r8/retrace/Retrace.java
index 3d59b8b..0cb273e 100644
--- a/src/main/java/com/android/tools/r8/retrace/Retrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/Retrace.java
@@ -138,7 +138,8 @@
       Timing timing = Timing.create("R8 retrace", command.printMemory());
       timing.begin("Read proguard map");
       ClassNameMapper classNameMapper =
-          ClassNameMapper.mapperFromString(command.proguardMapProducer.get());
+          ClassNameMapper.mapperFromString(
+              command.proguardMapProducer.get(), command.diagnosticsHandler);
       timing.end();
       RetraceBase retraceBase = RetraceBaseImpl.create(classNameMapper);
       RetraceCommandLineResult result;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBase.java b/src/main/java/com/android/tools/r8/retrace/RetraceBase.java
index 2874fc8..ee7d7ab 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBase.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBase.java
@@ -19,11 +19,5 @@
 
   RetraceTypeResult retrace(TypeReference typeReference);
 
-  String retraceSourceFile(ClassReference classReference, String sourceFile);
-
-  String retraceSourceFile(
-      ClassReference classReference,
-      String sourceFile,
-      ClassReference retracedClassReference,
-      boolean hasRetraceResult);
+  RetraceSourceFileResult retraceSourceFile(ClassReference classReference, String sourceFile);
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
index 9a68706..1561457 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceBaseImpl.java
@@ -10,15 +10,9 @@
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
 import com.android.tools.r8.utils.Box;
-import com.google.common.collect.Sets;
-import com.google.common.io.Files;
-import java.util.Set;
 
 public class RetraceBaseImpl implements RetraceBase {
 
-  private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
-      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source");
-
   private final ClassNameMapper classNameMapper;
 
   private RetraceBaseImpl(ClassNameMapper classNameMapper) {
@@ -46,10 +40,11 @@
   }
 
   @Override
-  public String retraceSourceFile(ClassReference classReference, String sourceFile) {
-    Box<String> retracedSourceFile = new Box<>();
+  public RetraceSourceFileResult retraceSourceFile(
+      ClassReference classReference, String sourceFile) {
+    Box<RetraceSourceFileResult> retracedSourceFile = new Box<>();
     retrace(classReference)
-        .forEach(element -> retracedSourceFile.set(element.retraceSourceFile(sourceFile, this)));
+        .forEach(element -> retracedSourceFile.set(element.retraceSourceFile(sourceFile)));
     return retracedSourceFile.get();
   }
 
@@ -57,42 +52,4 @@
   public RetraceTypeResult retrace(TypeReference typeReference) {
     return new RetraceTypeResult(typeReference, this);
   }
-
-  @Override
-  public String retraceSourceFile(
-      ClassReference obfuscatedClass,
-      String sourceFile,
-      ClassReference retracedClassReference,
-      boolean hasRetraceResult) {
-    boolean fileNameProbablyChanged =
-        hasRetraceResult
-            && !retracedClassReference.getTypeName().startsWith(obfuscatedClass.getTypeName());
-    if (!UNKNOWN_SOURCEFILE_NAMES.contains(sourceFile) && !fileNameProbablyChanged) {
-      // We have no new information, only rewrite filename if it is unknown.
-      // PG-retrace will always rewrite the filename, but that seems a bit to harsh to do.
-      return sourceFile;
-    }
-    if (!hasRetraceResult) {
-      // We have no mapping but but the file name is unknown, so the best we can do is take the
-      // name of the obfuscated clazz.
-      assert obfuscatedClass.getTypeName().equals(retracedClassReference.getTypeName());
-      return getClassSimpleName(obfuscatedClass.getTypeName()) + ".java";
-    }
-    String newFileName = getClassSimpleName(retracedClassReference.getTypeName());
-    String extension = Files.getFileExtension(sourceFile);
-    if (extension.isEmpty()) {
-      extension = "java";
-    }
-    return newFileName + "." + extension;
-  }
-
-  private static String getClassSimpleName(String clazz) {
-    int lastIndexOfPeriod = clazz.lastIndexOf('.');
-    // Check if we can find a subclass separator.
-    int endIndex = clazz.lastIndexOf('$');
-    if (lastIndexOfPeriod > endIndex || endIndex < 0) {
-      endIndex = clazz.length();
-    }
-    return clazz.substring(lastIndexOfPeriod + 1, endIndex);
-  }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
index 25505fe..a631ddf 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceClassResult.java
@@ -4,10 +4,14 @@
 
 package com.android.tools.r8.retrace;
 
+import static com.android.tools.r8.naming.MemberNaming.NoSignature.NO_SIGNATURE;
+import static com.android.tools.r8.retrace.RetraceUtils.synthesizeFileName;
+
 import com.android.tools.r8.Keep;
 import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.ClassNamingForNameMapper.MappedRangesOfName;
 import com.android.tools.r8.naming.MemberNaming;
+import com.android.tools.r8.naming.mappinginformation.MappingInformation;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.Reference;
 import com.android.tools.r8.retrace.RetraceClassResult.Element;
@@ -122,9 +126,26 @@
       return classResult;
     }
 
-    public String retraceSourceFile(String fileName, RetraceBase retraceBase) {
-      return retraceBase.retraceSourceFile(
-          classResult.obfuscatedReference, fileName, classReference, mapper != null);
+    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+      if (mapper != null && mapper.getAdditionalMappings().size() > 0) {
+        List<MappingInformation> mappingInformations =
+            mapper.getAdditionalMappings().get(NO_SIGNATURE);
+        if (mappingInformations != null) {
+          for (MappingInformation mappingInformation : mappingInformations) {
+            if (mappingInformation.isFileNameInformation()) {
+              return new RetraceSourceFileResult(
+                  mappingInformation.asFileNameInformation().getFileName(), false);
+            }
+          }
+        }
+      }
+      return new RetraceSourceFileResult(
+          synthesizeFileName(
+              classReference.getTypeName(),
+              classResult.obfuscatedReference.getTypeName(),
+              sourceFile,
+              mapper != null),
+          true);
     }
 
     public RetraceFieldResult lookupField(String fieldName) {
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
index f0719ea..ff1af91 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceMethodResult.java
@@ -180,5 +180,9 @@
       }
       return mappedRange.getFirstLineNumberOfOriginalRange();
     }
+
+    public RetraceSourceFileResult retraceSourceFile(String sourceFile) {
+      return RetraceUtils.getSourceFile(classElement, methodReference.getHolderClass(), sourceFile);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
index f1603f7..e2e78b2 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceRegularExpression.java
@@ -671,20 +671,21 @@
             retracedStrings.add(retraceString);
             continue;
           }
-          String newSourceFile =
-              retraceString.getQualifiedContext() != null
-                  ? retraceBase.retraceSourceFile(
-                      retraceString.classContext.getClassReference(),
-                      fileName,
+          RetraceSourceFileResult sourceFileResult =
+              retraceString.getMethodContext() != null
+                  ? retraceString.getMethodContext().retraceSourceFile(fileName)
+                  : RetraceUtils.getSourceFile(
+                      retraceString.getClassContext(),
                       retraceString.getQualifiedContext(),
-                      true)
-                  : retraceString.classContext.retraceSourceFile(fileName, retraceBase);
+                      fileName);
           retracedStrings.add(
               retraceString
                   .transform()
                   .setSource(fileName)
                   .replaceInString(
-                      newSourceFile, matcher.start(captureGroup), matcher.end(captureGroup))
+                      sourceFileResult.getFilename(),
+                      matcher.start(captureGroup),
+                      matcher.end(captureGroup))
                   .build());
         }
         return retracedStrings;
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
new file mode 100644
index 0000000..4b21be1
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceSourceFileResult.java
@@ -0,0 +1,27 @@
+// Copyright (c) 2020, 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.retrace;
+
+import com.android.tools.r8.Keep;
+
+@Keep
+public class RetraceSourceFileResult {
+
+  private final String filename;
+  private final boolean synthesized;
+
+  RetraceSourceFileResult(String filename, boolean synthesized) {
+    this.filename = filename;
+    this.synthesized = synthesized;
+  }
+
+  public String getFilename() {
+    return filename;
+  }
+
+  public boolean isSynthesized() {
+    return synthesized;
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
index c63a707..719dd44 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceStackTrace.java
@@ -421,7 +421,8 @@
         List<StackTraceLine> lines,
         String classLoaderName) {
       ClassReference classReference = Reference.classFromTypeName(clazz);
-      RetraceMethodResult retraceResult = retraceBase.retrace(classReference).lookupMethod(method);
+      RetraceClassResult classResult = retraceBase.retrace(classReference);
+      RetraceMethodResult retraceResult = classResult.lookupMethod(method);
       if (linePosition != NO_POSITION && linePosition != INVALID_POSITION) {
         retraceResult = retraceResult.narrowByLine(linePosition);
       }
@@ -437,8 +438,7 @@
                     methodReference.getHolderClass().getTypeName(),
                     methodReference.getMethodName(),
                     methodDescriptionFromMethodReference(methodReference, verbose),
-                    retraceBase.retraceSourceFile(
-                        classReference, fileName, methodReference.getHolderClass(), true),
+                    methodElement.retraceSourceFile(fileName).getFilename(),
                     hasLinePosition()
                         ? methodElement.getOriginalLineNumber(linePosition)
                         : linePosition,
diff --git a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
index 81c2272..c82bf36 100644
--- a/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
+++ b/src/main/java/com/android/tools/r8/retrace/RetraceUtils.java
@@ -4,11 +4,18 @@
 
 package com.android.tools.r8.retrace;
 
+import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.google.common.collect.Sets;
+import com.google.common.io.Files;
+import java.util.Set;
 
 public class RetraceUtils {
 
+  private static final Set<String> UNKNOWN_SOURCEFILE_NAMES =
+      Sets.newHashSet("", "SourceFile", "Unknown", "Unknown Source");
+
   public static String methodDescriptionFromMethodReference(
       MethodReference methodReference, boolean verbose) {
     if (!verbose || methodReference.isUnknown()) {
@@ -35,4 +42,64 @@
     sb.append(")");
     return sb.toString();
   }
+
+  public static boolean hasPredictableSourceFileName(String originalClassName, String sourceFile) {
+    String synthesizedSourceFileName = getClassSimpleName(originalClassName) + ".java";
+    return synthesizedSourceFileName.equals(sourceFile);
+  }
+
+  private static String getClassSimpleName(String clazz) {
+    int lastIndexOfPeriod = clazz.lastIndexOf('.');
+    // Check if we can find a subclass separator.
+    int endIndex = clazz.lastIndexOf('$');
+    if (lastIndexOfPeriod > endIndex || endIndex < 0) {
+      endIndex = clazz.length();
+    }
+    return clazz.substring(lastIndexOfPeriod + 1, endIndex);
+  }
+
+  static RetraceSourceFileResult getSourceFile(
+      RetraceClassResult.Element classElement, ClassReference context, String sourceFile) {
+    // For inline frames we do not have the class element associated with it.
+    if (context == null) {
+      return classElement.retraceSourceFile(sourceFile);
+    }
+    if (context.equals(classElement.getClassReference())) {
+      return classElement.retraceSourceFile(sourceFile);
+    } else {
+      return new RetraceSourceFileResult(
+          synthesizeFileName(
+              context.getTypeName(),
+              classElement.getClassReference().getTypeName(),
+              sourceFile,
+              true),
+          true);
+    }
+  }
+
+  public static String synthesizeFileName(
+      String retracedClassName,
+      String minifiedClassName,
+      String sourceFile,
+      boolean hasRetraceResult) {
+    boolean fileNameProbablyChanged =
+        hasRetraceResult && !retracedClassName.startsWith(minifiedClassName);
+    if (!UNKNOWN_SOURCEFILE_NAMES.contains(sourceFile) && !fileNameProbablyChanged) {
+      // We have no new information, only rewrite filename if it is unknown.
+      // PG-retrace will always rewrite the filename, but that seems a bit to harsh to do.
+      return sourceFile;
+    }
+    String extension = Files.getFileExtension(sourceFile);
+    if (extension.isEmpty()) {
+      extension = "java";
+    }
+    if (!hasRetraceResult) {
+      // We have no mapping but but file name is unknown, so the best we can do is take the
+      // name of the obfuscated clazz.
+      assert minifiedClassName.equals(retracedClassName);
+      return getClassSimpleName(minifiedClassName) + "." + extension;
+    }
+    String newFileName = getClassSimpleName(retracedClassName);
+    return newFileName + "." + extension;
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index 83f8ec0..552cc23 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -47,6 +47,8 @@
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.Range;
+import com.android.tools.r8.naming.mappinginformation.FileNameInformation;
+import com.android.tools.r8.retrace.RetraceUtils;
 import com.android.tools.r8.shaking.KeepInfoCollection;
 import com.android.tools.r8.utils.InternalOptions.LineNumberOptimization;
 import com.google.common.base.Suppliers;
@@ -287,6 +289,15 @@
                       originalType.toSourceString(),
                       com.android.tools.r8.position.Position.UNKNOWN));
 
+      // Check if source file should be added to the map
+      if (clazz.sourceFile != null) {
+        String sourceFile = clazz.sourceFile.toString();
+        if (!RetraceUtils.hasPredictableSourceFileName(clazz.toSourceString(), sourceFile)) {
+          Builder builder = onDemandClassNamingBuilder.get();
+          builder.addMappingInformation(FileNameInformation.build(sourceFile));
+        }
+      }
+
       // If the class is renamed add it to the classNamingBuilder.
       addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
 
diff --git a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
index 031b6c8..56fa95c 100644
--- a/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestShrinkerBuilder.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.references.MethodReference;
 import com.android.tools.r8.references.TypeReference;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.Arrays;
@@ -210,15 +211,15 @@
   }
 
   public T addKeepAttributeLineNumberTable() {
-    return addKeepAttributes("LineNumberTable");
+    return addKeepAttributes(ProguardKeepAttributes.LINE_NUMBER_TABLE);
   }
 
   public T addKeepAttributeSourceFile() {
-    return addKeepAttributes("SourceFile");
+    return addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE);
   }
 
   public T addKeepRuntimeVisibleAnnotations() {
-    return addKeepAttributes("RuntimeVisibleAnnotations");
+    return addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS);
   }
 
   public T addKeepAllAttributes() {
diff --git a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
index 2965085..8b68bd2 100644
--- a/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
+++ b/src/test/java/com/android/tools/r8/naming/ProguardMapReaderArgumentsTest.java
@@ -16,7 +16,7 @@
 
 import com.android.tools.r8.TestDiagnosticMessagesImpl;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
-import com.android.tools.r8.naming.mappinginformation.InformationParsingError;
+import com.android.tools.r8.naming.mappinginformation.MappingInformationDiagnostics;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
@@ -67,7 +67,7 @@
                 allOf(
                     diagnosticMessage(containsString("Could not find a handler for bar")),
                     diagnosticPosition(positionLine(8)))))
-        .assertAllInfosMatch(diagnosticType(InformationParsingError.class));
+        .assertAllInfosMatch(diagnosticType(MappingInformationDiagnostics.class));
   }
 
   @Test
@@ -111,7 +111,7 @@
                 allOf(
                     diagnosticMessage(containsString("Could not decode")),
                     diagnosticPosition(positionLine(8)))))
-        .assertAllInfosMatch(diagnosticType(InformationParsingError.class));
+        .assertAllInfosMatch(diagnosticType(MappingInformationDiagnostics.class));
   }
 
   @Test
diff --git a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
index 2d5e31b..d2b2aba 100644
--- a/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
+++ b/src/test/java/com/android/tools/r8/naming/retrace/StackTrace.java
@@ -280,6 +280,10 @@
   }
 
   public StackTrace retrace(String map) {
+    return retrace(map, null);
+  }
+
+  public StackTrace retrace(String map, String regularExpression) {
     class Box {
       List<String> result;
     }
@@ -291,6 +295,7 @@
                 stackTraceLines.stream()
                     .map(line -> line.originalLine)
                     .collect(Collectors.toList()))
+            .setRegularExpression(regularExpression)
             .setRetracedStackTraceConsumer(retraced -> box.result = retraced)
             .build());
     // Keep the original stderr in the retraced stacktrace.
diff --git a/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
new file mode 100644
index 0000000..bbe2d5b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/DuplicateMappingsTest.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, 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.retrace;
+
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+
+import com.android.tools.r8.DiagnosticsMatcher;
+import com.android.tools.r8.PositionMatcher;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessagesImpl;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import com.google.common.collect.ImmutableList;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DuplicateMappingsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public DuplicateMappingsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testSourceFileName() {
+    TestDiagnosticMessagesImpl diagnosticsHandler = new TestDiagnosticMessagesImpl();
+    Retrace.run(
+        RetraceCommand.builder(diagnosticsHandler)
+            .setProguardMapProducer(
+                () ->
+                    StringUtils.lines(
+                        "com.android.tools.r8.retrace.SourceFileTest$ClassWithCustomFileName ->"
+                            + " com.android.tools.r8.retrace.a:",
+                        "# {'id':'sourceFile','fileName':'foobarbaz.java'}",
+                        "# {'id':'sourceFile','fileName':'foobarbaz2.java'}"))
+            .setStackTrace(ImmutableList.of())
+            .setRetracedStackTraceConsumer(
+                strings -> {
+                  // No need to do anything, we are just checking for diagnostics.
+                })
+            .build());
+    diagnosticsHandler
+        .assertWarningsCount(1)
+        .assertWarningsMatch(
+            allOf(
+                DiagnosticsMatcher.diagnosticMessage(containsString("The mapping")),
+                DiagnosticsMatcher.diagnosticMessage(
+                    containsString("is not allowed in combination with")),
+                DiagnosticsMatcher.diagnosticPosition(PositionMatcher.positionLine(3))));
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
new file mode 100644
index 0000000..fb7f280
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/retrace/SourceFileTest.java
@@ -0,0 +1,140 @@
+// Copyright (c) 2020, 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.retrace;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.naming.retrace.StackTrace;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.List;
+import java.util.function.BiConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SourceFileTest extends TestBase {
+
+  private final TestParameters parameters;
+  private final boolean useRegularExpression;
+  private static final String FILE_NAME = "foobarbaz.java";
+
+  @Parameters(name = "{0}, useRegularExpression: {1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  public SourceFileTest(TestParameters parameters, boolean useRegularExpression) {
+    this.parameters = parameters;
+    this.useRegularExpression = useRegularExpression;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Main.class, ClassWithoutCustomFileName.class)
+        .addProgramClassFileData(
+            transformer(ClassWithCustomFileName.class).setSourceFile(FILE_NAME).transform())
+        .run(parameters.getRuntime(), Main.class)
+        .assertFailureWithErrorThatMatches(containsString("Hello World!"))
+        .inspectStackTrace(
+            stackTrace -> {
+              assertEquals(FILE_NAME, stackTrace.getStackTraceLines().get(0).fileName);
+            });
+  }
+
+  @Test
+  public void testR8WithCustomFileName() throws Exception {
+    runTest(
+        false,
+        ((stackTrace, inspector) -> {
+          assertEquals(FILE_NAME, stackTrace.getStackTraceLines().get(0).fileName);
+          assertEquals(
+              1,
+              inspector
+                  .clazz(ClassWithCustomFileName.class)
+                  .getNaming()
+                  .getAdditionalMappings()
+                  .size());
+        }));
+  }
+
+  @Test
+  public void testR8WithoutCustomFileName() throws Exception {
+    runTest(
+        true,
+        ((stackTrace, inspector) -> {
+          assertEquals("SourceFileTest.java", stackTrace.getStackTraceLines().get(0).fileName);
+          assertEquals(
+              0,
+              inspector
+                  .clazz(ClassWithoutCustomFileName.class)
+                  .getNaming()
+                  .getAdditionalMappings()
+                  .size());
+        }));
+  }
+
+  private void runTest(boolean addDummyArg, BiConsumer<StackTrace, CodeInspector> consumer)
+      throws Exception {
+    R8FullTestBuilder r8FullTestBuilder =
+        testForR8(parameters.getBackend())
+            .addProgramClasses(Main.class, ClassWithoutCustomFileName.class)
+            .addProgramClassFileData(
+                transformer(ClassWithCustomFileName.class).setSourceFile(FILE_NAME).transform())
+            .addKeepClassRules(ClassWithoutCustomFileName.class)
+            .enableInliningAnnotations()
+            .addKeepMainRule(Main.class)
+            .setMinApi(parameters.getApiLevel())
+            .addKeepAttributeSourceFile();
+    R8TestRunResult runResult =
+        addDummyArg
+            ? r8FullTestBuilder.run(parameters.getRuntime(), Main.class, "foo")
+            : r8FullTestBuilder.run(parameters.getRuntime(), Main.class);
+    runResult.assertFailureWithErrorThatMatches(containsString("Hello World!"));
+    StackTrace originalStackTrace = runResult.getOriginalStackTrace();
+    StackTrace retracedStackTrace =
+        originalStackTrace.retrace(
+            runResult.proguardMap(),
+            useRegularExpression ? Retrace.DEFAULT_REGULAR_EXPRESSION : null);
+    runResult.inspectFailure(inspector -> consumer.accept(retracedStackTrace, inspector));
+  }
+
+  public static class ClassWithoutCustomFileName {
+
+    @NeverInline
+    public static void foo() {
+      throw new RuntimeException("Hello World!");
+    }
+  }
+
+  public static class ClassWithCustomFileName {
+
+    @NeverInline
+    public static void foo() {
+      throw new RuntimeException("Hello World!");
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      if (args.length == 0) {
+        ClassWithCustomFileName.foo();
+      } else {
+        ClassWithoutCustomFileName.foo();
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 1f1d744..c34dc98 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -266,6 +266,16 @@
         });
   }
 
+  public ClassFileTransformer setSourceFile(String sourceFile) {
+    return addClassTransformer(
+        new ClassTransformer() {
+          @Override
+          public void visitSource(String source, String debug) {
+            super.visitSource(sourceFile, debug);
+          }
+        });
+  }
+
   public ClassFileTransformer setAccessFlags(Consumer<ClassAccessFlags> fn) {
     return addClassTransformer(
         new ClassTransformer() {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 6a04a0a..59c8760 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -7,6 +7,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.references.ClassReference;
 import java.util.List;
 import java.util.function.Consumer;
@@ -177,4 +178,9 @@
   public KotlinClassMetadata getKotlinClassMetadata() {
     return null;
   }
+
+  @Override
+  public ClassNamingForNameMapper getNaming() {
+    return null;
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 84d76a1..b2266ce 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -8,6 +8,7 @@
 
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.naming.ClassNamingForNameMapper;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.ClassReference;
 import com.android.tools.r8.references.MethodReference;
@@ -211,4 +212,6 @@
         Reference.classFromDescriptor(
             descriptor.substring(0, descriptor.length() - 1) + COMPANION_CLASS_NAME_SUFFIX + ";"));
   }
+
+  public abstract ClassNamingForNameMapper getNaming();
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index bd65812..0b1d950 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -403,4 +403,9 @@
     return KotlinClassMetadataReader.toKotlinClassMetadata(
         codeInspector.getFactory().kotlin, annotationSubject.getAnnotation());
   }
+
+  @Override
+  public ClassNamingForNameMapper getNaming() {
+    return naming;
+  }
 }