Merge "Remove class2greylist from frameworks/base." am: 32979f03fe am: 1397d297f0
am: 8636f97bf0
Change-Id: I4bedbf4a52a5caba565b050f5bad06b42a49063d
This commit is contained in:
@@ -1,33 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 +0,0 @@
|
|||||||
Main-Class: com.android.class2greylist.Class2Greylist
|
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,99 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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/UnsupportedAppUsage ;";
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
# 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))
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,202 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,103 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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