Add test for signature attribute parsing
Ensure that the signature parser always throws
GenericSignatureFormatError when there is a parsing error or when the
parser action handler throws.
Bug: 80029761
Change-Id: Iec0d29275b05a19df25f71467b90b73fde257916
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
index 0cc0d53..5b549a3 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureParser.java
@@ -75,24 +75,51 @@
}
public void parseClassSignature(String signature) {
- actions.start();
- setInput(signature);
- parseClassSignature();
- actions.stop();
+ try {
+ actions.start();
+ setInput(signature);
+ parseClassSignature();
+ actions.stop();
+ } catch (GenericSignatureFormatError e) {
+ throw e;
+ } catch (Throwable t) {
+ Error e = new GenericSignatureFormatError(
+ "Unknown error parsing generic signature: " + t.getMessage());
+ e.addSuppressed(t);
+ throw e;
+ }
}
public void parseMethodSignature(String signature) {
- actions.start();
- setInput(signature);
- parseMethodTypeSignature();
- actions.stop();
+ try {
+ actions.start();
+ setInput(signature);
+ parseMethodTypeSignature();
+ actions.stop();
+ } catch (GenericSignatureFormatError e) {
+ throw e;
+ } catch (Throwable t) {
+ Error e = new GenericSignatureFormatError(
+ "Unknown error parsing generic signature: " + t.getMessage());
+ e.addSuppressed(t);
+ throw e;
+ }
}
public void parseFieldSignature(String signature) {
- actions.start();
- setInput(signature);
- parseFieldTypeSignature();
- actions.stop();
+ try {
+ actions.start();
+ setInput(signature);
+ parseFieldTypeSignature();
+ actions.stop();
+ } catch (GenericSignatureFormatError e) {
+ throw e;
+ } catch (Throwable t) {
+ Error e = new GenericSignatureFormatError(
+ "Unknown error parsing generic signature: " + t.getMessage());
+ e.addSuppressed(t);
+ throw e;
+ }
}
private void setInput(String input) {
@@ -178,7 +205,7 @@
updateTypeVariableSignature();
break;
default:
- parseError();
+ parseError("Expected L, [ or T", pos);
}
}
@@ -341,15 +368,18 @@
eof = true;
}
} else {
- parseError("Unexpected end of signature");
+ parseError("Unexpected end of signature", pos);
}
}
private void expect(char c) {
+ if (eof) {
+ parseError("Unexpected end of signature", pos);
+ }
if (symbol == c) {
scanSymbol();
} else {
- parseError("Expected " + c);
+ parseError("Expected " + c, pos - 1);
}
}
@@ -369,7 +399,7 @@
// PRE: symbol is the first char of the identifier.
// POST: symbol = the next symbol AFTER the identifier.
private void scanIdentifier() {
- if (!eof) {
+ if (!eof && pos < buffer.length) {
StringBuilder identBuf = new StringBuilder(32);
if (!isStopSymbol(symbol)) {
identBuf.append(symbol);
@@ -399,15 +429,15 @@
parseError();
}
} else {
- parseError("Unexpected end of signature");
+ parseError("Unexpected end of signature", pos);
}
}
private void parseError() {
- parseError("Unexpected");
+ parseError("Unexpected", pos);
}
- private void parseError(String message) {
+ private void parseError(String message, int pos) {
String arrow = CharBuffer.allocate(pos).toString().replace('\0', ' ') + '^';
throw new GenericSignatureFormatError(
message + " at position " + (pos + 1) + "\n" + String.valueOf(buffer) + "\n" + arrow);
diff --git a/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
new file mode 100644
index 0000000..eebb19a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/GenericSignatureParserTest.java
@@ -0,0 +1,472 @@
+// 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) {
+ 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("Succesfully 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 onr 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("Succesfully parsed " + signature.substring(0, i) + " (position " + i +")");
+ }
+ } catch (GenericSignatureFormatError e) {
+ assertTrue("" + i + " Was: " + e.getMessage(), 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)
+ 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) {
+ 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 generic signaure: 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;>;"));
+ failingParseAction(
+ parser -> parser.parseFieldSignature("LOuter$InnerInterface<TU;>.Inner;"));
+ failingParseAction(
+ parser -> parser.parseMethodSignature("(LOuter$InnerInterface<TU;>.Inner;)V"));
+ }
+}