blob: b73cde805c9958a1a8d31bdd4f21ff80ace9e395 [file] [log] [blame]
// Copyright 2018 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.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
/**
* Checks classpaths (given as ClassLoaders) by reading the constant pool of the class file and
* attempting to load every referenced class. If there are some that are unable to be found, it
* stores a helpful error message if it knows where it might find them, and exits the program if it
* can't find the class with any given classpath.
*/
public class ClassPathValidator {
private final Set<String> mClassPathMissingJars = new HashSet<>();
private int mNumClassPathErrors;
static class ClassNotLoadedException extends ClassNotFoundException {
private final String mClassName;
ClassNotLoadedException(String className, Throwable ex) {
super("Couldn't load " + className, ex);
mClassName = className;
}
public String getClassName() {
return mClassName;
}
}
private static void printAndQuit(ClassNotLoadedException e, ClassReader classReader,
boolean verbose) throws ClassNotLoadedException {
System.err.println("Class \"" + e.getClassName()
+ "\" not found on any classpath. Used by class \"" + classReader.getClassName()
+ "\"");
if (verbose) {
throw e;
}
System.exit(1);
}
private static void validateClass(ClassLoader classLoader, String className)
throws ClassNotLoadedException {
if (className.startsWith("[")) {
// Dealing with an array type which isn't encoded nicely in the constant pool.
// For example, [[Lorg/chromium/Class$1;
className = className.substring(className.lastIndexOf('[') + 1);
if (className.charAt(0) == 'L' && className.endsWith(";")) {
className = className.substring(1, className.length() - 1);
} else {
// Bailing out if we have an non-class array type.
// This could be something like [B
return;
}
}
if (className.matches(".*\\bR(\\$\\w+)?$")) {
// Resources in R.java files are not expected to be valid at this stage in the build.
return;
}
if (className.matches("^libcore\\b.*")) {
// libcore exists on devices, but is not included in the Android sdk as it is a private
// API.
return;
}
try {
classLoader.loadClass(className.replace('/', '.'));
} catch (ClassNotFoundException e) {
throw new ClassNotLoadedException(className, e);
} catch (NoClassDefFoundError e) {
// We assume that this is caused by another class that is not going to able to be
// loaded, so we will skip this and let that class fail with ClassNotFoundException.
}
}
/**
* Given a .class file, see if every class referenced in the main class' constant pool can be
* loaded by the given ClassLoader.
*
* @param classReader .class file interface for reading the constant pool.
* @param classLoader classpath you wish to validate.
* @throws ClassNotLoadedException thrown if it can't load a certain class.
*/
private static void validateClassPath(ClassReader classReader, ClassLoader classLoader)
throws ClassNotLoadedException {
char[] charBuffer = new char[classReader.getMaxStringLength()];
// According to the Java spec, the constant pool is indexed from 1 to constant_pool_count -
// 1. See https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
for (int i = 1; i < classReader.getItemCount(); i++) {
int offset = classReader.getItem(i);
// Class entries correspond to 7 in the constant pool
// https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.4
if (offset > 0 && classReader.readByte(offset - 1) == 7) {
validateClass(classLoader, classReader.readUTF8(offset, charBuffer));
}
}
}
public void validateClassPathsAndOutput(ClassReader classReader,
ClassLoader directClassPathClassLoader, ClassLoader fullClassPathClassLoader,
Collection<String> jarsOnlyInFullClassPath, boolean isPrebuilt, boolean verbose)
throws ClassNotLoadedException {
if (isPrebuilt) {
// Prebuilts only need transitive dependencies checked, not direct dependencies.
try {
validateClassPath(classReader, fullClassPathClassLoader);
} catch (ClassNotLoadedException e) {
printAndQuit(e, classReader, verbose);
}
} else {
try {
validateClassPath(classReader, directClassPathClassLoader);
} catch (ClassNotLoadedException e) {
try {
validateClass(fullClassPathClassLoader, e.getClassName());
} catch (ClassNotLoadedException d) {
printAndQuit(d, classReader, verbose);
}
if (verbose) {
System.err.println("Class \"" + e.getClassName()
+ "\" not found in direct dependencies,"
+ " but found in indirect dependiences.");
}
// Iterating through all jars that are in the full classpath but not the direct
// classpath to find which one provides the class we are looking for.
for (String s : jarsOnlyInFullClassPath) {
try {
ClassLoader smallLoader =
ByteCodeProcessor.loadJars(Collections.singletonList(s));
validateClass(smallLoader, e.getClassName());
mClassPathMissingJars.add(s);
mNumClassPathErrors++;
break;
} catch (ClassNotLoadedException f) {
}
}
}
}
}
public int getNumClassPathErrors() {
return mNumClassPathErrors;
}
public Set<String> getClassPathMissingJars() {
return mClassPathMissingJars;
}
}