Skip to content

Commit

Permalink
Extend scanning upwards to external superclasses/ifaces/annotations #261
Browse files Browse the repository at this point in the history
  • Loading branch information
lukehutch committed Oct 18, 2018
1 parent 2394a3d commit 8a46a5e
Show file tree
Hide file tree
Showing 13 changed files with 456 additions and 313 deletions.
36 changes: 23 additions & 13 deletions src/main/java/io/github/classgraph/ClassInfo.java
Expand Up @@ -90,7 +90,13 @@ public class ClassInfo extends ScanResultObject implements Comparable<ClassInfo>
* If false, this classfile was matched during scanning (i.e. its classfile contents read), i.e. this class is a
* whitelisted (and non-blacklisted) class in a whitelisted (and non-blacklisted) package.
*/
private boolean isExternalClass;
private boolean isExternalClass = true;

/**
* Set to true when the class is actually scanned (as opposed to just referenced as a superclass, interface or
* annotation of a scanned class).
*/
private boolean isScannedClass;

/**
* The classpath element file (classpath root dir or jar) that this class was found within, or null if this
Expand Down Expand Up @@ -150,15 +156,14 @@ public class ClassInfo extends ScanResultObject implements Comparable<ClassInfo>
ClassInfo() {
}

private ClassInfo(final String name, final int classModifiers, final boolean isExternalClass) {
private ClassInfo(final String name, final int classModifiers) {
this();
this.name = name;
if (name.endsWith(";")) {
// Spot check to make sure class names were parsed from descriptors
throw new RuntimeException("Bad class name");
}
this.modifiers = classModifiers;
this.isExternalClass = isExternalClass;
}

// -------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -254,8 +259,7 @@ private static ClassInfo getOrCreateClassInfo(final String className, final int
final Map<String, ClassInfo> classNameToClassInfo) {
ClassInfo classInfo = classNameToClassInfo.get(className);
if (classInfo == null) {
classNameToClassInfo.put(className,
classInfo = new ClassInfo(className, classModifiers, /* isExternalClass = */ true));
classNameToClassInfo.put(className, classInfo = new ClassInfo(className, classModifiers));
}
classInfo.modifiers |= classModifiers;
if ((classModifiers & ANNOTATION_CLASS_MODIFIER) != 0) {
Expand Down Expand Up @@ -413,20 +417,29 @@ void addAnnotationParamDefaultValues(final AnnotationParameterValueList paramNam
* should be run in single threaded context.
*/
static ClassInfo addScannedClass(final String className, final int classModifiers, final boolean isInterface,
final boolean isAnnotation, final Map<String, ClassInfo> classNameToClassInfo,
final ClasspathElement classpathElement, final ScanSpec scanSpec, final LogNode log) {
final boolean isAnnotation, final boolean isExternalClass,
final Map<String, ClassInfo> classNameToClassInfo, final ClasspathElement classpathElement,
final ScanSpec scanSpec, final LogNode log) {
boolean classEncounteredMultipleTimes = false;
ClassInfo classInfo = classNameToClassInfo.get(className);
if (classInfo == null) {
// This is the first time this class has been seen, add it
classNameToClassInfo.put(className,
classInfo = new ClassInfo(className, classModifiers, /* isExternalClass = */ false));
classNameToClassInfo.put(className, classInfo = new ClassInfo(className, classModifiers));
} else {
if (!classInfo.isExternalClass) {
// Check if the class was scanned more than once
if (classInfo.isScannedClass) {
classEncounteredMultipleTimes = true;
}
}

// Mark the class as scanned
classInfo.isScannedClass = true;

// Mark the class as non-external if it is a whitelisted class
if (isExternalClass == false) {
classInfo.isExternalClass = isExternalClass;
}

// Remember which classpath element (zipfile / classpath root directory / module) the class was found in
final ModuleRef modRef = classpathElement.getClasspathElementModuleRef();
final File file = modRef != null ? null : classpathElement.getClasspathElementFile(log);
Expand Down Expand Up @@ -474,9 +487,6 @@ static ClassInfo addScannedClass(final String className, final int classModifier
classInfo.classLoaders = classLoaderOrder.toArray(new ClassLoader[0]);
}

// Mark the classfile as scanned
classInfo.isExternalClass = false;

// Merge modifiers
classInfo.modifiers |= classModifiers;
classInfo.isInterface |= isInterface;
Expand Down
14 changes: 8 additions & 6 deletions src/main/java/io/github/classgraph/ClassInfoUnlinked.java
Expand Up @@ -42,14 +42,15 @@
* classes. (The cross-linking is done in a separate step to avoid the complexity of dealing with race conditions.)
*/
class ClassInfoUnlinked {
private final String className;
final String className;
private final int classModifiers;
private final boolean isInterface;
private final boolean isAnnotation;
private final boolean isExternalClass;
// Superclass (can be null if no superclass, or if superclass is blacklisted)
private String superclassName;
private List<String> implementedInterfaces;
private AnnotationInfoList classAnnotations;
String superclassName;
List<String> implementedInterfaces;
AnnotationInfoList classAnnotations;
private String fullyQualifiedDefiningMethodName;
private List<SimpleEntry<String, String>> classContainmentEntries;
private AnnotationParameterValueList annotationParamDefaultValues;
Expand All @@ -59,11 +60,12 @@ class ClassInfoUnlinked {
private String typeSignature;

ClassInfoUnlinked(final String className, final int classModifiers, final boolean isInterface,
final boolean isAnnotation, final ClasspathElement classpathElement) {
final boolean isAnnotation, final boolean isExternalClass, final ClasspathElement classpathElement) {
this.className = (className);
this.classModifiers = classModifiers;
this.isInterface = isInterface;
this.isAnnotation = isAnnotation;
this.isExternalClass = isExternalClass;
this.classpathElement = classpathElement;
}

Expand Down Expand Up @@ -124,7 +126,7 @@ public void addAnnotationParamDefaultValue(final AnnotationParameterValue annota
/** Link classes. Not threadsafe, should be run in a single-threaded context. */
void link(final ScanSpec scanSpec, final Map<String, ClassInfo> classNameToClassInfo, final LogNode log) {
final ClassInfo classInfo = ClassInfo.addScannedClass(className, classModifiers, isInterface, isAnnotation,
classNameToClassInfo, classpathElement, scanSpec, log);
isExternalClass, classNameToClassInfo, classpathElement, scanSpec, log);
if (superclassName != null) {
classInfo.addSuperclass(superclassName, classNameToClassInfo);
}
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/io/github/classgraph/ClassfileBinaryParser.java
Expand Up @@ -331,7 +331,7 @@ private Object readAnnotationElementValue() throws IOException {
*/
ClassInfoUnlinked readClassInfoFromClassfileHeader(final ClasspathElement classpathElement,
final String relativePath, final InputStreamOrByteBufferAdapter inputStreamOrByteBuffer,
final ScanSpec scanSpec, final LogNode log) throws IOException {
final boolean isExternalClass, final ScanSpec scanSpec, final LogNode log) throws IOException {

this.inputStreamOrByteBuffer = inputStreamOrByteBuffer;

Expand Down Expand Up @@ -470,7 +470,7 @@ ClassInfoUnlinked readClassInfoFromClassfileHeader(final ClasspathElement classp
// Create holder object for the class information. This is "unlinked", in the sense that it is
// not linked other class info references at this point.
final ClassInfoUnlinked classInfoUnlinked = new ClassInfoUnlinked(className, classModifierFlags,
isInterface, isAnnotation, classpathElement);
isInterface, isAnnotation, isExternalClass, classpathElement);

// Connect class to superclass
classInfoUnlinked.addSuperclass(superclassName);
Expand Down
96 changes: 38 additions & 58 deletions src/main/java/io/github/classgraph/ClasspathElement.java
Expand Up @@ -34,9 +34,9 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentLinkedQueue;

import io.github.classgraph.utils.ClasspathOrModulePathEntry;
import io.github.classgraph.utils.JarUtils;
import io.github.classgraph.utils.LogNode;
import io.github.classgraph.utils.NestedJarHandler;
import io.github.classgraph.utils.WorkQueue;
Expand Down Expand Up @@ -71,13 +71,17 @@ abstract class ClasspathElement {
/** The scan spec. */
final ScanSpec scanSpec;

/** The list of all classpath resources found within whitelisted paths within this classpath element. */
protected List<Resource> fileMatches;
/** The list of all resources found within this classpath element that were whitelisted and not blacklisted. */
protected List<Resource> resourceMatches;

/**
* The list of whitelisted classfiles found within this classpath resource, if scanFiles is true.
*/
protected List<Resource> classfileMatches;
/** The list of all classfiles found within this classpath element that were whitelisted and not blacklisted. */
protected List<Resource> whitelistedClassfileResources;

/** The list of all classfiles found within this classpath element that were not specifically blacklisted. */
protected List<Resource> nonBlacklistedClassfileResources;

/** Map from class name to non-blacklisted resource. */
protected Map<String, Resource> classNameToNonBlacklistedResource;

/** The map from File to last modified timestamp, if scanFiles is true. */
protected Map<File, Long> fileToLastModified;
Expand Down Expand Up @@ -190,22 +194,28 @@ static ClasspathElement newInstance(final ClasspathOrModulePathEntry classpathRe

/** Get the number of classfile matches. */
int getNumClassfileMatches() {
return classfileMatches == null ? 0 : classfileMatches.size();
return whitelistedClassfileResources == null ? 0 : whitelistedClassfileResources.size();
}

// -------------------------------------------------------------------------------------------------------------

/**
* Apply relative path masking within this classpath resource -- remove relative paths that were found in an
* earlier classpath element.
*
* @return the masked classfile resources.
*/
void maskFiles(final int classpathIdx, final HashSet<String> classpathRelativePathsFound, final LogNode log) {
static List<Resource> maskClassfiles(final ScanSpec scanSpec, final int classpathIdx,
final List<Resource> classfileResources, final HashSet<String> classpathRelativePathsFound,
final LogNode log) {
if (!scanSpec.performScan) {
// Should not happen
throw new IllegalArgumentException("performScan is false");
}
// Take the union of classfile and file match relative paths, since matches can be in both lists if a user
// adds a custom file path matcher that matches paths ending in ".class"
final HashSet<String> maskedRelativePaths = new HashSet<>();
for (final Resource res : classfileMatches) {
for (final Resource res : classfileResources) {
// Don't mask module-info.class, since all modules need this classfile to be read
final String getPathRelativeToPackageRoot = res.getPath();
if (!getPathRelativeToPackageRoot.equals("module-info.class")
Expand All @@ -219,65 +229,35 @@ void maskFiles(final int classpathIdx, final HashSet<String> classpathRelativePa
}
if (!maskedRelativePaths.isEmpty()) {
// Replace the lists of matching resources with filtered versions with masked paths removed
final List<Resource> filteredClassfileMatches = new ArrayList<>();
for (final Resource classfileMatch : classfileMatches) {
final String getPathRelativeToPackageRoot = classfileMatch.getPath();
if (!maskedRelativePaths.contains(getPathRelativeToPackageRoot)) {
filteredClassfileMatches.add(classfileMatch);
final List<Resource> maskedClassfileResources = new ArrayList<>();
for (final Resource classfileMatch : classfileResources) {
final String classfileRelativePath = classfileMatch.getPath();
if (!maskedRelativePaths.contains(classfileRelativePath)) {
maskedClassfileResources.add(classfileMatch);
} else {
if (log != null) {
log.log(String.format("%06d-1", classpathIdx),
"Ignoring duplicate (masked) class " + getPathRelativeToPackageRoot
.substring(0, getPathRelativeToPackageRoot.length() - 6).replace('/', '.')
"Ignoring duplicate (masked) class "
+ JarUtils.classfilePathToClassName(classfileRelativePath)
+ " for classpath element " + classfileMatch);
}
}
}
classfileMatches = filteredClassfileMatches;
return maskedClassfileResources;
}
return classfileResources;
}

// -------------------------------------------------------------------------------------------------------------
/** Implement masking for classfiles defined more than once on the classpath. */
void maskClassfiles(final int classpathIdx, final HashSet<String> classpathRelativePathsFound,
final LogNode maskLog) {
// Mask whitelisted classfile resources
ClasspathElement.maskClassfiles(scanSpec, classpathIdx, whitelistedClassfileResources,
classpathRelativePathsFound, maskLog);

/** Parse any classfiles for any whitelisted classes found within this classpath element. */
void parseClassfiles(final int classfileStartIdx, final int classfileEndIdx,
final ConcurrentLinkedQueue<ClassInfoUnlinked> classInfoUnlinked, final LogNode log) throws Exception {
for (int i = classfileStartIdx; i < classfileEndIdx; i++) {
final Resource classfileResource = classfileMatches.get(i);
try {
final LogNode logNode = log == null ? null
: log.log(classfileResource.getPath(), "Parsing classfile " + classfileResource);
// Parse classpath binary format, creating a ClassInfoUnlinked object
final ClassInfoUnlinked thisClassInfoUnlinked = new ClassfileBinaryParser()
.readClassInfoFromClassfileHeader(this, classfileResource.getPath(),
// Open classfile as an InputStream
/* inputStreamOrByteBuffer = */ classfileResource.openOrRead(), //
scanSpec, logNode);
// If class was successfully read, output new ClassInfoUnlinked object
if (thisClassInfoUnlinked != null) {
classInfoUnlinked.add(thisClassInfoUnlinked);
thisClassInfoUnlinked.logTo(logNode);
}
if (logNode != null) {
logNode.addElapsedTime();
}
} catch (final IOException e) {
if (log != null) {
log.log("IOException while attempting to read classfile " + classfileResource + " -- skipping",
e);
}
} catch (final Throwable e) {
if (log != null) {
log.log("Exception while parsing classfile " + classfileResource, e);
}
// Re-throw
throw e;
} finally {
// Close classfile InputStream (and any associated ZipEntry);
// recycle ZipFile or ModuleReaderProxy if applicable
classfileResource.close();
}
}
// Mask all non-blacklisted classfile resources
ClasspathElement.maskClassfiles(scanSpec, classpathIdx, nonBlacklistedClassfileResources,
classpathRelativePathsFound, /* no need to log */ null);
}

// -------------------------------------------------------------------------------------------------------------
Expand Down

0 comments on commit 8a46a5e

Please sign in to comment.