blob: b767f4f08907c57cc5e6ddb05ee876efb24719d9 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package org.chromium.bytecode;
import org.objectweb.asm.ClassReader;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
/**
* Java application that takes in an input jar, performs a series of bytecode
* transformations, and generates an output jar.
*/
class ByteCodeProcessor {
private static final String CLASS_FILE_SUFFIX = ".class";
private static final int BUFFER_SIZE = 16384;
private static boolean sVerbose;
private static boolean sIsPrebuilt;
private static ClassLoader sDirectClassPathClassLoader;
private static ClassLoader sFullClassPathClassLoader;
private static Set<String> sFullClassPathJarPaths;
private static Set<String> sMissingClassesAllowlist;
private static Map<String, String> sJarToGnTarget;
private static ClassPathValidator sValidator;
private static Void processEntry(ZipEntry entry, byte[] data) {
ClassReader reader = new ClassReader(data);
if (sIsPrebuilt) {
sValidator.validateFullClassPath(
reader, sFullClassPathClassLoader, sMissingClassesAllowlist);
} else {
sValidator.validateDirectClassPath(reader, sDirectClassPathClassLoader,
sFullClassPathClassLoader, sFullClassPathJarPaths, sMissingClassesAllowlist,
sVerbose);
}
return null;
}
private static void process(String gnTarget, String inputJarPath)
throws ExecutionException, InterruptedException {
ExecutorService executorService =
Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
try (ZipInputStream inputStream = new ZipInputStream(
new BufferedInputStream(new FileInputStream(inputJarPath)))) {
while (true) {
ZipEntry entry = inputStream.getNextEntry();
if (entry == null) {
break;
}
byte[] data = readAllBytes(inputStream);
executorService.submit(() -> processEntry(entry, data));
}
executorService.shutdown(); // This is essential in order to avoid waiting infinitely.
executorService.awaitTermination(1, TimeUnit.HOURS);
} catch (IOException e) {
throw new RuntimeException(e);
}
if (sValidator.hasErrors()) {
sValidator.printAll(gnTarget, sJarToGnTarget);
System.exit(1);
}
}
private static byte[] readAllBytes(InputStream inputStream) throws IOException {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
int numRead = 0;
byte[] data = new byte[BUFFER_SIZE];
while ((numRead = inputStream.read(data, 0, data.length)) != -1) {
buffer.write(data, 0, numRead);
}
return buffer.toByteArray();
}
/**
* Loads a list of jars and returns a ClassLoader capable of loading all classes found in the
* given jars.
*/
static ClassLoader loadJars(Collection<String> paths) {
URL[] jarUrls = new URL[paths.size()];
int i = 0;
for (String path : paths) {
try {
jarUrls[i++] = new File(path).toURI().toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
return new URLClassLoader(jarUrls);
}
/**
* Extracts a length-encoded list of strings from the arguments, and adds them to |out|. Returns
* the new "next index" to be processed.
*/
private static int parseListArgument(String[] args, int index, Collection<String> out) {
int argLength = Integer.parseInt(args[index++]);
out.addAll(Arrays.asList(Arrays.copyOfRange(args, index, index + argLength)));
return index + argLength;
}
public static void main(String[] args) throws ClassPathValidator.ClassNotLoadedException,
ExecutionException, InterruptedException {
// Invoke this script using //build/android/gyp/bytecode_processor.py
int currIndex = 0;
String gnTarget = args[currIndex++];
String inputJarPath = args[currIndex++];
sVerbose = args[currIndex++].equals("--verbose");
sIsPrebuilt = args[currIndex++].equals("--is-prebuilt");
sMissingClassesAllowlist = new HashSet<>();
currIndex = parseListArgument(args, currIndex, sMissingClassesAllowlist);
ArrayList<String> sdkJarPaths = new ArrayList<>();
currIndex = parseListArgument(args, currIndex, sdkJarPaths);
ArrayList<String> directClassPathJarPaths = new ArrayList<>();
directClassPathJarPaths.add(inputJarPath);
directClassPathJarPaths.addAll(sdkJarPaths);
currIndex = parseListArgument(args, currIndex, directClassPathJarPaths);
sDirectClassPathClassLoader = loadJars(directClassPathJarPaths);
ArrayList<String> fullClassPathJarPaths = new ArrayList<>();
currIndex = parseListArgument(args, currIndex, fullClassPathJarPaths);
ArrayList<String> gnTargets = new ArrayList<>();
parseListArgument(args, currIndex, gnTargets);
sJarToGnTarget = new HashMap<>();
assert fullClassPathJarPaths.size() == gnTargets.size();
for (int i = 0; i < fullClassPathJarPaths.size(); ++i) {
sJarToGnTarget.put(fullClassPathJarPaths.get(i), gnTargets.get(i));
}
// Load all jars that are on the classpath for the input jar for analyzing class
// hierarchy.
sFullClassPathJarPaths = new HashSet<>();
sFullClassPathJarPaths.add(inputJarPath);
sFullClassPathJarPaths.addAll(sdkJarPaths);
sFullClassPathJarPaths.addAll(fullClassPathJarPaths);
sFullClassPathClassLoader = loadJars(sFullClassPathJarPaths);
sFullClassPathJarPaths.removeAll(directClassPathJarPaths);
sValidator = new ClassPathValidator();
process(gnTarget, inputJarPath);
}
}