Skip to content

Commit

Permalink
Use whitelist model for plugin class loading
Browse files Browse the repository at this point in the history
  • Loading branch information
electrum committed Aug 28, 2016
1 parent bbd19e8 commit 1adb7df
Show file tree
Hide file tree
Showing 2 changed files with 48 additions and 147 deletions.
Expand Up @@ -15,52 +15,44 @@

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;

import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;

import static java.util.Objects.requireNonNull;

class PluginClassLoader
extends URLClassLoader
{
private final List<String> hiddenClasses;
private final List<String> parentFirstClasses;
private final List<String> hiddenResources;
private final List<String> parentFirstResources;

public PluginClassLoader(List<URL> urls,
ClassLoader parent,
Iterable<String> hiddenClasses,
Iterable<String> parentFirstClasses)
private final ClassLoader spiClassLoader;
private final List<String> spiPackages;
private final List<String> spiResources;

public PluginClassLoader(
List<URL> urls,
ClassLoader spiClassLoader,
Iterable<String> spiPackages)
{
this(urls,
parent,
hiddenClasses,
parentFirstClasses,
Iterables.transform(hiddenClasses, PluginClassLoader::classNameToResource),
Iterables.transform(parentFirstClasses, PluginClassLoader::classNameToResource));
spiClassLoader,
spiPackages,
Iterables.transform(spiPackages, PluginClassLoader::classNameToResource));
}

public PluginClassLoader(List<URL> urls,
ClassLoader parent,
Iterable<String> hiddenClasses,
Iterable<String> parentFirstClasses,
Iterable<String> hiddenResources,
Iterable<String> parentFirstResources)
private PluginClassLoader(
List<URL> urls,
ClassLoader spiClassLoader,
Iterable<String> spiPackages,
Iterable<String> spiResources)
{
// child first requires a parent class loader
super(urls.toArray(new URL[urls.size()]), requireNonNull(parent, "parent is null"));
this.hiddenClasses = ImmutableList.copyOf(hiddenClasses);
this.parentFirstClasses = ImmutableList.copyOf(parentFirstClasses);
this.hiddenResources = ImmutableList.copyOf(hiddenResources);
this.parentFirstResources = ImmutableList.copyOf(parentFirstResources);
// use a null parent to prevent delegation to the system class loader
super(urls.toArray(new URL[urls.size()]), null);
this.spiClassLoader = requireNonNull(spiClassLoader, "spiClassLoader is null");
this.spiPackages = ImmutableList.copyOf(spiPackages);
this.spiResources = ImmutableList.copyOf(spiResources);
}

@Override
Expand All @@ -75,35 +67,13 @@ protected Class<?> loadClass(String name, boolean resolve)
return resolveClass(cachedClass, resolve);
}

// If this is not a parent first class, look for the class locally
if (!isParentFirstClass(name)) {
try {
Class<?> clazz = findClass(name);
return resolveClass(clazz, resolve);
}
catch (ClassNotFoundException ignored) {
// not a local class
}
// If this is an SPI class, only check SPI class loader
if (isSpiClass(name)) {
return resolveClass(spiClassLoader.loadClass(name), resolve);
}

// Check parent class loader for parent first or non-hidden classes
if (isParentFirstClass(name) || !isHiddenClass(name)) {
try {
Class<?> clazz = getParent().loadClass(name);
return resolveClass(clazz, resolve);
}
catch (ClassNotFoundException ignored) {
// this parent didn't have the class
}
}

// If this is a parent first class, now look for the class locally
if (isParentFirstClass(name)) {
Class<?> clazz = findClass(name);
return resolveClass(clazz, resolve);
}

throw new ClassNotFoundException(name);
// Look for class locally
return super.loadClass(name, resolve);
}
}

Expand All @@ -115,103 +85,41 @@ private Class<?> resolveClass(Class<?> clazz, boolean resolve)
return clazz;
}

private boolean isParentFirstClass(String name)
{
for (String nonOverridableClass : parentFirstClasses) {
// todo maybe make this more precise and only match base package
if (name.startsWith(nonOverridableClass)) {
return true;
}
}
return false;
}

private boolean isHiddenClass(String name)
{
for (String hiddenClass : hiddenClasses) {
// todo maybe make this more precise and only match base package
if (name.startsWith(hiddenClass)) {
return true;
}
}
return false;
}

@Override
public URL getResource(String name)
{
// If this is not a parent first resource, check local resources first
if (!isParentFirstResource(name)) {
URL url = findResource(name);
if (url != null) {
return url;
}
// If this is an SPI resource, only check SPI class loader
if (isSpiResource(name)) {
return spiClassLoader.getResource(name);
}

// Check parent class loaders
if (!isHiddenResource(name)) {
URL url = getParent().getResource(name);
if (url != null) {
return url;
}
}

// If this is a parent first resource, now check local resources
if (isParentFirstResource(name)) {
URL url = findResource(name);
if (url != null) {
return url;
}
}

return null;
// Look for resource locally
return super.getResource(name);
}

@Override
public Enumeration<URL> getResources(String name)
throws IOException
{
List<Iterator<URL>> resources = new ArrayList<>();

// If this is not a parent first resource, add resources from local urls first
if (!isParentFirstResource(name)) {
Iterator<URL> myResources = Iterators.forEnumeration(findResources(name));
resources.add(myResources);
// If this is an SPI resource, use SPI resources
if (isSpiClass(name)) {
return spiClassLoader.getResources(name);
}

// Add parent resources
if (!isHiddenResource(name)) {
Iterator<URL> parentResources = Iterators.forEnumeration(getParent().getResources(name));
resources.add(parentResources);
}

// If this is a parent first resource, now add resources from local urls
if (isParentFirstResource(name)) {
Iterator<URL> myResources = Iterators.forEnumeration(findResources(name));
resources.add(myResources);
}

return Iterators.asEnumeration(Iterators.concat(resources.iterator()));
// Use local resources
return super.getResources(name);
}

private boolean isParentFirstResource(String name)
private boolean isSpiClass(String name)
{
for (String nonOverridableResource : parentFirstResources) {
if (name.startsWith(nonOverridableResource)) {
return true;
}
}
return false;
// todo maybe make this more precise and only match base package
return spiPackages.stream().anyMatch(name::startsWith);
}

private boolean isHiddenResource(String name)
private boolean isSpiResource(String name)
{
for (String hiddenResource : hiddenResources) {
if (name.startsWith(hiddenResource)) {
return true;
}
}
return false;
// todo maybe make this more precise and only match base package
return spiResources.stream().anyMatch(name::startsWith);
}

private static String classNameToResource(String className)
Expand Down
Expand Up @@ -64,18 +64,11 @@
@ThreadSafe
public class PluginManager
{
private static final List<String> HIDDEN_CLASSES = ImmutableList.<String>builder()
.add("com.fasterxml.jackson")
.add("org.slf4j")
.build();

private static final ImmutableList<String> PARENT_FIRST_CLASSES = ImmutableList.<String>builder()
.add("com.facebook.presto")
.add("com.fasterxml.jackson.annotation")
.add("io.airlift.slice")
.add("io.airlift.units")
.add("javax.annotation")
.add("java.")
private static final ImmutableList<String> SPI_PACKAGES = ImmutableList.<String>builder()
.add("com.facebook.presto.spi.")
.add("com.fasterxml.jackson.annotation.")
.add("io.airlift.slice.")
.add("io.airlift.units.")
.build();

private static final Logger log = Logger.get(PluginManager.class);
Expand Down Expand Up @@ -303,7 +296,7 @@ private URLClassLoader createClassLoader(List<Artifact> artifacts, String name)
private URLClassLoader createClassLoader(List<URL> urls)
{
ClassLoader parent = getClass().getClassLoader();
return new PluginClassLoader(urls, parent, HIDDEN_CLASSES, PARENT_FIRST_CLASSES);
return new PluginClassLoader(urls, parent, SPI_PACKAGES);
}

private static List<File> listFiles(File installedPluginsDir)
Expand Down

0 comments on commit 1adb7df

Please sign in to comment.