blob: 4489b7527833b530b809679561681cf698b1e401 [file] [log] [blame]
// 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.naming;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.naming.signature.GenericSignatureAction;
import com.android.tools.r8.naming.signature.GenericSignatureParser;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.lang.reflect.GenericSignatureFormatError;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Supplier;
import org.junit.Test;
public class GenericSignatureParserTest extends TestBase {
private static class ReGenerateGenericSignatureRewriter
implements GenericSignatureAction<String> {
private StringBuilder renamedSignature;
public String getRenamedSignature() {
return renamedSignature.toString();
}
@Override
public void parsedSymbol(char symbol) {
renamedSignature.append(symbol);
}
@Override
public void parsedIdentifier(String identifier) {
renamedSignature.append(identifier);
}
@Override
public String parsedTypeName(String name, ParserPosition parserPosition) {
renamedSignature.append(name);
return name;
}
@Override
public String parsedInnerTypeName(String enclosingType, String name) {
renamedSignature.append(name);
return name;
}
@Override
public void start() {
renamedSignature = new StringBuilder();
}
@Override
public void stop() {
// nothing to do
}
}
public void parseSimpleError(BiConsumer<GenericSignatureParser<String>, String> parse,
Consumer<GenericSignatureFormatError> errorChecker) {
try {
String signature = "X";
GenericSignatureParser<String> parser =
new GenericSignatureParser<>(new ReGenerateGenericSignatureRewriter());
parse.accept(parser, signature);
fail("Successfully parsed " + signature);
} catch (GenericSignatureFormatError e) {
errorChecker.accept(e);
}
}
@Test
public void simpleParseError() {
parseSimpleError(
GenericSignatureParser::parseClassSignature,
e -> assertTrue(e.getMessage().startsWith("Expected L at position 1")));
// TODO(sgjesse): The position 2 reported here is one off.
parseSimpleError(
GenericSignatureParser::parseFieldSignature,
e -> assertTrue(e.getMessage().startsWith("Expected L, [ or T at position 2")));
parseSimpleError(GenericSignatureParser::parseMethodSignature,
e -> assertTrue(e.getMessage().startsWith("Expected ( at position 1")));
}
private void parseSignature(String signature, Set<Integer> validPrefixes,
Consumer<String> parser, ReGenerateGenericSignatureRewriter rewriter) {
for (int i = 0; i < 2; i++) {
parser.accept(signature);
assertEquals(signature, rewriter.getRenamedSignature());
}
for (int i = 1; i < signature.length(); i++) {
try {
if (validPrefixes == null || !validPrefixes.contains(i)) {
parser.accept(signature.substring(0, i));
fail("Successfully parsed " + signature.substring(0, i) + " (position " + i +")");
}
} catch (GenericSignatureFormatError e) {
assertTrue(e.getMessage().contains("at position " + (i + 1)));
}
}
}
private void parseClassSignature(String signature, Set<Integer> validPrefixes) {
ReGenerateGenericSignatureRewriter rewriter = new ReGenerateGenericSignatureRewriter();
GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
parseSignature(signature, validPrefixes, parser::parseClassSignature, rewriter);
}
private void parseFieldSignature(String signature, Set<Integer> validPrefixes) {
ReGenerateGenericSignatureRewriter rewriter = new ReGenerateGenericSignatureRewriter();
GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
parseSignature(signature, validPrefixes, parser::parseFieldSignature, rewriter);
}
private void parseMethodSignature(String signature, Set<Integer> validPrefixes) {
ReGenerateGenericSignatureRewriter rewriter = new ReGenerateGenericSignatureRewriter();
GenericSignatureParser<String> parser = new GenericSignatureParser<>(rewriter);
parseSignature(signature, validPrefixes, parser::parseMethodSignature, rewriter);
}
public void forClassSignatures(BiConsumer<String, Set<Integer>> consumer) {
consumer.accept("Ljava/lang/Object;", null);
consumer.accept("LOuter$InnerInterface<TU;>;", null);
consumer.accept("La;", null);
consumer.accept("La.i;", null);
consumer.accept("La.i.j;", null);
consumer.accept("La/b;", null);
consumer.accept("La/b.i;", null);
consumer.accept("La/b.i.j;", null);
consumer.accept("La/b/c;", null);
consumer.accept("La/b/c.i;", null);
consumer.accept("La/b/c.i.j;", null);
consumer.accept("La$b;", null);
consumer.accept("La$b.i;", null);
consumer.accept("La$b.i.j;", null);
consumer.accept("La$b$c;", null);
consumer.accept("La$b$c.i;", null);
consumer.accept("La$b$c.i.j;", null);
StringBuilder builder = new StringBuilder();
for (int i = 0; i < 3; i++) {
builder.append("TT;");
consumer.accept("La<" + builder.toString() + ">;", null);
consumer.accept("La<" + builder.toString() + ">.i;", null);
consumer.accept("La<" + builder.toString() + ">.i.j;", null);
consumer.accept("La/b<" + builder.toString() + ">;", null);
consumer.accept("La/b<" + builder.toString() + ">.i;", null);
consumer.accept("La/b<" + builder.toString() + ">.i.j;", null);
consumer.accept("La/b/c<" + builder.toString() + ">;", null);
consumer.accept("La/b/c<" + builder.toString() + ">.i;", null);
consumer.accept("La/b/c<" + builder.toString() + ">.i.j;", null);
consumer.accept("La$b<" + builder.toString() + ">;", null);
consumer.accept("La$b<" + builder.toString() + ">.i;", null);
consumer.accept("La$b<" + builder.toString() + ">.i.j;", null);
consumer.accept("La$b$c<" + builder.toString() + ">;", null);
consumer.accept("La$b$c<" + builder.toString() + ">.i;", null);
consumer.accept("La$b$c<" + builder.toString() + ">.i.j;", null);
}
}
public void forBasicTypes(Consumer<String> consumer) {
for (char c : "BCDFIJSZ".toCharArray()) {
consumer.accept(new String(new char[]{c}));
}
}
public void forBasicTypesAndVoid(Consumer<String> consumer) {
forBasicTypes(consumer);
consumer.accept("V");
}
public void forTypeVariableSignatures(BiConsumer<String, Set<Integer>> consumer) {
consumer.accept("TT;", null);
consumer.accept("Ta;", null);
consumer.accept("Tab;", null);
consumer.accept("Tabc;", null);
consumer.accept("Ta-b;", null);
consumer.accept("Ta-b-c;", null);
}
public void forArrayTypeSignatures(BiConsumer<String, Set<Integer>> consumer) {
StringBuilder arrayPrefix = new StringBuilder();
for (int i = 0; i < 3; i++) {
arrayPrefix.append("[");
forBasicTypes(t -> consumer.accept(arrayPrefix.toString() + t, null));
forClassSignatures((x, y) -> {
consumer.accept(arrayPrefix.toString() + x, y);
});
forTypeVariableSignatures((x, y) -> {
consumer.accept(arrayPrefix.toString() + x, y);
});
//consumer.accept();
}
}
public void forClassTypeSignatures(BiConsumer<String, Set<Integer>> consumer) {
// In https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.3.4 (Java 7) it
// says: "If the class bound does not specify a type, it is taken to be Object.". That sentence
// is not present for Java 8 or Java 9. We do test it here, and javac from OpenJDK 8 will also
// produce that for a class that extends Object and implements at least one interface.
// class C<T> { ... }.
consumer.accept("<T:>Ljava/lang/Object;", null);
consumer.accept("<T:Ljava/lang/Object;>Ljava/lang/Object;", null);
// class C<T,U> { ... }.
consumer.accept("<T:U:>Ljava/lang/Object;", null);
consumer.accept("<T:Ljava/lang/Object;U:Ljava/lang/Object;>Ljava/lang/Object;", null);
// class C<T extends AbstractList> { ... }.
consumer.accept("<T:Ljava/util/AbstractList;>Ljava/lang/Object;", null);
// class C<T extends AbstractList<T>> { ... }.
consumer.accept("<T:Ljava/util/AbstractList<TT;>;>Ljava/lang/Object;", null);
// class C<T extends List> { ... }.
consumer.accept("<T::Ljava/util/List;>Ljava/lang/Object;", null);
// class C<T extends List<T>> { ... }.
consumer.accept("<T::Ljava/util/List<TT;>;>Ljava/lang/Object;", null);
// class C<T extends List<T[]>> { ... }.
consumer.accept("<T::Ljava/util/List<[TT;>;>Ljava/lang/Object;", null);
// class C<T extends List<T[][]>> { ... }.
consumer.accept("<T::Ljava/util/List<[[TT;>;>Ljava/lang/Object;", null);
// class C<T extends AbstractList & List> { ... }.
consumer.accept("<T:Ljava/util/AbstractList;:Ljava/util/List;>Ljava/lang/Object;", null);
// class C<T extends AbstractList<T> & List<T>> { ... }.
consumer.accept(
"<T:Ljava/util/AbstractList<TT;>;:Ljava/util/List<TT;>;>Ljava/lang/Object;", null);
// class C<T extends AbstractList<T[]> & List<T[]>> { ... }.
consumer.accept(
"<T:Ljava/util/AbstractList<[TT;>;:Ljava/util/List<[TT;>;>Ljava/lang/Object;", null);
// class C<T extends AbstractList<T[][]> & List<T[][]>> { ... }.
consumer.accept(
"<T:Ljava/util/AbstractList<[[TT;>;:Ljava/util/List<[[TT;>;>Ljava/lang/Object;", null);
// class C<T extends AbstractList & List & Iterator> { ... }.
consumer.accept(
"<T:Ljava/util/AbstractList;:Ljava/util/List;:Ljava/util/Iterator;>Ljava/lang/Object;",
null);
// class C<T extends AbstractList<T> & List<T> & Iterator<T>> { ... }.
consumer.accept(
"<T:Ljava/util/AbstractList<TT;>;:Ljava/util/List<TT;>;:Ljava/util/Iterator<TT;>;>"
+ "Ljava/lang/Object;", null);
// class C<T,U> { ... }.
consumer.accept("<T:U:>Ljava/lang/Object;", null);
consumer.accept("<T:Ljava/lang/Object;U:Ljava/lang/Object;>Ljava/lang/Object;", null);
// class C extends java.util.AbstractList<String>
consumer.accept("Ljava/util/AbstractList<Ljava/lang/String;>;", null);
// class C extends java.util.AbstractList<String> implements List<String>
consumer.accept(
"Ljava/util/AbstractList<Ljava/lang/String;>;Ljava/util/List<Ljava/lang/String;>;",
ImmutableSet.of(44));
// class C extends java.util.AbstractList<String> implements List<String>, Iterator<String>
consumer.accept(
"Ljava/util/AbstractList<Ljava/lang/String;>;"
+ "Ljava/util/List<Ljava/lang/String;>;Ljava/util/Iterator<Ljava/lang/String;>;",
ImmutableSet.of(44, 80));
// class C<T> extends java.util.AbstractList<T>
consumer.accept("<T:Ljava/lang/Object;>Ljava/util/AbstractList<TT;>;", null);
// class C<T> extends java.util.AbstractList<T> implements List<T>
consumer.accept("<T:Ljava/lang/Object;>Ljava/util/AbstractList<TT;>;Ljava/util/List<TT;>;",
ImmutableSet.of(51));
// class C<T> extends java.util.AbstractList<T> implements List<T>, Iterator<T>
consumer.accept("<T:Ljava/lang/Object;>Ljava/util/AbstractList<TT;>"
+ ";Ljava/util/List<TT;>;Ljava/util/Iterator<TT;>;",
ImmutableSet.of(51, 72));
// class Outer<T> {
// class Inner {
// class InnerInner {
// }
// class ExtendsInnerInner extends InnerInner {
// }
// }
// class ExtendsInner extends Inner {
// }
// }
consumer.accept("<T:Ljava/lang/Object;>Ljava/lang/Object;", null); // Outer signature.
// Inner has no signature.
// InnerInner has no signature.
consumer.accept("LOuter<TT;>.Inner.InnerInner;", null); // ExtendsInnerInner signature.
consumer.accept("LOuter<TT;>.Inner;", null); // ExtendsInner signature.
// class Outer<T> {
// class Inner<T> {
// }
// interface InnerInterface<T> {
// }
// abstract class ExtendsInner<U> extends Inner implements InnerInterface<U> {
// }
// }
consumer.accept(
"<U:Ljava/lang/Object;>LOuter<TT;>.Inner<TT;>;LOuter$InnerInterface<TU;>;",
ImmutableSet.of(45));
}
public void forMethodSignatures(BiConsumer<String, Set<Integer>> consumer) {
forBasicTypesAndVoid(t -> consumer.accept("()" + t, null));
forBasicTypesAndVoid(t -> consumer.accept("(BCDFIJSZ)" + t, null));
forBasicTypesAndVoid(t -> consumer.accept("<T:>(BCDFIJSZ)" + t, null));
forBasicTypesAndVoid(t -> consumer.accept("<T:Ljava/util/List;>(BCDFIJSZ)" + t, null));
forBasicTypesAndVoid(t -> consumer.accept("<T:U:>(BCDFIJSZ)" + t, null));
forBasicTypesAndVoid(
t -> consumer.accept("<T:Ljava/util/List;U:Ljava/util/List;>(BCDFIJSZ)" + t, null));
consumer.accept("<T:U:>(Ljava/util/List<TT;>;Ljava/util/Iterator<TT;>;)V", null);
consumer.accept(
"<T:U:>(Ljava/util/List<TT;>;Ljava/util/Iterator<TT;>;)Ljava/util/List<TT;>;", null);
consumer.accept("<T:U:>(La/b/c<TT;>.i<TT;>;)V", null);
consumer.accept("<T:U:>(La/b/c<TT;>.i<TT;>.j<TU;>;)V", null);
consumer.accept("<T:>()La/b/c<TT;>;", null);
consumer.accept("<T:>()La/b/c<TT;>.i<TT;>;", null);
consumer.accept("<T:>()La/b/c<TT;>.i<TT;>.j<TT;>;", null);
}
@Test
public void testClassSignatureGenericClass() {
forClassTypeSignatures(this::parseClassSignature);
}
@Test
public void parseFieldSignature() {
forClassSignatures(this::parseFieldSignature);
forTypeVariableSignatures(this::parseFieldSignature);
forArrayTypeSignatures(this::parseFieldSignature);
}
@Test
public void parseMethodSignature() {
forMethodSignatures(this::parseMethodSignature);
}
private void failingParseAction(
Consumer<GenericSignatureParser<String>> parse, String errorMessageType)
throws Exception {
class ThrowsInParserActionBase<E extends Error> extends ReGenerateGenericSignatureRewriter {
protected Supplier<? extends E> exceptionSupplier;
private ThrowsInParserActionBase(Supplier<? extends E> exceptionSupplier) {
this.exceptionSupplier = exceptionSupplier;
}
}
class ThrowsInParsedSymbol<E extends Error> extends ThrowsInParserActionBase<E> {
private ThrowsInParsedSymbol(Supplier<? extends E> exceptionSupplier) {
super(exceptionSupplier);
}
@Override
public void parsedSymbol(char symbol) {
throw exceptionSupplier.get();
}
}
class ThrowsInParsedIdentifier<E extends Error> extends ThrowsInParserActionBase<E> {
private ThrowsInParsedIdentifier(Supplier<? extends E> exceptionSupplier) {
super(exceptionSupplier);
}
@Override
public void parsedIdentifier(String identifier) {
throw exceptionSupplier.get();
}
}
class ThrowsInParsedTypeName<E extends Error> extends ThrowsInParserActionBase<E> {
private ThrowsInParsedTypeName(Supplier<? extends E> exceptionSupplier) {
super(exceptionSupplier);
}
@Override
public String parsedTypeName(String name, ParserPosition parserPosition) {
throw exceptionSupplier.get();
}
}
class ThrowsInParsedInnerTypeName<E extends Error> extends ThrowsInParserActionBase<E> {
private ThrowsInParsedInnerTypeName(Supplier<? extends E> exceptionSupplier) {
super(exceptionSupplier);
}
@Override
public String parsedInnerTypeName(String enclosingType, String name) {
throw exceptionSupplier.get();
}
}
class ThrowsInStart<E extends Error> extends ThrowsInParserActionBase<E> {
private ThrowsInStart(Supplier<? extends E> exceptionSupplier) {
super(exceptionSupplier);
}
@Override
public void start() {
throw exceptionSupplier.get();
}
}
class ThrowsInStop<E extends Error> extends ThrowsInParserActionBase<E> {
private ThrowsInStop(Supplier<? extends E> exceptionSupplier) {
super(exceptionSupplier);
}
@Override
public void stop() {
throw exceptionSupplier.get();
}
}
List<GenericSignatureAction<String>> throwingActions = ImmutableList.of(
new ThrowsInParsedSymbol<>(() -> new GenericSignatureFormatError("ERROR")),
new ThrowsInParsedSymbol<>(() -> new Error("ERROR")),
new ThrowsInParsedIdentifier<>(() -> new GenericSignatureFormatError("ERROR")),
new ThrowsInParsedIdentifier<>(() -> new Error("ERROR")),
new ThrowsInParsedTypeName<>(() -> new GenericSignatureFormatError("ERROR")),
new ThrowsInParsedTypeName<>(() -> new Error("ERROR")),
new ThrowsInParsedInnerTypeName<>(() -> new GenericSignatureFormatError("ERROR")),
new ThrowsInParsedInnerTypeName<>(() -> new Error("ERROR")),
new ThrowsInStart<>(() -> new GenericSignatureFormatError("ERROR")),
new ThrowsInStart<>(() -> new Error("ERROR")),
new ThrowsInStop<>(() -> new GenericSignatureFormatError("ERROR")),
new ThrowsInStop<>(() -> new Error("ERROR")));
int plainErrorCount = 0;
for (GenericSignatureAction<String> action : throwingActions) {
GenericSignatureParser<String> parser = new GenericSignatureParser<>(action);
try {
// This class signature hits all action callbacks.
parse.accept(parser);
fail("Parse succeeded for " + action.getClass().getSimpleName());
} catch (GenericSignatureFormatError e) {
if (e.getSuppressed().length == 0) {
assertEquals("ERROR", e.getMessage());
} else {
plainErrorCount++;
assertEquals("Unknown error parsing "
+ errorMessageType + " signature: ERROR", e.getMessage());
}
}
}
assertEquals(6, plainErrorCount);
}
@Test
public void failingParseAction() throws Exception {
// These signatures hits all action callbacks.
failingParseAction(parser -> parser.parseClassSignature(
"<U:Ljava/lang/Object;>LOuter<TT;>.Inner;Ljava/util/List<TU;>;"), "class");
failingParseAction(
parser -> parser.parseFieldSignature("LOuter$InnerInterface<TU;>.Inner;"), "field");
failingParseAction(
parser -> parser.parseMethodSignature("(LOuter$InnerInterface<TU;>.Inner;)V"), "method");
}
}