299 lines
12 KiB
Java
299 lines
12 KiB
Java
/*******************************************************************************
|
|
* Copyright (c) 2002 - 2006 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.util.scope;
|
|
|
|
import java.io.File;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.util.Enumeration;
|
|
import java.util.Iterator;
|
|
import java.util.Set;
|
|
import java.util.jar.Attributes;
|
|
import java.util.jar.JarEntry;
|
|
import java.util.jar.JarFile;
|
|
import java.util.jar.Manifest;
|
|
|
|
import org.osgi.framework.Constants;
|
|
|
|
import com.ibm.wala.classLoader.IClass;
|
|
import com.ibm.wala.classLoader.IMethod;
|
|
import com.ibm.wala.ipa.callgraph.AnalysisScope;
|
|
import com.ibm.wala.ipa.callgraph.impl.ArgumentTypeEntrypoint;
|
|
import com.ibm.wala.ipa.callgraph.impl.BasicEntrypoints;
|
|
import com.ibm.wala.ipa.cha.ClassHierarchy;
|
|
import com.ibm.wala.util.StringStuff;
|
|
import com.ibm.wala.util.collections.HashSetFactory;
|
|
import com.ibm.wala.util.debug.Trace;
|
|
import com.ibm.wala.util.warnings.WalaException;
|
|
|
|
/**
|
|
*
|
|
* All public and protected methods in the "root packages" are considered
|
|
* entrypoints, except not inner classes
|
|
*
|
|
* @author pistoia
|
|
* @author sfink
|
|
*/
|
|
public class EclipseEntrypoints extends BasicEntrypoints {
|
|
|
|
private final static boolean DEBUG = false;
|
|
|
|
/**
|
|
* @param scope
|
|
* governing analysis scope
|
|
* @param cha
|
|
* governing class hierarchy
|
|
* @param internalEntrypoints
|
|
* This boolean flag is used to decide whether internal packages
|
|
* (those containing the substring ".internal" in their names) should
|
|
* contribute entry points to the analysis (value <code>true</code>)
|
|
* or not (value <code>false</code>). The default value is
|
|
* <code>false</code> because internal packages are not supposed to
|
|
* be invoked from external bundles.
|
|
* @throws WalaException
|
|
*/
|
|
public EclipseEntrypoints(EclipseAnalysisScope scope, final ClassHierarchy cha, boolean internalEntrypoints) throws WalaException {
|
|
String pluginName = scope.getPluginName();
|
|
String pluginsDirName = scope.getPluginsDirName();
|
|
|
|
// Build the set of root packages
|
|
Set<JarFile> rootJars = findRootJars(pluginName, pluginsDirName);
|
|
// Build a set of root packages.
|
|
Set rootPackages = findRootPackages(internalEntrypoints, rootJars);
|
|
for (Iterator classIt = cha.iterateAllClasses(); classIt.hasNext();) {
|
|
IClass klass = (IClass) classIt.next();
|
|
String className = StringStuff.jvmToReadableType(klass.getName().toString());
|
|
if (className.contains("$")) {
|
|
continue;
|
|
}
|
|
String packageName = computePackageName(className);
|
|
if (!rootPackages.contains(packageName)) {
|
|
continue;
|
|
}
|
|
if (!klass.isInterface()) {
|
|
if (isApplicationClass(scope, klass)) {
|
|
for (Iterator methodIt = klass.getDeclaredMethods(); methodIt.hasNext();) {
|
|
IMethod method = (IMethod) methodIt.next();
|
|
if (!method.isAbstract() && method.isPublic() || method.isProtected()) {
|
|
add(new ArgumentTypeEntrypoint(method, cha));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if (DEBUG) {
|
|
Trace.println(getClass() + "Number of EntryPoints:" + size());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param scope
|
|
* an <code>AnalysisScope</code> object representing the governing
|
|
* analysis scope
|
|
* @param klass
|
|
* the <code>IClass</code> object whose class loading membership is
|
|
* being tested
|
|
* @return <code>true</code> iff <code>klass</code> is loaded by the
|
|
* application loader.
|
|
*/
|
|
private boolean isApplicationClass(AnalysisScope scope, IClass klass) {
|
|
return scope.getApplicationLoader().equals(klass.getClassLoader().getReference());
|
|
}
|
|
|
|
private String computePackageName(String className) {
|
|
int lastDot = className.lastIndexOf('.');
|
|
if (lastDot == -1) {
|
|
return "";
|
|
}
|
|
return className.substring(0, lastDot);
|
|
}
|
|
|
|
/**
|
|
* findRootJars - All the plugins listed in the configuration file must be
|
|
* considered for the analysis scope as well as for finding the root nodes.
|
|
*
|
|
* @param p
|
|
* The configuration properties file for the
|
|
* EclipseAnalysisScopeBuilder.
|
|
* @return A Set containing entry <code>JARFile</code> objects. An <i>entry
|
|
* JAR file</i> is a JAR file containing some class files whose
|
|
* methods could be used as entry points. By default, all the public
|
|
* and protected methods in non-internal packages are considered entry
|
|
* points.
|
|
* @throws WalaException
|
|
*/
|
|
private static Set<JarFile> findRootJars(String pluginName, String pluginsDirName) throws WalaException {
|
|
Set<JarFile> rootJars = HashSetFactory.make();
|
|
|
|
File pluginDirectory = new File(pluginsDirName);
|
|
File plugIn = EclipseAnalysisScope.findPluginDirOrJAR(pluginName, pluginsDirName, pluginDirectory);
|
|
if (plugIn == null) {
|
|
throw new WalaException("EclipseAnalysisScopeBuilder: Unable to identify " + pluginName);
|
|
}
|
|
String realName = plugIn.getPath();
|
|
if (plugIn.isDirectory()) {
|
|
System.out.println("EclipseAnalysisScopeBuilder: " + pluginName + " converted to " + realName);
|
|
// Find all the JAR files in the plugin directory
|
|
Set<JarFile> pluginAllJars = EclipseAnalysisScope.findJars(plugIn);
|
|
// Among them, find the root JAR files for this plugin
|
|
Set<JarFile> pluginRootJars = findBundleRootJars(realName, pluginAllJars);
|
|
// Add the root JARs for this plugin to the set of all the root
|
|
// JAR files for the whole analysis
|
|
if (pluginRootJars == null || pluginRootJars.isEmpty()) {
|
|
System.out.println("EclipseAnalysisScopeBuilder: Plugin or fragment contains no JAR files. Nothing to analyze.");
|
|
} else {
|
|
rootJars.addAll(pluginRootJars);
|
|
}
|
|
} else {
|
|
try {
|
|
JarFile jarFile = new JarFile(plugIn);
|
|
System.out.println("EclipseAnalysisScopeBuilder: JAR file " + pluginsDirName + "/" + pluginName + ".jar found");
|
|
rootJars.add(jarFile);
|
|
} catch (IOException ioe) {
|
|
System.out.println("EclipseAnalysisScopeBuilder: JAR file " + pluginsDirName + "/" + pluginName + ".jar not found");
|
|
}
|
|
}
|
|
return rootJars;
|
|
}
|
|
|
|
/**
|
|
* findRootPackages - Builds up a list of package names from the rootJars Set
|
|
* and saves the results in private TreeSet var rootPackages.
|
|
*
|
|
* @return A Set containing String objects, each String object representing
|
|
* the name of a package that should be considered a root package. A
|
|
* <i>root package </i> is a package such that all its classes' public
|
|
* and protected methods are considered root methods. By default, a
|
|
* root package does not contain the String ".internal." in its name.
|
|
*/
|
|
private static Set<String> findRootPackages(boolean internalEntrypoints, Set<JarFile> rootJars) {
|
|
Set<String> rootPackages = HashSetFactory.make();
|
|
if (DEBUG) {
|
|
System.out.println("ROOT JARS:");
|
|
}
|
|
for (JarFile rootJarFile : rootJars) {
|
|
if (DEBUG) {
|
|
System.out.println(rootJarFile.getName());
|
|
}
|
|
Enumeration entries = rootJarFile.entries();
|
|
while (entries.hasMoreElements()) {
|
|
JarEntry entry = (JarEntry) entries.nextElement();
|
|
String entryName = entry.getName();
|
|
// Filter out the internal packages, which should not
|
|
// be invoked by client code.
|
|
|
|
if (!internalEntrypoints && entryName.indexOf("/internal/") != -1) {
|
|
continue;
|
|
}
|
|
if (entryName.endsWith(".class")) {
|
|
// Indentify the package name
|
|
int lastSlash = entryName.lastIndexOf('/');
|
|
String tempPackageName = entryName.substring(0, lastSlash);
|
|
String packageName = tempPackageName.replace('/', '.');
|
|
rootPackages.add(packageName);
|
|
}
|
|
}
|
|
}
|
|
if (rootPackages.isEmpty()) {
|
|
StringBuffer buf = new StringBuffer();
|
|
int numberOfRootJars = rootJars.size();
|
|
buf.append("EclipseAnalysisScopeBuilder: No entry points in JAR file");
|
|
buf.append((numberOfRootJars == 1) ? ":\n" : "s:\n");
|
|
Iterator<JarFile> rootJarsIter = rootJars.iterator();
|
|
while (rootJarsIter.hasNext()) {
|
|
buf.append("\t" + rootJarsIter.next() + "\n");
|
|
}
|
|
buf.append("The analysis is terminating.");
|
|
System.out.println(buf.toString());
|
|
System.exit(0);
|
|
}
|
|
if (DEBUG) {
|
|
System.out.println("ROOT PACKAGES: " + rootPackages);
|
|
}
|
|
return rootPackages;
|
|
}
|
|
|
|
/**
|
|
* Find all the root JAR files for a plugin, given the plugin directory. The
|
|
* root JAR files are returned as String objects, each String representing the
|
|
* fully qualified name of the JAR file. The information is based on the
|
|
* plugin configuration file.
|
|
*
|
|
* @param dirName
|
|
* a String representing the fully qualified name of the directory
|
|
* where a plugin is stored.
|
|
* @param pluginAllJars
|
|
* all the JAR files found in the plugin directory.
|
|
* @return a TreeSet of String object, each object representing the fully
|
|
* qualified name of a plugin JAR file.
|
|
*/
|
|
private static Set<JarFile> findBundleRootJars(String dirName, Set<JarFile> pluginAllJars) {
|
|
String manifestFileName = dirName + File.separator + "META-INF" + File.separator + "MANIFEST.MF";
|
|
Set<JarFile> pluginRootJars = HashSetFactory.make();
|
|
InputStream is = EclipseAnalysisScope.getInputStream(manifestFileName);
|
|
if (is == null) {
|
|
System.out.println("EclipseAnalysisScopeBuilder: " + manifestFileName + " was not found.");
|
|
return pluginAllJars;
|
|
} else {
|
|
try {
|
|
Manifest m = new Manifest(is);
|
|
Attributes a = m.getMainAttributes();
|
|
// If FRAGMENT_HOST is set, then this is a fragment, not a plugin.
|
|
String hostPlugin = a.getValue(Constants.FRAGMENT_HOST);
|
|
if (hostPlugin != null) {
|
|
hostPlugin = parseFragmentHost(hostPlugin);
|
|
}
|
|
String bundleClasspath = a.getValue(Constants.BUNDLE_CLASSPATH);
|
|
if (bundleClasspath != null) {
|
|
String requiredArray[] = bundleClasspath.split(",");
|
|
if (requiredArray != null) {
|
|
for (int i = 0; i < requiredArray.length; i++) {
|
|
String libraryName = (requiredArray[i].trim()).split(";")[0];
|
|
// Verify that the JAR file is really there
|
|
String fullLibraryName = dirName + File.separator + libraryName;
|
|
Iterator pluginAllJarsIter = pluginAllJars.iterator();
|
|
while (pluginAllJarsIter.hasNext()) {
|
|
JarFile jarFile = (JarFile) pluginAllJarsIter.next();
|
|
String jarFileName = jarFile.getName();
|
|
if (jarFileName.equals(fullLibraryName)) {
|
|
pluginRootJars.add(jarFile);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch (Exception e) {
|
|
System.out.println("EclipseAnalysisScopeBuilder: " + e.getLocalizedMessage());
|
|
}
|
|
}
|
|
return pluginRootJars;
|
|
}
|
|
|
|
/**
|
|
* Parses the name of an Eclipse 3.0 fragment host plugin from the value
|
|
* returned by the manifest.
|
|
*
|
|
* e.g. if value contains: BadPlugin2;bundle-version="1.0.0" this method
|
|
* returns "BadPlugin2".
|
|
*
|
|
* @param value
|
|
* @return String - the name of the fragment's plugin host.
|
|
*
|
|
*/
|
|
private static String parseFragmentHost(String value) {
|
|
String result = null;
|
|
String elements[] = value.split(";");
|
|
if (elements != null)
|
|
result = elements[0];
|
|
return result;
|
|
}
|
|
} |