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: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.instrumentation;
042:
043: import org.netbeans.lib.profiler.ProfilerEngineSettings;
044: import org.netbeans.lib.profiler.classfile.BaseClassInfo;
045: import org.netbeans.lib.profiler.classfile.DynamicClassInfo;
046: import org.netbeans.lib.profiler.classfile.PlaceholderClassInfo;
047: import org.netbeans.lib.profiler.global.ProfilingSessionStatus;
048: import org.netbeans.lib.profiler.utils.MiscUtils;
049: import org.netbeans.lib.profiler.utils.Wildcards;
050: import java.util.ArrayList;
051:
052: /**
053: * Recursive method scaner that implements the lazy instrumentation scheme ("Scheme B" in the JFluid paper).
054: *
055: * @author Misha Dmitriev
056: * @author Adrian Mos
057: */
058: public class RecursiveMethodInstrumentor1 extends
059: RecursiveMethodInstrumentor {
060: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
061:
062: /** A placeholder for a class that contains reachable method and in which transferData() is specialized for this instrumentation scheme */
063: protected class ReachableMethodPlaceholder1 extends
064: ReachableMethodPlaceholder {
065: //~ Constructors ---------------------------------------------------------------------------------------------------------
066:
067: public ReachableMethodPlaceholder1(String refClassName,
068: int classLoaderId) {
069: super (refClassName, classLoaderId);
070: }
071:
072: //~ Methods --------------------------------------------------------------------------------------------------------------
073:
074: public void transferDataIntoRealClass(DynamicClassInfo clazz) {
075: super .transferDataIntoRealClass(clazz);
076:
077: int len = methodNamesAndSigs.size();
078:
079: for (int i = 0; i < len; i += 2) {
080: locateAndMarkMethodReachable(clazz,
081: (String) methodNamesAndSigs.get(i),
082: (String) methodNamesAndSigs.get(i + 1), false,
083: false, false);
084: }
085: }
086: }
087:
088: //~ Constructors -------------------------------------------------------------------------------------------------------------
089:
090: public RecursiveMethodInstrumentor1(ProfilingSessionStatus status,
091: ProfilerEngineSettings settings) {
092: super (status, settings);
093: }
094:
095: //~ Methods ------------------------------------------------------------------------------------------------------------------
096:
097: Object[] getInitialMethodsToInstrument(String[] loadedClasses,
098: int[] loadedClassLoaderIds, byte[][] cachedClassFileBytes,
099: RootMethods roots) {
100: DynamicClassInfo[] loadedClassInfos = preGetInitialMethodsToInstrument(
101: loadedClasses, loadedClassLoaderIds,
102: cachedClassFileBytes);
103:
104: rootMethods = roots;
105:
106: // Check which root classes have already been loaded, and mark their root methods accordingly
107: for (int j = 0; j < loadedClassInfos.length; j++) {
108: if (loadedClassInfos[j] == null) {
109: continue; // Can this happen?
110: }
111:
112: String className = loadedClassInfos[j].getName();
113:
114: tryInstrumentSpawnedThreads(loadedClassInfos[j]);
115:
116: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
117: String rootClassName = rootMethods.classNames[rIdx]
118: .intern();
119:
120: boolean isMatch = false;
121:
122: if (rootMethods.classesWildcard[rIdx]) {
123: if (matchesWildcard(Wildcards
124: .unwildPackage(rootClassName), className)) {
125: // System.out.println("Matched package wildcard - " + rootClassName);
126: isMatch = true;
127: }
128: } else {
129: if (className == rootClassName) { // precise match
130: isMatch = true;
131: }
132: }
133:
134: if (isMatch) { // This root class is loaded
135:
136: if (Wildcards.isPackageWildcard(rootClassName)
137: || Wildcards
138: .isMethodWildcard(rootMethods.methodNames[rIdx])) {
139: if (rootMethods.markerMethods[rIdx]) {
140: markAllMethodsMarker(loadedClassInfos[j]);
141: } else {
142: markAllMethodsRoot(loadedClassInfos[j]);
143: }
144:
145: String[] methodNames = loadedClassInfos[j]
146: .getMethodNames();
147: String[] signatures = loadedClassInfos[j]
148: .getMethodSignatures();
149:
150: for (int methodIdx = 0; methodIdx < methodNames.length; methodIdx++) {
151: locateAndMarkMethodReachable(
152: loadedClassInfos[j],
153: methodNames[methodIdx],
154: signatures[methodIdx], false,
155: false, false);
156: }
157: } else {
158: markMethod(loadedClassInfos[j], rIdx);
159: locateAndMarkMethodReachable(
160: loadedClassInfos[j],
161: rootMethods.methodNames[rIdx],
162: rootMethods.methodSignatures[rIdx],
163: false, false, false);
164: }
165: }
166: }
167: }
168:
169: locateAndMarkMethodReachable(javaClassForName(
170: "java/lang/ClassLoader", 0), "loadClass",
171: "(Ljava/lang/String;)Ljava/lang/Class;", true, false,
172: true); // NOI18N
173:
174: return createInstrumentedMethodPack();
175: }
176:
177: public Object[] getMethodsToInstrumentUponClassLoad(
178: String className, int classLoaderId,
179: boolean threadInCallGraph) {
180: //System.out.println("*** MS1: instr. upon CL: " + className);
181: className = className.replace('.', '/').intern(); // NOI18N
182:
183: // If a class doesn't pass the current instrumentation filter, we can't immediately reject it, since there is a chance
184: // it contains some root methods. So we have to check that first.
185: boolean isRootClass = false;
186:
187: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
188: final String rootClassName = rootMethods.classNames[rIdx]
189: .intern();
190:
191: if (rootMethods.classesWildcard[rIdx]) {
192: if (matchesWildcard(Wildcards
193: .unwildPackage(rootClassName), className)) {
194: // System.out.println("Matched package wildcard - " + rootClassName);
195: isRootClass = true;
196:
197: break;
198: }
199: } else {
200: if (className == rootClassName) {
201: isRootClass = true;
202:
203: break;
204: }
205: }
206: }
207:
208: boolean normallyFilteredOut = !instrFilter
209: .passesFilter(className);
210:
211: if (!isRootClass) {
212: if (normallyFilteredOut) {
213: return null;
214: }
215: }
216:
217: DynamicClassInfo clazz = javaClassForName(className,
218: classLoaderId);
219:
220: if (clazz == null) {
221: return null; // Warning already issued
222: }
223:
224: initInstrMethodData();
225: instrumentClinit = threadInCallGraph;
226:
227: if (!clazz.isLoaded()) {
228: clazz.setLoaded(true);
229: addToSubclassList(clazz, normallyFilteredOut ? null : clazz); // null as a second parameter will result in NOT marking any methods reachable
230:
231: // Check to see if this class has been marked as root by the user:
232: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
233: final String rootClassName = rootMethods.classNames[rIdx]
234: .intern();
235:
236: boolean isMatch = false;
237:
238: if (rootMethods.classesWildcard[rIdx]) {
239: if (matchesWildcard(Wildcards
240: .unwildPackage(rootClassName), className)) {
241: // System.out.println("Matched package wildcard - " + rootClassName);
242: isMatch = true;
243: }
244: } else {
245: if (className == rootClassName) { // precise match
246: isMatch = true;
247: }
248: }
249:
250: if (isMatch) { // it is indeed a root class
251:
252: if (Wildcards.isPackageWildcard(rootClassName)
253: || Wildcards
254: .isMethodWildcard(rootMethods.methodNames[rIdx])) {
255: if (rootMethods.markerMethods[rIdx]) {
256: markAllMethodsMarker(clazz);
257: } else {
258: markAllMethodsRoot(clazz);
259: }
260:
261: String[] methodNames = clazz.getMethodNames();
262: String[] signatures = clazz
263: .getMethodSignatures();
264:
265: for (int methodIdx = 0; methodIdx < methodNames.length; methodIdx++) {
266: locateAndMarkMethodReachable(clazz,
267: methodNames[methodIdx],
268: signatures[methodIdx], false,
269: false, false);
270: }
271: } else {
272: markMethod(clazz, rIdx);
273: locateAndMarkMethodReachable(clazz,
274: rootMethods.methodNames[rIdx],
275: rootMethods.methodSignatures[rIdx],
276: false, false, false);
277: }
278: }
279: }
280:
281: tryInstrumentSpawnedThreads(clazz);
282: countReachableScannableMethods(clazz);
283:
284: return createInstrumentedMethodPack(clazz);
285: } else {
286: return null;
287: }
288: }
289:
290: public Object[] getMethodsToInstrumentUponMethodInvocation(
291: String className, int classLoaderId, String methodName,
292: String methodSignature) {
293: //System.out.println("*** MS1: instr. upon MI: " + className + "." + methodName + methodSignature);
294: className = className.replace('.', '/').intern(); // NOI18N
295: methodName = methodName.intern();
296: methodSignature = methodSignature.intern();
297: initInstrMethodData();
298:
299: checkAndScanMethod(className, classLoaderId, methodName,
300: methodSignature);
301:
302: return createInstrumentedMethodPack();
303: }
304:
305: public Object[] getMethodsToInstrumentUponReflectInvoke(
306: String className, int classLoaderId, String methodName,
307: String methodSignature) {
308: //System.out.println("*** MS1: instr. upon reflect MI: " + className + "." + methodName + methodSignature);
309: className = className.replace('.', '/').intern();
310:
311: DynamicClassInfo clazz = javaClassForName(className,
312: classLoaderId);
313:
314: if (clazz == null) {
315: // System.err.println("Warning: could not find class " + className + " loaded by the VM on the class path");
316: // warning already issued in ClassRepository.lookupClass method, no need to do it again
317: return null;
318: }
319:
320: if (!clazz.isLoaded()) {
321: return null; // Probably impossible
322: }
323:
324: initInstrMethodData();
325: instrumentClinit = true;
326:
327: methodName = methodName.intern();
328: methodSignature = methodSignature.intern();
329:
330: locateAndMarkMethodReachable(clazz, methodName,
331: methodSignature, false, false, false);
332:
333: //countReachableScannableMethods(clazz);
334: return createInstrumentedMethodPack();
335: }
336:
337: protected void findAndMarkOverridingMethodsReachable(
338: DynamicClassInfo super Class, DynamicClassInfo subClass) {
339: String[] methodNames = super Class.getMethodNames();
340: String[] methodSignatures = super Class.getMethodSignatures();
341:
342: for (int i = 0; i < methodNames.length; i++) {
343: if (!(super Class.isMethodVirtual(i) && super Class
344: .isMethodReachable(i))) {
345: continue;
346: }
347:
348: // int idx = subClass.overridesVirtualMethod(superClass, i); - I once tried this, but with no visible effect. Strict check
349: // for whether a method with the same name and signature in subclass really overrrides a method in superclass, given all other
350: // conditions that we have already checked (e.g. that the method in superclass is not private), will only detect a pathological
351: // case when both method versions are package-private. This is rare, if ever happens at all.
352: locateAndMarkMethodReachable(subClass, methodNames[i],
353: methodSignatures[i], true, false, false);
354: }
355: }
356:
357: protected void processInvoke(DynamicClassInfo clazz,
358: boolean virtualCall, int index) {
359: byte[] savedCodeBytes = codeBytes;
360: int savedOffset = offset;
361:
362: String[] cms = clazz.getRefMethodsClassNameAndSig(index);
363:
364: if (cms == null) {
365: return; // That's how coming across our own stuff currently manifests itself, e.g. when scanning instrumented Method.invoke()
366: }
367:
368: String refClassName = cms[0];
369:
370: if (refClassName
371: .startsWith(PROFILER_SERVER_SLASHED_CLASS_PREFIX)) {
372: return; // We may come across our own stuff e.g. when scanning instrumented Method.invoke()
373: }
374:
375: int loaderId = clazz.getLoaderId();
376:
377: // Now let's check if the callee class is actually loaded at this time. If not, we just record the fact that the
378: // callee method is reachable using a placeholder class, and return
379: BaseClassInfo refClazz = loadedJavaClassOrExistingPlaceholderForName(
380: refClassName, loaderId);
381:
382: if ((refClazz == null)
383: || (refClazz instanceof PlaceholderClassInfo)) {
384: ReachableMethodPlaceholder pci = (refClazz == null) ? new ReachableMethodPlaceholder1(
385: refClassName, loaderId)
386: : (ReachableMethodPlaceholder) refClazz;
387: pci.registerReachableMethod(cms[1], cms[2]);
388:
389: if (refClazz == null) {
390: registerPlaceholder(pci);
391: }
392: } else {
393: locateAndMarkMethodReachable((DynamicClassInfo) refClazz,
394: cms[1], cms[2], virtualCall, true, true);
395: }
396:
397: offset = savedOffset;
398: codeBytes = savedCodeBytes;
399: }
400:
401: /**
402: * If instrumenteSpawnedThreads is true and the given class implements Runnable, find and mark as root its run() method.
403: */
404: protected boolean tryInstrumentSpawnedThreads(DynamicClassInfo clazz) {
405: if (instrumentSpawnedThreads) {
406: if (clazz.implements Interface("java/lang/Runnable")
407: && (clazz.getName() != "java/lang/Thread")) { // NOI18N
408:
409: boolean res = markMethodRoot(clazz, "run", "()V"); // NOI18N
410: locateAndMarkMethodReachable(clazz, "run", "()V",
411: false, false, false); // NOI18N
412:
413: return res;
414: }
415: }
416:
417: return false;
418: }
419:
420: private void checkAndScanMethod(String className,
421: int classLoaderId, String methodName, String methodSignature) {
422: DynamicClassInfo clazz = javaClassForName(className,
423: classLoaderId);
424:
425: if (clazz == null) {
426: return;
427: }
428:
429: int idx = clazz.getMethodIndex(methodName, methodSignature);
430:
431: if (idx == -1) {
432: MiscUtils.internalError("can't find method " + methodName
433: + methodSignature + " in class " + className); // NOI18N
434: }
435:
436: if (clazz.isMethodUnscannable(idx)) {
437: MiscUtils.internalError("got to scan unscannable method "
438: + className + "." + methodName + methodSignature); // NOI18N
439: }
440:
441: clazz.setMethodScanned(idx);
442: scanMethod(clazz, idx);
443: }
444:
445: /** Called to count methods to instrument in a single loaded class. */
446: private void countReachableScannableMethods(DynamicClassInfo clazz) {
447: nInstrMethods = 0;
448:
449: String[] methodNames = clazz.getMethodNames();
450:
451: for (int i = 0; i < methodNames.length; i++) {
452: if (!clazz.isMethodInstrumented(i)) {
453: if (clazz.isMethodReachable(i)
454: && !clazz.isMethodUnscannable(i)) {
455: nInstrMethods++;
456: } else if (instrumentClinit
457: && (methodNames[i] == "<clinit>")) {
458: nInstrMethods++; // NOI18N
459: }
460: }
461: }
462: }
463:
464: /**
465: * Mark the given method reachable. The boolean parameters affect this function in the following way:
466: * - virtualCall = true means that we reached this method via a "call virtual" instruction, or otherwise know that it's
467: * virtual, and want to treat it as virtual. So, for example, if checkSubclasses is true, a method with this name and
468: * signature should be looked up in subclasses of this class.
469: * - lookupInSuperIfNotFoundInThis = true means that if the given method is not found in this class, it should be looked
470: * up in its superclasses.
471: * - checkSubclasses = true means that if a method is virtual (either because virtualCall == true or because this method is
472: * really defined in this class and marked as virtual), methods that override it in subclasses of this class should also
473: * be marked reachable.
474: */
475: private boolean locateAndMarkMethodReachable(
476: DynamicClassInfo clazz, String methodName,
477: String methodSignature, boolean virtualCall,
478: boolean lookupInSuperIfNotFoundInThis,
479: boolean checkSubclasses) {
480: if (clazz == null) {
481: return false; // Normally shouldn't happen, it's just development-time facilitation (introduced when working on 1.5 support)
482: }
483:
484: String className = clazz.getName();
485: int idx = clazz.getMethodIndex(methodName, methodSignature);
486:
487: if (idx != -1) {
488: if (clazz.isMethodReachable(idx)) {
489: return true;
490: }
491:
492: clazz.setMethodReachable(idx);
493:
494: if (!clazz.isMethodStatic(idx)
495: && !clazz.isMethodPrivate(idx)
496: && !clazz.isMethodFinal(idx)
497: && (methodName != "<init>")) {
498: clazz.setMethodVirtual(idx); // NOI18N
499: }
500:
501: if (clazz.isMethodNative(idx)
502: || clazz.isMethodAbstract(idx)
503: || (!clazz.isMethodRoot(idx)
504: && !clazz.isMethodMarker(idx) && !instrFilter
505: .passesFilter(className))
506: || (className == "java/lang/Object")) { // NOI18N // Actually, just the Object.<init> method?
507: clazz.setMethodUnscannable(idx);
508: } else {
509: byte[] bytecode = clazz.getMethodBytecode(idx);
510:
511: if ((dontInstrumentEmptyMethods && isEmptyMethod(bytecode))
512: || (dontScanGetterSetterMethods && isGetterSetterMethod(bytecode))) {
513: clazz.setMethodUnscannable(idx);
514: } else {
515: if (isLeafMethod(bytecode)) {
516: clazz.setMethodLeaf(idx);
517: }
518: }
519: }
520:
521: if (!clazz.isLoaded()) {
522: return true; // No need to check subclasses because there are no loaded subclasses if this class itself is not loaded
523: // Class is loaded, method is reachable and not unscannable are sufficient conditions for instrumenting method
524: }
525:
526: if (!clazz.isMethodUnscannable(idx)) {
527: markClassAndMethodForInstrumentation(clazz, idx);
528: }
529: }
530:
531: if (checkSubclasses
532: && (((idx != -1) && clazz.isMethodVirtual(idx)) || ((idx == -1) && virtualCall))) {
533: ArrayList subclasses = clazz.getSubclasses();
534:
535: if (subclasses != null) {
536: for (int i = 0; i < subclasses.size(); i++) {
537: //System.out.println("Gonna scan subclass " + subclassNames.get(i) + " of class " + className + " for method " + methodName);
538: // DynamicClassInfo subClass = javaClassForName((String) subclassNames.get(i));
539: // if ((idx != -1 && subClass.overridesVirtualMethod(clazz, idx) != -1) || idx == -1) - see the comment in findAndMarkOverridingMethods()
540: // on why this seems to be of no use.
541: locateAndMarkMethodReachable(
542: (DynamicClassInfo) subclasses.get(i),
543: methodName, methodSignature, virtualCall,
544: false, false);
545: }
546: }
547: }
548:
549: if (idx != -1) {
550: return true;
551: }
552:
553: if (!lookupInSuperIfNotFoundInThis) {
554: return false;
555: }
556:
557: // Method not defined in this class - try superclass, and if not successful - all superinterfaces
558: if (!clazz.isInterface()) {
559: DynamicClassInfo super Clazz = clazz.getSuperClass();
560:
561: if ((super Clazz != null)
562: && (super Clazz.getName() != clazz.getName())) {
563: if (locateAndMarkMethodReachable(super Clazz,
564: methodName, methodSignature, virtualCall, true,
565: false)) {
566: return true;
567: }
568: }
569: }
570:
571: DynamicClassInfo[] interfaces = clazz.getSuperInterfaces();
572:
573: if (interfaces == null) {
574: return false;
575: }
576:
577: for (int i = 0; i < interfaces.length; i++) {
578: DynamicClassInfo intfClazz = interfaces[i];
579:
580: if (intfClazz == null) {
581: continue;
582: }
583:
584: if (locateAndMarkMethodReachable(intfClazz, methodName,
585: methodSignature, virtualCall, true, false)) {
586: return true;
587: }
588: }
589:
590: return false;
591: }
592:
593: //----------------------------------- Private implementation ------------------------------------------------
594: private DynamicClassInfo[] preGetInitialMethodsToInstrument(
595: String[] loadedClasses, int[] loadedClassLoaderIds,
596: byte[][] cachedClassFileBytes) {
597: //System.out.println("*** MS1: instr. initial: " + rootClassName);
598: resetLoadedClassData();
599: storeClassFileBytesForCustomLoaderClasses(loadedClasses,
600: loadedClassLoaderIds, cachedClassFileBytes);
601: initInstrMethodData();
602:
603: DynamicClassInfo[] loadedClassInfos = new DynamicClassInfo[loadedClasses.length];
604:
605: for (int i = 0; i < loadedClasses.length; i++) {
606: String className = loadedClasses[i].replace('.', '/')
607: .intern(); // NOI18N
608: DynamicClassInfo clazz = javaClassForName(className,
609: loadedClassLoaderIds[i]);
610:
611: if (clazz == null) {
612: continue;
613: }
614:
615: clazz.setLoaded(true);
616: addToSubclassList(clazz, clazz);
617: loadedClassInfos[i] = clazz;
618: }
619:
620: return loadedClassInfos;
621: }
622: }
|