Merge "Add new "class2greylist" tool."
am: 7f0aa734a3
Change-Id: I7d53d60ea3537530a9e21e2de2f53eb764f7d0b5
This commit is contained in:
33
tools/hiddenapi/class2greylist/Android.bp
Normal file
33
tools/hiddenapi/class2greylist/Android.bp
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
java_library_host {
|
||||||
|
name: "class2greylistlib",
|
||||||
|
srcs: ["src/**/*.java"],
|
||||||
|
static_libs: [
|
||||||
|
"commons-cli-1.2",
|
||||||
|
"apache-bcel",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
java_binary_host {
|
||||||
|
name: "class2greylist",
|
||||||
|
manifest: "src/class2greylist.mf",
|
||||||
|
static_libs: [
|
||||||
|
"class2greylistlib",
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
1
tools/hiddenapi/class2greylist/src/class2greylist.mf
Normal file
1
tools/hiddenapi/class2greylist/src/class2greylist.mf
Normal file
@@ -0,0 +1 @@
|
|||||||
|
Main-Class: com.android.class2greylist.Class2Greylist
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.bcel.Const;
|
||||||
|
import org.apache.bcel.classfile.AnnotationEntry;
|
||||||
|
import org.apache.bcel.classfile.DescendingVisitor;
|
||||||
|
import org.apache.bcel.classfile.ElementValuePair;
|
||||||
|
import org.apache.bcel.classfile.EmptyVisitor;
|
||||||
|
import org.apache.bcel.classfile.Field;
|
||||||
|
import org.apache.bcel.classfile.FieldOrMethod;
|
||||||
|
import org.apache.bcel.classfile.JavaClass;
|
||||||
|
import org.apache.bcel.classfile.Method;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visits a JavaClass instance and pulls out all members annotated with a
|
||||||
|
* specific annotation. The signatures of such members are passed to {@link
|
||||||
|
* Status#greylistEntry(String)}. Any errors result in a call to {@link
|
||||||
|
* Status#error(String)}.
|
||||||
|
*
|
||||||
|
* If the annotation has a property "expectedSignature" the generated signature
|
||||||
|
* will be verified against the one specified there. If it differs, an error
|
||||||
|
* will be generated.
|
||||||
|
*/
|
||||||
|
public class AnnotationVisitor extends EmptyVisitor {
|
||||||
|
|
||||||
|
private static final String EXPECTED_SIGNATURE = "expectedSignature";
|
||||||
|
|
||||||
|
private final JavaClass mClass;
|
||||||
|
private final String mAnnotationType;
|
||||||
|
private final Status mStatus;
|
||||||
|
private final DescendingVisitor mDescendingVisitor;
|
||||||
|
|
||||||
|
public AnnotationVisitor(JavaClass clazz, String annotation, Status d) {
|
||||||
|
mClass = clazz;
|
||||||
|
mAnnotationType = annotation;
|
||||||
|
mStatus = d;
|
||||||
|
mDescendingVisitor = new DescendingVisitor(clazz, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void visit() {
|
||||||
|
mStatus.debug("Visit class %s", mClass.getClassName());
|
||||||
|
mDescendingVisitor.visit();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getClassDescriptor(JavaClass clazz) {
|
||||||
|
// JavaClass.getName() returns the Java-style name (with . not /), so we must fetch
|
||||||
|
// the original class name from the constant pool.
|
||||||
|
return clazz.getConstantPool().getConstantString(
|
||||||
|
clazz.getClassNameIndex(), Const.CONSTANT_Class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitMethod(Method method) {
|
||||||
|
visitMember(method, "L%s;->%s%s");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visitField(Field field) {
|
||||||
|
visitMember(field, "L%s;->%s:%s");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void visitMember(FieldOrMethod member, String signatureFormatString) {
|
||||||
|
JavaClass definingClass = (JavaClass) mDescendingVisitor.predecessor();
|
||||||
|
mStatus.debug("Visit member %s : %s", member.getName(), member.getSignature());
|
||||||
|
for (AnnotationEntry a : member.getAnnotationEntries()) {
|
||||||
|
if (mAnnotationType.equals(a.getAnnotationType())) {
|
||||||
|
mStatus.debug("Method has annotation %s", mAnnotationType);
|
||||||
|
String signature = String.format(Locale.US, signatureFormatString,
|
||||||
|
getClassDescriptor(definingClass), member.getName(), member.getSignature());
|
||||||
|
for (ElementValuePair property : a.getElementValuePairs()) {
|
||||||
|
switch (property.getNameString()) {
|
||||||
|
case EXPECTED_SIGNATURE:
|
||||||
|
String expected = property.getValue().stringifyValue();
|
||||||
|
if (!signature.equals(expected)) {
|
||||||
|
error(definingClass, member,
|
||||||
|
"Expected signature does not match generated:\n"
|
||||||
|
+ "Expected: %s\n"
|
||||||
|
+ "Generated: %s", expected, signature);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mStatus.greylistEntry(signature);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void error(JavaClass clazz, FieldOrMethod member, String message, Object... args) {
|
||||||
|
StringBuilder error = new StringBuilder();
|
||||||
|
error.append(clazz.getSourceFileName())
|
||||||
|
.append(": ")
|
||||||
|
.append(clazz.getClassName())
|
||||||
|
.append(".")
|
||||||
|
.append(member.getName())
|
||||||
|
.append(": ")
|
||||||
|
.append(String.format(Locale.US, message, args));
|
||||||
|
|
||||||
|
mStatus.error(error.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.commons.cli.CommandLine;
|
||||||
|
import org.apache.commons.cli.CommandLineParser;
|
||||||
|
import org.apache.commons.cli.GnuParser;
|
||||||
|
import org.apache.commons.cli.HelpFormatter;
|
||||||
|
import org.apache.commons.cli.OptionBuilder;
|
||||||
|
import org.apache.commons.cli.Options;
|
||||||
|
import org.apache.commons.cli.ParseException;
|
||||||
|
import org.apache.commons.cli.PatternOptionBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build time tool for extracting a list of members from jar files that have the @UsedByApps
|
||||||
|
* annotation, for building the greylist.
|
||||||
|
*/
|
||||||
|
public class Class2Greylist {
|
||||||
|
|
||||||
|
private static final String ANNOTATION_TYPE = "Landroid/annotation/UsedByApps;";
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
Options options = new Options();
|
||||||
|
options.addOption(OptionBuilder
|
||||||
|
.withLongOpt("debug")
|
||||||
|
.hasArgs(0)
|
||||||
|
.withDescription("Enable debug")
|
||||||
|
.create("d"));
|
||||||
|
options.addOption(OptionBuilder
|
||||||
|
.withLongOpt("help")
|
||||||
|
.hasArgs(0)
|
||||||
|
.withDescription("Show this help")
|
||||||
|
.create("h"));
|
||||||
|
|
||||||
|
CommandLineParser parser = new GnuParser();
|
||||||
|
CommandLine cmd;
|
||||||
|
|
||||||
|
try {
|
||||||
|
cmd = parser.parse(options, args);
|
||||||
|
} catch (ParseException e) {
|
||||||
|
System.err.println(e.getMessage());
|
||||||
|
help(options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cmd.hasOption('h')) {
|
||||||
|
help(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] jarFiles = cmd.getArgs();
|
||||||
|
if (jarFiles.length == 0) {
|
||||||
|
System.err.println("Error: no jar files specified.");
|
||||||
|
help(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
Status status = new Status(cmd.hasOption('d'));
|
||||||
|
|
||||||
|
for (String jarFile : jarFiles) {
|
||||||
|
status.debug("Processing jar file %s", jarFile);
|
||||||
|
try {
|
||||||
|
JarReader reader = new JarReader(status, jarFile);
|
||||||
|
reader.stream().forEach(clazz -> new AnnotationVisitor(
|
||||||
|
clazz, ANNOTATION_TYPE, status).visit());
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {
|
||||||
|
status.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (status.ok()) {
|
||||||
|
System.exit(0);
|
||||||
|
} else {
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void help(Options options) {
|
||||||
|
new HelpFormatter().printHelp(
|
||||||
|
"class2greylist path/to/classes.jar [classes2.jar ...]",
|
||||||
|
"Extracts greylist entries from classes jar files given",
|
||||||
|
options, null, true);
|
||||||
|
System.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.apache.bcel.classfile.ClassParser;
|
||||||
|
import org.apache.bcel.classfile.JavaClass;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
import java.util.zip.ZipFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads {@link JavaClass} members from a zip/jar file, providing a stream of them for processing.
|
||||||
|
* Any errors are reported via {@link Status#error(Throwable)}.
|
||||||
|
*/
|
||||||
|
public class JarReader {
|
||||||
|
|
||||||
|
private final Status mStatus;
|
||||||
|
private final String mFileName;
|
||||||
|
private final ZipFile mZipFile;
|
||||||
|
|
||||||
|
public JarReader(Status s, String filename) throws IOException {
|
||||||
|
mStatus = s;
|
||||||
|
mFileName = filename;
|
||||||
|
mZipFile = new ZipFile(mFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private JavaClass openZipEntry(ZipEntry e) {
|
||||||
|
try {
|
||||||
|
mStatus.debug("Reading %s from %s", e.getName(), mFileName);
|
||||||
|
return new ClassParser(mZipFile.getInputStream(e), e.getName()).parse();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
mStatus.error(ioe);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public Stream<JavaClass> stream() {
|
||||||
|
return mZipFile.stream()
|
||||||
|
.filter(zipEntry -> zipEntry.getName().endsWith(".class"))
|
||||||
|
.map(zipEntry -> openZipEntry(zipEntry))
|
||||||
|
.filter(Objects::nonNull);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void close() throws IOException {
|
||||||
|
mZipFile.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* 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 java.util.Locale;
|
||||||
|
|
||||||
|
public class Status {
|
||||||
|
|
||||||
|
// Highlight "Error:" in red.
|
||||||
|
private static final String ERROR = "\u001B[31mError: \u001B[0m";
|
||||||
|
|
||||||
|
private final boolean mDebug;
|
||||||
|
private boolean mHasErrors;
|
||||||
|
|
||||||
|
public Status(boolean debug) {
|
||||||
|
mDebug = debug;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void debug(String msg, Object... args) {
|
||||||
|
if (mDebug) {
|
||||||
|
System.err.println(String.format(Locale.US, msg, args));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void error(Throwable t) {
|
||||||
|
System.err.print(ERROR);
|
||||||
|
t.printStackTrace(System.err);
|
||||||
|
mHasErrors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void error(String message) {
|
||||||
|
System.err.print(ERROR);
|
||||||
|
System.err.println(message);
|
||||||
|
mHasErrors = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void greylistEntry(String signature) {
|
||||||
|
System.out.println(signature);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean ok() {
|
||||||
|
return !mHasErrors;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
tools/hiddenapi/class2greylist/test/Android.mk
Normal file
32
tools/hiddenapi/class2greylist/test/Android.mk
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
LOCAL_PATH := $(call my-dir)
|
||||||
|
|
||||||
|
include $(CLEAR_VARS)
|
||||||
|
|
||||||
|
# Only compile source java files in this apk.
|
||||||
|
LOCAL_SRC_FILES := $(call all-java-files-under, src)
|
||||||
|
|
||||||
|
LOCAL_MODULE := class2greylisttest
|
||||||
|
|
||||||
|
LOCAL_STATIC_JAVA_LIBRARIES := class2greylistlib truth-host-prebuilt mockito-host junit-host
|
||||||
|
|
||||||
|
# tag this module as a cts test artifact
|
||||||
|
LOCAL_COMPATIBILITY_SUITE := general-tests
|
||||||
|
|
||||||
|
include $(BUILD_HOST_JAVA_LIBRARY)
|
||||||
|
|
||||||
|
# Build the test APKs using their own makefiles
|
||||||
|
include $(call all-makefiles-under,$(LOCAL_PATH))
|
||||||
21
tools/hiddenapi/class2greylist/test/AndroidTest.xml
Normal file
21
tools/hiddenapi/class2greylist/test/AndroidTest.xml
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- 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.
|
||||||
|
-->
|
||||||
|
<configuration description="class2greylist tests">
|
||||||
|
<test class="com.android.compatibility.common.tradefed.testtype.JarHostTest" >
|
||||||
|
<option name="jar" value="class2greylisttest.jar" />
|
||||||
|
<option name="runtime-hint" value="1m" />
|
||||||
|
</test>
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,202 @@
|
|||||||
|
/*
|
||||||
|
* 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.javac;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
|
|
||||||
|
import com.android.class2greylist.Status;
|
||||||
|
import com.android.class2greylist.AnnotationVisitor;
|
||||||
|
|
||||||
|
import com.google.common.base.Joiner;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class AnnotationVisitorTest {
|
||||||
|
|
||||||
|
private static final String ANNOTATION = "Lannotation/Anno;";
|
||||||
|
|
||||||
|
private Javac mJavac;
|
||||||
|
@Mock
|
||||||
|
private Status mStatus;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() throws IOException {
|
||||||
|
initMocks(this);
|
||||||
|
mJavac = new Javac();
|
||||||
|
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.Target;",
|
||||||
|
"@Retention(CLASS)",
|
||||||
|
"public @interface Anno {",
|
||||||
|
" String expectedSignature() default \"\";",
|
||||||
|
"}"));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertNoErrors() {
|
||||||
|
verify(mStatus, never()).error(any(Throwable.class));
|
||||||
|
verify(mStatus, never()).error(any(String.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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() {}",
|
||||||
|
"}"));
|
||||||
|
assertThat(mJavac.compile()).isTrue();
|
||||||
|
|
||||||
|
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
|
||||||
|
.visit();
|
||||||
|
|
||||||
|
assertNoErrors();
|
||||||
|
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(mStatus, times(1)).greylistEntry(greylist.capture());
|
||||||
|
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() {}",
|
||||||
|
"}"));
|
||||||
|
assertThat(mJavac.compile()).isTrue();
|
||||||
|
|
||||||
|
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
|
||||||
|
.visit();
|
||||||
|
|
||||||
|
assertNoErrors();
|
||||||
|
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(mStatus, times(1)).greylistEntry(greylist.capture());
|
||||||
|
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;",
|
||||||
|
"}"));
|
||||||
|
assertThat(mJavac.compile()).isTrue();
|
||||||
|
|
||||||
|
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
|
||||||
|
.visit();
|
||||||
|
|
||||||
|
assertNoErrors();
|
||||||
|
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(mStatus, times(1)).greylistEntry(greylist.capture());
|
||||||
|
assertThat(greylist.getValue()).isEqualTo("La/b/Class;->i:I");
|
||||||
|
}
|
||||||
|
|
||||||
|
@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() {}",
|
||||||
|
"}"));
|
||||||
|
assertThat(mJavac.compile()).isTrue();
|
||||||
|
|
||||||
|
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
|
||||||
|
.visit();
|
||||||
|
|
||||||
|
assertNoErrors();
|
||||||
|
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(mStatus, times(1)).greylistEntry(greylist.capture());
|
||||||
|
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() {}",
|
||||||
|
"}"));
|
||||||
|
assertThat(mJavac.compile()).isTrue();
|
||||||
|
|
||||||
|
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
|
||||||
|
.visit();
|
||||||
|
|
||||||
|
verify(mStatus, times(1)).error(any(String.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@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() {}",
|
||||||
|
" }",
|
||||||
|
"}"));
|
||||||
|
assertThat(mJavac.compile()).isTrue();
|
||||||
|
|
||||||
|
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class$Inner"), ANNOTATION,
|
||||||
|
mStatus).visit();
|
||||||
|
|
||||||
|
assertNoErrors();
|
||||||
|
ArgumentCaptor<String> greylist = ArgumentCaptor.forClass(String.class);
|
||||||
|
verify(mStatus, times(1)).greylistEntry(greylist.capture());
|
||||||
|
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() {}",
|
||||||
|
"}"));
|
||||||
|
assertThat(mJavac.compile()).isTrue();
|
||||||
|
|
||||||
|
new AnnotationVisitor(mJavac.getCompiledClass("a.b.Class"), ANNOTATION, mStatus)
|
||||||
|
.visit();
|
||||||
|
|
||||||
|
assertNoErrors();
|
||||||
|
verify(mStatus, never()).greylistEntry(any(String.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
/*
|
||||||
|
* 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.javac;
|
||||||
|
|
||||||
|
import com.google.common.io.Files;
|
||||||
|
|
||||||
|
import org.apache.bcel.classfile.ClassParser;
|
||||||
|
import org.apache.bcel.classfile.JavaClass;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.net.URI;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import javax.tools.DiagnosticCollector;
|
||||||
|
import javax.tools.JavaCompiler;
|
||||||
|
import javax.tools.JavaFileObject;
|
||||||
|
import javax.tools.SimpleJavaFileObject;
|
||||||
|
import javax.tools.StandardJavaFileManager;
|
||||||
|
import javax.tools.StandardLocation;
|
||||||
|
import javax.tools.ToolProvider;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper class for compiling snippets of Java source and providing access to the resulting class
|
||||||
|
* files.
|
||||||
|
*/
|
||||||
|
public class Javac {
|
||||||
|
|
||||||
|
private final JavaCompiler mJavac;
|
||||||
|
private final StandardJavaFileManager mFileMan;
|
||||||
|
private final List<JavaFileObject> mCompilationUnits;
|
||||||
|
private final File mClassOutDir;
|
||||||
|
|
||||||
|
public Javac() throws IOException {
|
||||||
|
mJavac = ToolProvider.getSystemJavaCompiler();
|
||||||
|
mFileMan = mJavac.getStandardFileManager(null, Locale.US, null);
|
||||||
|
mClassOutDir = Files.createTempDir();
|
||||||
|
mFileMan.setLocation(StandardLocation.CLASS_OUTPUT, Arrays.asList(mClassOutDir));
|
||||||
|
mFileMan.setLocation(StandardLocation.CLASS_PATH, Arrays.asList(mClassOutDir));
|
||||||
|
mCompilationUnits = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String classToFileName(String classname) {
|
||||||
|
return classname.replace('.', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
public Javac addSource(String classname, String contents) {
|
||||||
|
JavaFileObject java = new SimpleJavaFileObject(URI.create(
|
||||||
|
String.format("string:///%s.java", classToFileName(classname))),
|
||||||
|
JavaFileObject.Kind.SOURCE
|
||||||
|
){
|
||||||
|
@Override
|
||||||
|
public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
|
||||||
|
return contents;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
mCompilationUnits.add(java);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean compile() {
|
||||||
|
JavaCompiler.CompilationTask task = mJavac.getTask(
|
||||||
|
null,
|
||||||
|
mFileMan,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
mCompilationUnits);
|
||||||
|
return task.call();
|
||||||
|
}
|
||||||
|
|
||||||
|
public InputStream getClassFile(String classname) throws IOException {
|
||||||
|
Iterable<? extends JavaFileObject> objs = mFileMan.getJavaFileObjects(
|
||||||
|
new File(mClassOutDir, String.format("%s.class", classToFileName(classname))));
|
||||||
|
if (!objs.iterator().hasNext()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return objs.iterator().next().openInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
public JavaClass getCompiledClass(String classname) throws IOException {
|
||||||
|
return new ClassParser(getClassFile(classname),
|
||||||
|
String.format("%s.class", classToFileName(classname))).parse();
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user