| // 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"); |
| } |
| } |