Merge "Emit warning for duplicate fields and methods"
diff --git a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
index 64ba508..e998206 100644
--- a/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
+++ b/src/main/java/com/android/tools/r8/graph/JarClassFileReader.java
@@ -27,15 +27,21 @@
import com.android.tools.r8.graph.DexValue.DexValueString;
import com.android.tools.r8.graph.DexValue.DexValueType;
import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.FieldSignatureEquivalence;
import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.utils.MethodSignatureEquivalence;
+import com.android.tools.r8.utils.StringDiagnostic;
+import com.google.common.base.Equivalence.Wrapper;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.HashSet;
import java.util.List;
import java.util.Objects;
+import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import org.objectweb.asm.AnnotationVisitor;
@@ -148,8 +154,10 @@
private List<DexAnnotationElement> defaultAnnotations = null;
private final List<DexEncodedField> staticFields = new ArrayList<>();
private final List<DexEncodedField> instanceFields = new ArrayList<>();
+ private final Set<Wrapper<DexField>> fieldSignatures = new HashSet<>();
private final List<DexEncodedMethod> directMethods = new ArrayList<>();
private final List<DexEncodedMethod> virtualMethods = new ArrayList<>();
+ private final Set<Wrapper<DexMethod>> methodSignatures = new HashSet<>();
public CreateDexClassVisitor(
Origin origin,
@@ -380,13 +388,20 @@
public void visitEnd() {
FieldAccessFlags flags = FieldAccessFlags.fromCfAccessFlags(cleanAccessFlags(access));
DexField dexField = parent.application.getField(parent.type, name, desc);
- DexAnnotationSet annotationSet = createAnnotationSet(annotations);
- DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
- DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
- if (flags.isStatic()) {
- parent.staticFields.add(field);
+ Wrapper<DexField> signature = FieldSignatureEquivalence.get().wrap(dexField);
+ if (parent.fieldSignatures.add(signature)) {
+ DexAnnotationSet annotationSet = createAnnotationSet(annotations);
+ DexValue staticValue = flags.isStatic() ? getStaticValue(value, dexField.type) : null;
+ DexEncodedField field = new DexEncodedField(dexField, flags, annotationSet, staticValue);
+ if (flags.isStatic()) {
+ parent.staticFields.add(field);
+ } else {
+ parent.instanceFields.add(field);
+ }
} else {
- parent.instanceFields.add(field);
+ parent.application.options.reporter.warning(
+ new StringDiagnostic(
+ String.format("Field `%s` has multiple definitions", dexField.toSourceString())));
}
}
@@ -603,10 +618,20 @@
annotationsList,
code,
parent.version);
- if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
- parent.directMethods.add(dexMethod);
+ Wrapper<DexMethod> signature = MethodSignatureEquivalence.get().wrap(method);
+ if (parent.methodSignatures.add(signature)) {
+ if (flags.isStatic() || flags.isConstructor() || flags.isPrivate()) {
+ parent.directMethods.add(dexMethod);
+ } else {
+ parent.virtualMethods.add(dexMethod);
+ }
} else {
- parent.virtualMethods.add(dexMethod);
+ internalOptions.reporter.warning(
+ new StringDiagnostic(
+ String.format(
+ "Ignoring an implementation of the method `%s` because it has multiple "
+ + "definitions",
+ method.toSourceString())));
}
if (defaultAnnotation != null) {
parent.addDefaultAnnotation(name, defaultAnnotation);
diff --git a/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
new file mode 100644
index 0000000..93f9084
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/invalid/DuplicateDefinitionsTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2018, 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.invalid;
+
+import static com.android.tools.r8.utils.DexInspectorMatchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.DexInspector;
+import com.android.tools.r8.utils.DexInspector.ClassSubject;
+import com.google.common.collect.ImmutableList;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import org.junit.Test;
+
+public class DuplicateDefinitionsTest extends JasminTestBase {
+
+ @Test
+ public void testDuplicateMethods() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+ ClassBuilder classBuilder = jasminBuilder.addClass("C");
+ classBuilder.addMainMethod(".limit locals 1", ".limit stack 0", "return");
+ classBuilder.addMainMethod(".limit locals 1", ".limit stack 0", "return");
+ classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
+ classBuilder.addVirtualMethod("method", "V", ".limit locals 1", ".limit stack 0", "return");
+
+ // Run D8 and intercept warnings.
+ PrintStream stderr = System.err;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(baos));
+
+ AndroidApp app = compileWithD8(jasminBuilder.build());
+
+ String output = new String(baos.toByteArray(), Charset.defaultCharset());
+ System.setOut(stderr);
+
+ // Check that warnings were emitted.
+ assertThat(
+ output,
+ containsString(
+ "Ignoring an implementation of the method `void C.main(java.lang.String[])` because "
+ + "it has multiple definitions"));
+ assertThat(
+ output,
+ containsString(
+ "Ignoring an implementation of the method `void C.method()` because "
+ + "it has multiple definitions"));
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz("C");
+ assertThat(clazz, isPresent());
+
+ // There are two direct methods, but only because one is <init>.
+ assertEquals(2, clazz.getDexClass().directMethods().length);
+ assertThat(clazz.method("void", "<init>", ImmutableList.of()), isPresent());
+
+ // There is only one virtual method.
+ assertEquals(1, clazz.getDexClass().virtualMethods().length);
+ }
+
+ @Test
+ public void testDuplicateFields() throws Exception {
+ JasminBuilder jasminBuilder = new JasminBuilder();
+ ClassBuilder classBuilder = jasminBuilder.addClass("C");
+ classBuilder.addField("public", "fld", "LC;", null);
+ classBuilder.addField("public", "fld", "LC;", null);
+ classBuilder.addStaticField("staticFld", "LC;", null);
+ classBuilder.addStaticField("staticFld", "LC;", null);
+
+ // Run D8 and intercept warnings.
+ PrintStream stderr = System.err;
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(baos));
+
+ AndroidApp app = compileWithD8(jasminBuilder.build());
+
+ String output = new String(baos.toByteArray(), Charset.defaultCharset());
+ System.setOut(stderr);
+
+ // Check that warnings were emitted.
+ assertThat(output, containsString("Field `C C.fld` has multiple definitions"));
+ assertThat(output, containsString("Field `C C.staticFld` has multiple definitions"));
+
+ DexInspector inspector = new DexInspector(app);
+ ClassSubject clazz = inspector.clazz("C");
+ assertThat(clazz, isPresent());
+
+ // Redundant fields have been removed.
+ assertEquals(1, clazz.getDexClass().instanceFields().length);
+ assertEquals(1, clazz.getDexClass().staticFields().length);
+ }
+}