1 /*******************************************************************************
2  * Copyright (c) 2008, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *******************************************************************************/
14 package org.eclipse.pde.api.tools.internal.builder;
15 
16 import java.text.MessageFormat;
17 import java.util.LinkedList;
18 import java.util.List;
19 
20 import org.eclipse.core.runtime.CoreException;
21 import org.eclipse.core.runtime.IProgressMonitor;
22 import org.eclipse.core.runtime.MultiStatus;
23 import org.eclipse.core.runtime.OperationCanceledException;
24 import org.eclipse.core.runtime.SubMonitor;
25 import org.eclipse.pde.api.tools.internal.provisional.ApiDescriptionVisitor;
26 import org.eclipse.pde.api.tools.internal.provisional.ApiPlugin;
27 import org.eclipse.pde.api.tools.internal.provisional.IApiAnnotations;
28 import org.eclipse.pde.api.tools.internal.provisional.VisibilityModifiers;
29 import org.eclipse.pde.api.tools.internal.provisional.builder.IApiProblemDetector;
30 import org.eclipse.pde.api.tools.internal.provisional.builder.IReference;
31 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IElementDescriptor;
32 import org.eclipse.pde.api.tools.internal.provisional.descriptors.IPackageDescriptor;
33 import org.eclipse.pde.api.tools.internal.provisional.model.ApiTypeContainerVisitor;
34 import org.eclipse.pde.api.tools.internal.provisional.model.IApiComponent;
35 import org.eclipse.pde.api.tools.internal.provisional.model.IApiType;
36 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeContainer;
37 import org.eclipse.pde.api.tools.internal.provisional.model.IApiTypeRoot;
38 import org.eclipse.pde.api.tools.internal.provisional.problems.IApiProblem;
39 
40 /**
41  * The reference analyzer
42  *
43  * @since 1.1
44  */
45 public class ReferenceAnalyzer {
46 
47 	/**
48 	 * Natural log of 2.
49 	 */
50 	private static final double LOG2 = Math.log(2);
51 
52 	/**
53 	 * Empty result collection.
54 	 */
55 	private static final IApiProblem[] EMPTY_RESULT = new IApiProblem[0];
56 	/**
57 	 * No problem detector to use
58 	 */
59 	private static final IApiProblemDetector[] NO_PROBLEM_DETECTORS = new IApiProblemDetector[0];
60 
61 	/**
62 	 * Visits each class file, extracting references.
63 	 */
64 	class Visitor extends ApiTypeContainerVisitor {
65 
66 		private IProgressMonitor fMonitor = null;
67 
Visitor(IProgressMonitor monitor)68 		public Visitor(IProgressMonitor monitor) {
69 			fMonitor = monitor;
70 		}
71 
72 		@Override
visitPackage(String packageName)73 		public boolean visitPackage(String packageName) {
74 			fMonitor.subTask(MessageFormat.format(BuilderMessages.ReferenceAnalyzer_checking_api_used_by, packageName));
75 			return true;
76 		}
77 
78 		@Override
endVisitPackage(String packageName)79 		public void endVisitPackage(String packageName) {
80 			fMonitor.worked(1);
81 		}
82 
83 		@Override
visit(String packageName, IApiTypeRoot classFile)84 		public void visit(String packageName, IApiTypeRoot classFile) {
85 			if (!fMonitor.isCanceled()) {
86 				try {
87 					IApiType type = classFile.getStructure();
88 					if (type == null) {
89 						// do nothing for bad class files
90 						return;
91 					}
92 					// don't process inner/anonymous/local types, this is done
93 					// in the extractor
94 					if (type.isMemberType() || type.isLocal() || type.isAnonymous()) {
95 						return;
96 					}
97 					List<IReference> references = type.extractReferences(fAllReferenceKinds, null);
98 					// keep potential matches
99 					for (IReference ref : references) {
100 						// compute index of interested problem detectors
101 						int index = getLog2(ref.getReferenceKind());
102 						IApiProblemDetector[] detectors = fIndexedDetectors[index];
103 						boolean added = false;
104 						if (detectors != null) {
105 							for (IApiProblemDetector detector : detectors) {
106 								if (detector.considerReference(ref)) {
107 									if (!added) {
108 										fReferences.add(ref);
109 										added = true;
110 									}
111 								}
112 							}
113 						}
114 					}
115 				} catch (CoreException e) {
116 					fStatus.add(e.getStatus());
117 				}
118 			}
119 		}
120 	}
121 
122 	/**
123 	 * Scan status
124 	 */
125 	MultiStatus fStatus;
126 
127 	/**
128 	 * Bit mask of reference kinds that problem detectors care about.
129 	 */
130 	int fAllReferenceKinds = 0;
131 
132 	/**
133 	 * List of references to consider/resolve.
134 	 */
135 	List<IReference> fReferences = new LinkedList<>();
136 
137 	/**
138 	 * Problem detectors indexed by the log base 2 of each reference kind they
139 	 * are interested in. Provides a fast way to hand references off to
140 	 * interested problem detectors.
141 	 */
142 	IApiProblemDetector[][] fIndexedDetectors;
143 
144 	/**
145 	 * Indexes the problem detectors by the reference kinds they are interested
146 	 * in. For example, a detector interested in a
147 	 * {@link org.eclipse.pde.api.tools.internal.provisional.search.ReferenceModifiers#REF_INSTANTIATE}
148 	 * will be in the 26th index (0x1 << 27, which is 2 ^ 26). Also initializes
149 	 * the bit mask of all interesting reference kinds.
150 	 *
151 	 * @param detectors problem detectors
152 	 */
indexProblemDetectors(IApiProblemDetector[] detectors)153 	void indexProblemDetectors(IApiProblemDetector[] detectors) {
154 		fIndexedDetectors = new IApiProblemDetector[32][];
155 		for (IApiProblemDetector detector : detectors) {
156 			int kinds = detector.getReferenceKinds();
157 			fAllReferenceKinds |= kinds;
158 			int mask = 0x1;
159 			for (int bit = 0; bit < 32; bit++) {
160 				if ((mask & kinds) > 0) {
161 					IApiProblemDetector[] indexed = fIndexedDetectors[bit];
162 					if (indexed == null) {
163 						fIndexedDetectors[bit] = new IApiProblemDetector[] { detector };
164 					} else {
165 						IApiProblemDetector[] next = new IApiProblemDetector[indexed.length + 1];
166 						System.arraycopy(indexed, 0, next, 0, indexed.length);
167 						next[indexed.length] = detector;
168 						fIndexedDetectors[bit] = next;
169 					}
170 				}
171 				mask = mask << 1;
172 			}
173 		}
174 	}
175 
176 	/**
177 	 * log 2 (x) = ln(x) / ln(2)
178 	 *
179 	 * @param bitConstant a single bit constant (0x1 << n)
180 	 * @return log base 2 of the constant (the power of 2 the constant is equal
181 	 *         to)
182 	 */
getLog2(int bitConstant)183 	int getLog2(int bitConstant) {
184 		double logX = Math.log(bitConstant);
185 		double pow = logX / LOG2;
186 		return (int) Math.round(pow);
187 	}
188 
189 	/**
190 	 * Scans the given scope extracting all reference information.
191 	 *
192 	 * @param scope scope to scan
193 	 * @param monitor progress monitor
194 	 * @exception CoreException if the scan fails
195 	 */
extractReferences(IApiTypeContainer scope, IProgressMonitor monitor)196 	void extractReferences(IApiTypeContainer scope, IProgressMonitor monitor) throws CoreException {
197 		fStatus = new MultiStatus(ApiPlugin.PLUGIN_ID, 0, BuilderMessages.ReferenceAnalyzer_api_analysis_error, null);
198 		String[] packageNames = scope.getPackageNames();
199 		SubMonitor localMonitor = SubMonitor.convert(monitor, packageNames.length);
200 		ApiTypeContainerVisitor visitor = new Visitor(localMonitor);
201 		long start = System.currentTimeMillis();
202 		try {
203 			scope.accept(visitor);
204 		} catch (CoreException e) {
205 			fStatus.add(e.getStatus());
206 		}
207 		long end = System.currentTimeMillis();
208 		if (!fStatus.isOK()) {
209 			throw new CoreException(fStatus);
210 		}
211 		localMonitor.done();
212 		if (ApiPlugin.DEBUG_REFERENCE_ANALYZER) {
213 			System.out.println("Reference Analyzer: extracted " + fReferences.size() + " references in " + (end - start) + "ms"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
214 		}
215 	}
216 
217 	/**
218 	 * Analyzes the given {@link IApiComponent} within the given
219 	 * {@link IApiTypeContainer} (scope) and returns a collection of detected
220 	 * {@link IApiProblem}s or an empty collection, never <code>null</code>
221 	 *
222 	 * @param component
223 	 * @param scope
224 	 * @param monitor
225 	 * @return the collection of detected {@link IApiProblem}s or an empty
226 	 *         collection, never <code>null</code>
227 	 * @throws CoreException
228 	 */
analyze(IApiComponent component, IApiTypeContainer scope, IProgressMonitor monitor)229 	public IApiProblem[] analyze(IApiComponent component, IApiTypeContainer scope, IProgressMonitor monitor) throws CoreException {
230 		SubMonitor localMonitor = SubMonitor.convert(monitor, 4);
231 		// build problem detectors
232 		IApiProblemDetector[] detectors = buildProblemDetectors(component, ProblemDetectorBuilder.K_ALL, localMonitor.split(1));
233 		// analyze
234 		try {
235 			// 1. extract references
236 			localMonitor.subTask(BuilderMessages.ReferenceAnalyzer_analyzing_api_checking_use);
237 			extractReferences(scope, localMonitor.split(1));
238 			// 2. resolve problematic references
239 			localMonitor.subTask(BuilderMessages.ReferenceAnalyzer_analyzing_api_checking_use);
240 			if (fReferences.size() != 0) {
241 				ReferenceResolver.resolveReferences(fReferences, localMonitor.split(1));
242 			}
243 			// 3. create problems
244 			List<IApiProblem> allProblems = new LinkedList<>();
245 			localMonitor.subTask(BuilderMessages.ReferenceAnalyzer_analyzing_api_checking_use);
246 			SubMonitor loopMonitor = localMonitor.split(1).setWorkRemaining(detectors.length);
247 			for (IApiProblemDetector detector : detectors) {
248 				allProblems.addAll(detector.createProblems(loopMonitor.split(1)));
249 			}
250 			IApiProblem[] array = allProblems.toArray(new IApiProblem[allProblems.size()]);
251 			return array;
252 		} catch (OperationCanceledException e) {
253 			return EMPTY_RESULT;
254 		} finally {
255 			// clean up
256 			fIndexedDetectors = null;
257 			fReferences.clear();
258 		}
259 	}
260 
261 	/**
262 	 * Returns the collection of problem detectors for the given reference kind
263 	 *
264 	 * @param referencekind
265 	 * @return
266 	 */
getProblemDetectors(int referencekind)267 	public IApiProblemDetector[] getProblemDetectors(int referencekind) {
268 		if (fIndexedDetectors != null) {
269 			int index = getLog2(referencekind);
270 			if (index > -1 && index < fIndexedDetectors.length) {
271 				IApiProblemDetector[] detectors = fIndexedDetectors[index];
272 				if (detectors != null) {
273 					return detectors;
274 				}
275 			}
276 			return NO_PROBLEM_DETECTORS;
277 		}
278 		return NO_PROBLEM_DETECTORS;
279 	}
280 
281 	/**
282 	 * Builds problem detectors to use when analyzing the given component.
283 	 *
284 	 * @param component component to be analyzed
285 	 * @param kindmask the kinds of detectors to build. See
286 	 *            {@link ProblemDetectorBuilder} for kinds
287 	 * @param monitor
288 	 *
289 	 * @return problem detectors
290 	 */
buildProblemDetectors(IApiComponent component, int kindmask, IProgressMonitor monitor)291 	public IApiProblemDetector[] buildProblemDetectors(IApiComponent component, int kindmask, IProgressMonitor monitor) {
292 		try {
293 			long start = System.currentTimeMillis();
294 			IApiComponent[] components = component.getBaseline().getPrerequisiteComponents(new IApiComponent[] { component });
295 			final ProblemDetectorBuilder visitor = new ProblemDetectorBuilder(component, kindmask);
296 			SubMonitor loopMonitor = SubMonitor.convert(monitor, components.length);
297 			for (IApiComponent componentLoop : components) {
298 				SubMonitor iterationMonitor = loopMonitor.split(1);
299 				IApiComponent prereq = componentLoop;
300 				if (!prereq.equals(component)) {
301 					visitor.setOwningComponent(prereq);
302 					try {
303 						prereq.getApiDescription().accept(visitor, iterationMonitor);
304 					} catch (CoreException e) {
305 						ApiPlugin.log(e.getStatus());
306 					}
307 				}
308 			}
309 			long end = System.currentTimeMillis();
310 			if (ApiPlugin.DEBUG_REFERENCE_ANALYZER) {
311 				System.out.println("Time to build problem detectors: " + (end - start) + "ms"); //$NON-NLS-1$//$NON-NLS-2$
312 			}
313 			// add names from the leak component as well
314 			ApiDescriptionVisitor nameVisitor = new ApiDescriptionVisitor() {
315 				@Override
316 				public boolean visitElement(IElementDescriptor element, IApiAnnotations description) {
317 					if (element.getElementType() == IElementDescriptor.PACKAGE) {
318 						if (VisibilityModifiers.isPrivate(description.getVisibility())) {
319 							visitor.addNonApiPackageName(((IPackageDescriptor) element).getName());
320 						}
321 					}
322 					return false;
323 				}
324 			};
325 			component.getApiDescription().accept(nameVisitor, null);
326 			List<IApiProblemDetector> detectors = visitor.getProblemDetectors();
327 			int size = detectors.size();
328 			if (size == 0) {
329 				return NO_PROBLEM_DETECTORS;
330 			}
331 			IApiProblemDetector[] array = detectors.toArray(new IApiProblemDetector[size]);
332 			indexProblemDetectors(array);
333 			return array;
334 		} catch (CoreException e) {
335 			ApiPlugin.log(e);
336 		}
337 		return NO_PROBLEM_DETECTORS;
338 	}
339 }
340