Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7f0aa734 authored by Mathew Inwood's avatar Mathew Inwood Committed by Gerrit Code Review
Browse files

Merge "Add new "class2greylist" tool."

parents 3e3a6e47 6395690e
Loading
Loading
Loading
Loading
+33 −0
Original line number Diff line number Diff line
//
// 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
Original line number Diff line number Diff line
Main-Class: com.android.class2greylist.Class2Greylist
+118 −0
Original line number Diff line number Diff line
/*
 * 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());
    }

}
+99 −0
Original line number Diff line number Diff line
/*
 * 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);
    }
}
+65 −0
Original line number Diff line number Diff line
/*
 * 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();
    }
}
Loading