001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041:
042: package org.netbeans.insane.impl;
043:
044: import java.lang.reflect.*;
045: import java.util.*;
046:
047: import org.netbeans.insane.scanner.*;
048:
049: /**
050: * InsaneEngine is the implementation of the unified heap walking algorithm,
051: * which can recognize and visit both member and class fields of objects
052: * on the heap. It starts from a set of root objects and notifies the registered
053: * visitor about all found instances. It can also check the objects against
054: * a provided filter to implement bounded heap scan.
055: *
056: * @author Nenik
057: */
058: public final class InsaneEngine {
059:
060: private Filter filter;
061: private Visitor visitor;
062: private ObjectMap objects;
063: private boolean analyzeStaticData;
064:
065: /** Creates a new instance of InsaneEngine
066: * @param f Filter, can be null.
067: */
068: public InsaneEngine(Filter f, Visitor v, boolean analyzeStatic) {
069: this (new SmallObjectMap2(), f, v, analyzeStatic);
070: }
071:
072: public InsaneEngine(ObjectMap backend, Filter f, Visitor v,
073: boolean analyzeStatic) {
074: objects = backend;
075: filter = f == null ? ScannerUtils.noFilter() : f;
076: visitor = v;
077: analyzeStaticData = analyzeStatic;
078: }
079:
080: // normal set is enough, java.lang.Class have identity-based equals()
081: private Set<Class> knownClasses = new HashSet<Class>();
082:
083: // The queue for BFS scan of the heap.
084: // each thing, before added to the queue, is added
085: // to the known* structures and reported to the visitor
086: private Queue<Object> queue = new Queue<Object>();
087:
088: public void traverse(Collection roots) throws Exception {
089: // process all given roots first - remember them and put them into queue
090: for (Iterator it = roots.iterator(); it.hasNext();)
091: recognize(it.next());
092:
093: while (!queue.isEmpty()) {
094: process(queue.get());
095: }
096: }
097:
098: // recognize an object:
099: // * recognize it's class
100: // * report newly found items
101: // * add newly found items to the queue
102: private void recognize(Object o) {
103: assert o != null : "Recognize objects, not null";
104:
105: // dispatch the recognition
106: if (o instanceof Class) {
107: recognizeClass((Class) o);
108: } else {
109: recognizeObject(o);
110: }
111: }
112:
113: private void recognizeClass(Class cls) {
114: // already known?
115: if (knownClasses.contains(cls))
116: return;
117:
118: // my own classes
119: if (cls.getName().startsWith("org.netbeans.insane.scanner"))
120: return;
121:
122: if (!analyzeStaticData) {
123: knownClasses.add(cls);
124: visitor.visitClass(cls);
125: return;
126: }
127:
128: try {
129: // check the superclass
130: Class sup = cls.getSuperclass();
131: if (sup != null)
132: recognizeClass(sup);
133:
134: // check all interfaces
135: Class[] ifaces = cls.getInterfaces();
136: for (int i = 0; i < ifaces.length; i++)
137: recognizeClass(ifaces[i]);
138:
139: // mark the class as known
140: knownClasses.add(cls);
141:
142: // scan fields for new types
143: Field[] fields = cls.getDeclaredFields();
144: for (int i = 0; i < fields.length; i++)
145: recognizeClass(fields[i].getType());
146:
147: // scan method signatures for new types
148: Method[] methods = cls.getDeclaredMethods();
149: for (int i = 0; i < methods.length; i++) {
150: recognizeClass(methods[i].getReturnType());
151: Class[] params = methods[i].getParameterTypes();
152: for (int j = 0; j < params.length; j++)
153: recognizeClass(params[j]);
154: }
155:
156: // scan constructor signatures for new types
157: Constructor[] cons = cls.getConstructors();
158: for (int i = 0; i < cons.length; i++) {
159: Class[] params = cons[i].getParameterTypes();
160: for (int j = 0; j < params.length; j++)
161: recognizeClass(params[j]);
162: }
163:
164: } catch (Error e) {
165: System.err.println("Failed analysing class "
166: + cls.getName() + " because of " + e);
167: }
168: // enqueue it for further processing of outgoing static references
169: queue.add(cls);
170:
171: // finally report the class itself
172: visitor.visitClass(cls);
173: }
174:
175: private void recognizeObject(Object o) {
176: // already known?
177: if (objects.isKnown(o))
178: return;
179:
180: // my own data structures
181: if (o.getClass().getName().startsWith(
182: "org.netbeans.insane.scanner"))
183: return;
184:
185: // XXX - implicit safety filter. Really needed?
186: assert !(o.getClass().getName().startsWith("sun.reflect."));
187:
188: // check its class first
189: recognizeClass(o.getClass());
190:
191: // recognize it - mark it as known and assign ID to it
192: objects.getID(o);
193:
194: // enqueue it for further processing of outgoing references
195: queue.add(o);
196:
197: // then report the object itself
198: visitor.visitObject(objects, o);
199: }
200:
201: private void process(Object o) throws Exception {
202: // dispatch the processing
203: if (o instanceof Class) {
204: processClass((Class) o);
205: } else {
206: processObject(o);
207: }
208: }
209:
210: /* follow static references and possibly other class data
211: */
212: private void processClass(Class cls) throws Exception {
213: if (!analyzeStaticData)
214: return;
215:
216: if (cls.getName().startsWith("java.lang.reflect")
217: || cls.getName().equals("java.lang.Class")) {
218: return;
219: }
220:
221: //if (cls.getName().startsWith("org.netbeans.insane.scanner")) return; // skip refs from myself
222:
223: ClassLoader cl = cls.getClassLoader();
224: if (cl != null)
225: recognize(cl);
226:
227: // process only fields declared by this class,
228: // fields of all superclasses were already processed in separate run.
229: Field[] flds = null;
230: try {
231: flds = cls.getDeclaredFields();
232: } catch (NoClassDefFoundError e) {
233: System.err.println("Failed analysing class "
234: + cls.getName() + " because of " + e);
235: return; // unlinkable class
236: } catch (Throwable t) {
237: System.err.println("Failed analysing class "
238: + cls.getName() + " because of " + t);
239: return; // Some other problem
240: }
241: for (int i = 0; i < flds.length; i++) {
242: Field act = flds[i];
243: if (((act.getModifiers() & Modifier.STATIC) != 0)
244: && (!act.getType().isPrimitive())) {
245: act.setAccessible(true);
246: Object target;
247: try {
248: target = act.get(null);
249: } catch (Throwable t) {
250: System.err.println("Failed to read field " + act
251: + " because of " + t);
252: continue;
253: }
254: if (target != null) {
255: if (target.getClass().getName().startsWith(
256: "sun.reflect"))
257: continue;
258:
259: if (filter.accept(target, null, act)) {
260: recognize(target);
261: // can be refused by recognize, needs to recheck here
262: if (objects.isKnown(target))
263: visitor.visitStaticReference(objects,
264: target, act);
265: }
266: }
267: }
268: }
269: }
270:
271: private void processObject(Object obj) throws Exception {
272: assert objects.isKnown(obj) : "Objects in queue must be known";
273:
274: Class cls = obj.getClass();
275:
276: if (cls.getName().startsWith("java.lang.reflect"))
277: return;
278:
279: if (cls.isArray() && !cls.getComponentType().isPrimitive()) {
280: // enqueue all object array entries
281: Object[] arr = (Object[]) obj;
282: // it may be Class[], so not recognizeObject directly
283: for (int i = 0; i < arr.length; i++) {
284: Object target = arr[i];
285: if (target != null) {
286: if (filter.accept(target, arr, null)) {
287: recognize(target);
288: if (objects.isKnown(target))
289: visitor.visitArrayReference(objects, obj,
290: target, i);
291: }
292: }
293: }
294: } else {
295: // enqueue all instance fields of reference type
296: while (cls != null) { // go over the class hierarchy
297: try {
298: Field[] flds = cls.getDeclaredFields();
299: for (int i = 0; i < flds.length; i++) {
300: Field act = flds[i];
301:
302: if (((act.getModifiers() & Modifier.STATIC) == 0)
303: && (!act.getType().isPrimitive())) {
304:
305: act.setAccessible(true);
306: Object target = act.get(obj);
307: if (target != null) {
308: if (filter.accept(target, obj, act)) {
309: recognize(target);
310: if (objects.isKnown(target))
311: visitor.visitObjectReference(
312: objects, obj, target,
313: act);
314: }
315: }
316: }
317: }
318: } catch (Error e) {
319: System.err.println("Skipped analysing class "
320: + cls.getName() + " because of " + e);
321: }
322: cls = cls.getSuperclass();
323: }
324: }
325: }
326:
327: /*
328: * A queue implementation that tries to be as effective
329: * as LinkedList but use as little storage as ArrayList
330: */
331: private static class Queue<T> extends ArrayList<T> {
332: private int offset = 0;
333:
334: public Queue() {
335: }
336:
337: public void put(T o) {
338: add(o);
339: }
340:
341: public boolean isEmpty() {
342: return offset >= size();
343: }
344:
345: public Object get() {
346: if (isEmpty())
347: throw new NoSuchElementException();
348: Object o = get(offset++);
349: if (offset > 1000) {
350: removeRange(0, offset);
351: offset = 0;
352: }
353: return o;
354: }
355: }
356: }
|