364 lines
13 KiB
Java
364 lines
13 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2007 IBM Corporation.
|
|
* All rights reserved. This program and the accompanying materials
|
|
* are made available under the terms of the Eclipse Public License v1.0
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.eclipse.org/legal/epl-v10.html
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial API and implementation
|
|
*******************************************************************************/
|
|
package com.ibm.wala.ide.util;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Collection;
|
|
import java.util.Collections;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.jar.JarFile;
|
|
import java.util.zip.ZipException;
|
|
|
|
import org.eclipse.core.resources.IProject;
|
|
import org.eclipse.core.resources.IResource;
|
|
import org.eclipse.core.resources.IWorkspace;
|
|
import org.eclipse.core.resources.IWorkspaceRoot;
|
|
import org.eclipse.core.resources.ResourcesPlugin;
|
|
import org.eclipse.core.runtime.CoreException;
|
|
import org.eclipse.core.runtime.IPath;
|
|
import org.eclipse.jdt.core.IClasspathEntry;
|
|
import org.eclipse.osgi.service.resolver.BundleDescription;
|
|
import org.eclipse.osgi.service.resolver.ImportPackageSpecification;
|
|
import org.eclipse.pde.core.plugin.IPluginModelBase;
|
|
import org.eclipse.pde.internal.core.ClasspathUtilCore;
|
|
import org.eclipse.pde.internal.core.PDECore;
|
|
|
|
import com.ibm.wala.classLoader.BinaryDirectoryTreeModule;
|
|
import com.ibm.wala.classLoader.JarFileModule;
|
|
import com.ibm.wala.classLoader.Module;
|
|
import com.ibm.wala.client.AbstractAnalysisEngine;
|
|
import com.ibm.wala.ide.classloader.EclipseSourceDirectoryTreeModule;
|
|
import com.ibm.wala.ipa.callgraph.AnalysisScope;
|
|
import com.ibm.wala.types.ClassLoaderReference;
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
|
import com.ibm.wala.util.collections.MapUtil;
|
|
import com.ibm.wala.util.config.AnalysisScopeReader;
|
|
import com.ibm.wala.util.debug.Assertions;
|
|
|
|
/**
|
|
* Representation of an analysis scope from an Eclipse project.
|
|
*
|
|
* We set up classloaders as follows:
|
|
* <ul>
|
|
* <li>The project being analyzed is in the Application Loader
|
|
* <li>Frameworks, application libraries, and linked projects on which the main project depends are in the Extension loader
|
|
* <li>System libraries are in the primordial loader.
|
|
* <li>All source modules go in a special Source loader. This includes source from linked projects if
|
|
* SOURCE_FOR_PROJ_AND_LINKED_PROJS is specified.
|
|
* </ul>
|
|
*/
|
|
@SuppressWarnings("restriction")
|
|
public abstract class EclipseProjectPath<E, P> {
|
|
|
|
protected abstract P makeProject(IProject p);
|
|
|
|
protected abstract E resolve(E entry);
|
|
|
|
protected abstract void resolveClasspathEntry(P project, E entry, ILoader loader, boolean includeSource, boolean cpeFromMainProject);
|
|
|
|
protected abstract void resolveProjectClasspathEntries(P project, boolean includeSource);
|
|
|
|
|
|
public interface ILoader {
|
|
ClassLoaderReference ref();
|
|
}
|
|
|
|
/**
|
|
* Eclipse projects are modeled with 3 loaders, as described above.
|
|
*/
|
|
public enum Loader implements ILoader {
|
|
APPLICATION(ClassLoaderReference.Application), EXTENSION(ClassLoaderReference.Extension), PRIMORDIAL(
|
|
ClassLoaderReference.Primordial);
|
|
|
|
private ClassLoaderReference ref;
|
|
|
|
Loader(ClassLoaderReference ref) {
|
|
this.ref = ref;
|
|
}
|
|
|
|
@Override
|
|
public ClassLoaderReference ref() {
|
|
return ref;
|
|
}
|
|
}
|
|
|
|
public enum AnalysisScopeType {
|
|
NO_SOURCE, SOURCE_FOR_PROJ, SOURCE_FOR_PROJ_AND_LINKED_PROJS
|
|
}
|
|
|
|
/**
|
|
* names of OSGi bundles already processed.
|
|
*/
|
|
private final Set<String> bundlesProcessed = HashSetFactory.make();
|
|
|
|
// SJF: Intentionally do not use HashMapFactory, since the Loader keys in the following must use
|
|
// identityHashCode. TODO: fix this source of non-determinism?
|
|
protected final Map<ILoader, List<Module>> modules = new HashMap<>();
|
|
|
|
/**
|
|
* Classpath entries that have already been resolved and added to the scope.
|
|
*/
|
|
protected final Collection<E> alreadyResolved = HashSetFactory.make();
|
|
|
|
/**
|
|
* Which source files, if any, should be included in the analysis scope.
|
|
*/
|
|
private final AnalysisScopeType scopeType;
|
|
|
|
protected EclipseProjectPath(AnalysisScopeType scopeType) throws IOException, CoreException {
|
|
this.scopeType = scopeType;
|
|
for (ILoader loader : Loader.values()) {
|
|
MapUtil.findOrCreateList(modules, loader);
|
|
}
|
|
}
|
|
|
|
public EclipseProjectPath create(IProject project) throws CoreException, IOException {
|
|
if (project == null) {
|
|
throw new IllegalArgumentException("null project");
|
|
}
|
|
|
|
boolean includeSource = (scopeType != AnalysisScopeType.NO_SOURCE);
|
|
|
|
resolveProjectClasspathEntries(makeProject(project), includeSource);
|
|
if (isPluginProject(project)) {
|
|
resolvePluginClassPath(project, includeSource);
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
|
|
protected void resolveLibraryPathEntry(ILoader loader, IPath p) {
|
|
File file = makeAbsolute(p).toFile();
|
|
JarFile j;
|
|
try {
|
|
j = new JarFile(file);
|
|
} catch (ZipException z) {
|
|
// a corrupted file. ignore it.
|
|
return;
|
|
} catch (IOException z) {
|
|
// should ignore directories as well..
|
|
return;
|
|
}
|
|
if (isPrimordialJarFile(j)) {
|
|
List<Module> s = MapUtil.findOrCreateList(modules, loader);
|
|
s.add(file.isDirectory() ? (Module) new BinaryDirectoryTreeModule(file) : (Module) new JarFileModule(j));
|
|
}
|
|
}
|
|
|
|
protected void resolveSourcePathEntry(ILoader loader, boolean includeSource, boolean cpeFromMainProject, IPath p, IPath o, IPath[] excludePaths, String fileExtension) {
|
|
if (includeSource) {
|
|
List<Module> s = MapUtil.findOrCreateList(modules, loader);
|
|
s.add(new EclipseSourceDirectoryTreeModule(p, excludePaths, fileExtension));
|
|
} else if (o != null) {
|
|
File output = makeAbsolute(o).toFile();
|
|
List<Module> s = MapUtil.findOrCreateList(modules, cpeFromMainProject ? Loader.APPLICATION : loader);
|
|
s.add(new BinaryDirectoryTreeModule(output));
|
|
}
|
|
}
|
|
|
|
protected void resolveProjectPathEntry(ILoader loader, boolean includeSource, IPath p) {
|
|
IPath projectPath = makeAbsolute(p);
|
|
IWorkspace ws = ResourcesPlugin.getWorkspace();
|
|
IWorkspaceRoot root = ws.getRoot();
|
|
IProject project = (IProject) root.getContainerForLocation(projectPath);
|
|
try {
|
|
P javaProject = makeProject(project);
|
|
if (javaProject != null) {
|
|
if (isPluginProject(project)) {
|
|
resolvePluginClassPath(project, includeSource);
|
|
}
|
|
resolveProjectClasspathEntries(javaProject, scopeType == AnalysisScopeType.SOURCE_FOR_PROJ_AND_LINKED_PROJS ? includeSource : false);
|
|
}
|
|
} catch (CoreException e1) {
|
|
e1.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
Assertions.UNREACHABLE();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* traverse the bundle description for an Eclipse project and populate the analysis scope accordingly
|
|
*/
|
|
private void resolvePluginClassPath(IProject p, boolean includeSource) throws CoreException, IOException {
|
|
IPluginModelBase model = findModel(p);
|
|
if (!model.isInSync() || model.isDisposed()) {
|
|
model.load();
|
|
}
|
|
|
|
BundleDescription bd = model.getBundleDescription();
|
|
|
|
if (bd == null) {
|
|
// temporary debugging code; remove once we figure out what the heck is going on here --MS
|
|
System.err.println("model.isDisposed(): " + model.isDisposed());
|
|
System.err.println("model.isInSync(): " + model.isInSync());
|
|
System.err.println("model.isEnabled(): " + model.isEnabled());
|
|
System.err.println("model.isLoaded(): " + model.isLoaded());
|
|
System.err.println("model.isValid(): " + model.isValid());
|
|
}
|
|
for (int i = 0; i < 3 && bd == null; i++) {
|
|
// Uh oh. bd is null. Go to sleep, cross your fingers, and try again.
|
|
// This is horrible. We can't figure out the race condition yet which causes this to happen.
|
|
try {
|
|
Thread.sleep(5000);
|
|
} catch (InterruptedException e) {
|
|
// whatever.
|
|
}
|
|
bd = findModel(p).getBundleDescription();
|
|
}
|
|
|
|
if (bd == null) {
|
|
throw new IllegalStateException("bundle description was null for " + p);
|
|
}
|
|
resolveBundleDescriptionClassPath(makeProject(p), bd, Loader.APPLICATION, includeSource);
|
|
}
|
|
|
|
/**
|
|
* traverse a bundle description and populate the analysis scope accordingly
|
|
*/
|
|
private void resolveBundleDescriptionClassPath(P project, BundleDescription bd, Loader loader, boolean includeSource) throws CoreException,
|
|
IOException {
|
|
assert bd != null;
|
|
if (alreadyProcessed(bd)) {
|
|
return;
|
|
}
|
|
bundlesProcessed.add(bd.getName());
|
|
|
|
// handle the classpath entries for bd
|
|
ArrayList<IClasspathEntry> l = new ArrayList<>();
|
|
ClasspathUtilCore.addLibraries(findModel(bd), l);
|
|
resolveClasspathEntries(project, l, loader, includeSource, false);
|
|
|
|
// recurse to handle dependencies. put these in the Extension loader
|
|
for (ImportPackageSpecification b : bd.getImportPackages()) {
|
|
resolveBundleDescriptionClassPath(project, b.getBundle(), Loader.EXTENSION, includeSource);
|
|
}
|
|
for (BundleDescription b : bd.getResolvedRequires()) {
|
|
resolveBundleDescriptionClassPath(project, b, Loader.EXTENSION, includeSource);
|
|
}
|
|
for (BundleDescription b : bd.getFragments()) {
|
|
resolveBundleDescriptionClassPath(project, b, Loader.EXTENSION, includeSource);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* have we already processed a particular bundle description?
|
|
*/
|
|
private boolean alreadyProcessed(BundleDescription bd) {
|
|
return bundlesProcessed.contains(bd.getName());
|
|
}
|
|
|
|
/**
|
|
* Is javaProject a plugin project?
|
|
*/
|
|
private static boolean isPluginProject(IProject project) {
|
|
IPluginModelBase model = findModel(project);
|
|
if (model == null) {
|
|
return false;
|
|
}
|
|
if (model.getPluginBase().getId() == null) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* @return true if the given jar file should be handled by the Primordial loader. If false, other provisions should be made to add
|
|
* the jar file to the appropriate component of the AnalysisScope. Subclasses can override this method.
|
|
*/
|
|
protected boolean isPrimordialJarFile(JarFile j) {
|
|
return true;
|
|
}
|
|
|
|
@SuppressWarnings("unchecked")
|
|
protected void resolveClasspathEntries(P project, List l, ILoader loader, boolean includeSource, boolean entriesFromTopLevelProject) {
|
|
for (int i = 0; i < l.size(); i++) {
|
|
resolveClasspathEntry(project, resolve((E)l.get(i)), loader, includeSource, entriesFromTopLevelProject);
|
|
}
|
|
}
|
|
|
|
public static IPath makeAbsolute(IPath p) {
|
|
IPath absolutePath = p;
|
|
if (p.toFile().exists()) {
|
|
return p;
|
|
}
|
|
|
|
IResource resource = ResourcesPlugin.getWorkspace().getRoot().findMember(p);
|
|
if (resource != null && resource.exists()) {
|
|
absolutePath = resource.getLocation();
|
|
}
|
|
return absolutePath;
|
|
}
|
|
|
|
/**
|
|
* Convert this path to a WALA analysis scope
|
|
*
|
|
* @throws IOException
|
|
*/
|
|
public AnalysisScope toAnalysisScope(ClassLoader classLoader, File exclusionsFile) throws IOException {
|
|
AnalysisScope scope = AnalysisScopeReader.readJavaScope(AbstractAnalysisEngine.SYNTHETIC_J2SE_MODEL, exclusionsFile,
|
|
classLoader);
|
|
return toAnalysisScope(scope);
|
|
}
|
|
|
|
public AnalysisScope toAnalysisScope(AnalysisScope scope) {
|
|
|
|
for (ILoader loader : modules.keySet()) {
|
|
for (Module m : modules.get(loader)) {
|
|
scope.addToScope(loader.ref(), m);
|
|
}
|
|
}
|
|
return scope;
|
|
|
|
}
|
|
|
|
public AnalysisScope toAnalysisScope(final File exclusionsFile) throws IOException {
|
|
return toAnalysisScope(getClass().getClassLoader(), exclusionsFile);
|
|
}
|
|
|
|
public AnalysisScope toAnalysisScope() throws IOException {
|
|
return toAnalysisScope(getClass().getClassLoader(), null);
|
|
}
|
|
|
|
public Collection<Module> getModules(ILoader loader, boolean binary) {
|
|
return Collections.unmodifiableCollection(modules.get(loader));
|
|
}
|
|
|
|
@Override
|
|
public String toString() {
|
|
try {
|
|
return toAnalysisScope((File) null).toString();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
return "Error in toString()";
|
|
}
|
|
}
|
|
|
|
private static IPluginModelBase findModel(IProject p) {
|
|
// PluginRegistry is specific to Eclipse 3.3+. Use PDECore for compatibility with 3.2
|
|
// return PluginRegistry.findModel(p);
|
|
return PDECore.getDefault().getModelManager().findModel(p);
|
|
}
|
|
|
|
private static IPluginModelBase findModel(BundleDescription bd) {
|
|
// PluginRegistry is specific to Eclipse 3.3+. Use PDECore for compatibility with 3.2
|
|
// return PluginRegistry.findModel(bd);
|
|
return PDECore.getDefault().getModelManager().findModel(bd);
|
|
}
|
|
}
|