/******************************************************************************* * 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<ILoader, List<Module>>(); /** * 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 { assert project != null; 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<IClasspathEntry>(); 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 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 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 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); } }