blob: a6d6a16d9f14ee21f766db627734805e73305345 [file] [log] [blame]
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.class2greylist;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static java.util.Collections.emptyMap;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Predicate;
public class UnsupportedAppUsageAnnotationHandlerTest extends AnnotationHandlerTestBase {
private static final String ANNOTATION = "Lannotation/Anno;";
private static final Map<Integer, String> NULL_SDK_MAP;
static {
Map<Integer, String> map = new HashMap<>();
map.put(null, "flag-null");
NULL_SDK_MAP = Collections.unmodifiableMap(map);
}
@Before
public void setup() throws IOException {
mJavac.addSource("annotation.Anno", Joiner.on('\n').join(
"package annotation;",
"import static java.lang.annotation.RetentionPolicy.CLASS;",
"import java.lang.annotation.Retention;",
"import java.lang.annotation.Repeatable;",
"@Retention(CLASS)",
"@Repeatable(Anno.Container.class)",
"public @interface Anno {",
" String expectedSignature() default \"\";",
" int maxTargetSdk() default Integer.MAX_VALUE;",
" String implicitMember() default \"\";",
" @Retention(CLASS)",
" public @interface Container {",
" Anno[] value();",
" }",
"}"));
}
private UnsupportedAppUsageAnnotationHandler createGreylistHandler(
Predicate<UnsupportedAppUsageAnnotationHandler.ClassMember> greylistFilter,
Map<Integer, String> validMaxTargetSdkValues) {
return new UnsupportedAppUsageAnnotationHandler(
mStatus, mConsumer, greylistFilter, validMaxTargetSdkValues);
}
@Test
public void testGreylistMethod() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno",
" public void method() {}",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
}
@Test
public void testGreylistConstructor() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno",
" public Class() {}",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;-><init>()V");
}
@Test
public void testGreylistField() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno",
" public int i;",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I");
}
@Test
public void testGreylistImplicit() throws IOException {
mJavac.addSource("a.b.EnumClass", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"@Anno(implicitMember=\"values()[La/b/EnumClass;\")",
"public enum EnumClass {",
" VALUE",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.EnumClass"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/EnumClass;->values()[La/b/EnumClass;");
}
@Test
public void testGreylistImplicit_Invalid_MissingOnClass() throws IOException {
mJavac.addSource("a.b.EnumClass", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"@Anno",
"public enum EnumClass {",
" VALUE",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.EnumClass"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
ArgumentCaptor<String> format = ArgumentCaptor.forClass(String.class);
verify(mStatus, times(1)).error(format.capture(), any());
// Ensure that the correct error is reported.
assertThat(format.getValue())
.contains("Missing property implicitMember on annotation on class");
}
@Test
public void testGreylistImplicit_Invalid_PresentOnMember() throws IOException {
mJavac.addSource("a.b.EnumClass", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public enum EnumClass {",
" @Anno(implicitMember=\"values()[La/b/EnumClass;\")",
" VALUE",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.EnumClass"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
ArgumentCaptor<String> format = ArgumentCaptor.forClass(String.class);
verify(mStatus, times(1)).error(format.capture(), any());
assertThat(format.getValue())
.contains("Expected annotation with an implicitMember property to be on a class");
}
@Test
public void testGreylistMethodExpectedSignature() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno(expectedSignature=\"La/b/Class;->method()V\")",
" public void method() {}",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method()V");
}
@Test
public void testGreylistMethodExpectedSignatureWrong() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno(expectedSignature=\"La/b/Class;->nomethod()V\")",
" public void method() {}",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
verify(mStatus, times(1)).error(any(), any());
}
@Test
public void testGreylistInnerClassMethod() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" public class Inner {",
" @Anno",
" public void method() {}",
" }",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class$Inner;->method()V");
}
@Test
public void testMethodNotGreylisted() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"public class Class {",
" public void method() {}",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
verify(mConsumer, never()).consume(any(String.class), any(), any());
}
@Test
public void testMethodArgGenerics() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class<T extends String> {",
" @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
" public void method(T arg) {}",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP))
).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
}
@Test
public void testOverrideMethodWithBridge() throws IOException {
mJavac.addSource("a.b.Base", Joiner.on('\n').join(
"package a.b;",
"abstract class Base<T> {",
" protected abstract void method(T arg);",
"}"));
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class<T extends String> extends Base<T> {",
" @Override",
" @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
" public void method(T arg) {}",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
// A bridge method is generated for the above, so we expect 2 greylist entries.
verify(mConsumer, times(2)).consume(greylist.capture(), any(), any());
assertThat(greylist.getAllValues()).containsExactly(
"La/b/Class;->method(Ljava/lang/Object;)V",
"La/b/Class;->method(Ljava/lang/String;)V");
}
@Test
public void testOverridePublicMethodWithBridge() throws IOException {
mJavac.addSource("a.b.Base", Joiner.on('\n').join(
"package a.b;",
"public abstract class Base<T> {",
" public void method(T arg) {}",
"}"));
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class<T extends String> extends Base<T> {",
" @Override",
" @Anno(expectedSignature=\"La/b/Class;->method(Ljava/lang/String;)V\")",
" public void method(T arg) {}",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
// A bridge method is generated for the above, so we expect 2 greylist entries.
verify(mConsumer, times(2)).consume(greylist.capture(), any(), any());
assertThat(greylist.getAllValues()).containsExactly(
"La/b/Class;->method(Ljava/lang/Object;)V",
"La/b/Class;->method(Ljava/lang/String;)V");
}
@Test
public void testBridgeMethodsFromInterface() throws IOException {
mJavac.addSource("a.b.Interface", Joiner.on('\n').join(
"package a.b;",
"public interface Interface {",
" public void method(Object arg);",
"}"));
mJavac.addSource("a.b.Base", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"class Base {",
" @Anno(expectedSignature=\"La/b/Base;->method(Ljava/lang/Object;)V\")",
" public void method(Object arg) {}",
"}"));
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"public class Class extends Base implements Interface {",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Interface"), mStatus, handlerMap)
.visit();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
// A bridge method is generated for the above, so we expect 2 greylist entries.
verify(mConsumer, times(2)).consume(greylist.capture(), any(), any());
assertThat(greylist.getAllValues()).containsExactly(
"La/b/Class;->method(Ljava/lang/Object;)V",
"La/b/Base;->method(Ljava/lang/Object;)V");
}
@Test
public void testPublicBridgeExcluded() throws IOException {
mJavac.addSource("a.b.Base", Joiner.on('\n').join(
"package a.b;",
"public abstract class Base<T> {",
" public void method(T arg) {}",
"}"));
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class<T extends String> extends Base<T> {",
" @Override",
" @Anno",
" public void method(T arg) {}",
"}"));
mJavac.compile();
Set<String> publicApis = Sets.newHashSet(
"La/b/Base;->method(Ljava/lang/Object;)V",
"La/b/Class;->method(Ljava/lang/Object;)V");
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION,
new UnsupportedAppUsageAnnotationHandler(
mStatus,
mConsumer,
publicApis,
NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Base"), mStatus, handlerMap).visit();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
// The bridge method generated for the above, is a public API so should be excluded
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->method(Ljava/lang/String;)V");
}
@Test
public void testVolatileField() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno(expectedSignature=\"La/b/Class;->field:I\")",
" public volatile int field;",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(
member -> !member.isBridgeMethod, // exclude bridge methods
NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
assertNoErrors();
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
verify(mConsumer, times(1)).consume(greylist.capture(), any(), any());
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->field:I");
}
@Test
public void testVolatileFieldWrongSignature() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno(expectedSignature=\"La/b/Class;->wrong:I\")",
" public volatile int field;",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(x -> true, NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
verify(mStatus, times(1)).error(any(), any());
}
@Test
public void testMethodMaxTargetSdk() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno(maxTargetSdk=1)",
" public int field;",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(
x -> true,
ImmutableMap.of(1, "flag1")));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
assertNoErrors();
verify(mConsumer, times(1)).consume(any(), any(), eq(ImmutableSet.of("flag1")));
}
@Test
public void testMethodNoMaxTargetSdk() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno",
" public int field;",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(
x -> true,
NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
assertNoErrors();
verify(mConsumer, times(1)).consume(any(), any(), eq(ImmutableSet.of("flag-null")));
}
@Test
public void testMethodMaxTargetSdkOutOfRange() throws IOException {
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno;",
"public class Class {",
" @Anno(maxTargetSdk=2)",
" public int field;",
"}"));
mJavac.compile();
Map<String, AnnotationHandler> handlerMap =
ImmutableMap.of(ANNOTATION, createGreylistHandler(
x -> true,
NULL_SDK_MAP));
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus, handlerMap).visit();
verify(mStatus, times(1)).error(any(), any());
}
@Test
public void testAnnotationPropertiesIntoMap() throws IOException {
mJavac.addSource("annotation.Anno2", Joiner.on('\n').join(
"package annotation;",
"import static java.lang.annotation.RetentionPolicy.CLASS;",
"import java.lang.annotation.Retention;",
"@Retention(CLASS)",
"public @interface Anno2 {",
" String expectedSignature() default \"\";",
" int maxTargetSdk() default Integer.MAX_VALUE;",
" long trackingBug() default 0;",
"}"));
mJavac.addSource("a.b.Class", Joiner.on('\n').join(
"package a.b;",
"import annotation.Anno2;",
"public class Class {",
" @Anno2(maxTargetSdk=2, trackingBug=123456789)",
" public int field;",
"}"));
mJavac.compile();
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), mStatus,
ImmutableMap.of("Lannotation/Anno2;", createGreylistHandler(x -> true,
ImmutableMap.of(2, "flag2")))
).visit();
assertNoErrors();
ArgumentCaptor<Map<String, String>> properties = ArgumentCaptor.forClass(Map.class);
verify(mConsumer, times(1)).consume(any(), properties.capture(), any());
assertThat(properties.getValue()).containsExactly(
"maxTargetSdk", "2",
"trackingBug", "123456789");
}
}