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.instrumentation.RecursiveMethodInstrumentor.ReachableMethodPlaceholder;
049: import org.netbeans.lib.profiler.utils.Wildcards;
050: import java.util.ArrayList;
051:
052: /**
053: * Recursive method scaner that implements the eager instrumentation scheme ("Scheme A" in the JFluid paper).
054: *
055: * @author Misha Dmitriev
056: * @author Adrian Mos
057: */
058: public class RecursiveMethodInstrumentor2 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 ReachableMethodPlaceholder2 extends
064: ReachableMethodPlaceholder {
065: //~ Constructors ---------------------------------------------------------------------------------------------------------
066:
067: public ReachableMethodPlaceholder2(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: checkAndScanMethod(clazz, (String) methodNamesAndSigs
081: .get(i),
082: (String) methodNamesAndSigs.get(i + 1), false,
083: false, false);
084: }
085: }
086: }
087:
088: //~ Constructors -------------------------------------------------------------------------------------------------------------
089:
090: public RecursiveMethodInstrumentor2(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: tryInstrumentSpawnedThreads(loadedClassInfos[j]); // This only checks for Runnable.run()
113:
114: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
115: String rootClassName = rootMethods.classNames[rIdx];
116: boolean isMatch = false;
117:
118: if (rootMethods.classesWildcard[rIdx]) {
119: if (matchesWildcard(Wildcards
120: .unwildPackage(rootClassName),
121: loadedClassInfos[j].getName())) {
122: // System.out.println("(Instrumentor 2) Matched package wildcard - " + rootClassName);
123: isMatch = true;
124: }
125: } else {
126: if (loadedClassInfos[j].getName().equals(
127: rootClassName)) { // precise match
128: isMatch = true;
129: }
130: }
131:
132: if (isMatch) { // This root class is loaded
133:
134: if (Wildcards.isPackageWildcard(rootClassName)
135: || Wildcards
136: .isMethodWildcard(rootMethods.methodNames[rIdx])) {
137: if (rootMethods.markerMethods[rIdx]) {
138: markAllMethodsMarker(loadedClassInfos[j]);
139: } else {
140: markAllMethodsRoot(loadedClassInfos[j]);
141: }
142:
143: String[] methodNames = loadedClassInfos[j]
144: .getMethodNames();
145: String[] signatures = loadedClassInfos[j]
146: .getMethodSignatures();
147:
148: for (int methodIdx = 0; methodIdx < methodNames.length; methodIdx++) {
149: checkAndScanMethod(loadedClassInfos[j],
150: methodNames[methodIdx],
151: signatures[methodIdx], false,
152: false, false);
153: }
154: } else {
155: markMethod(loadedClassInfos[j], rIdx);
156: checkAndScanMethod(loadedClassInfos[j],
157: rootMethods.methodNames[rIdx],
158: rootMethods.methodSignatures[rIdx],
159: false, false, false);
160: }
161: }
162: }
163: }
164:
165: // So that class loading is measured correctly from the beginning
166: checkAndScanMethod(
167: javaClassForName("java/lang/ClassLoader", 0),
168: "loadClass", "(Ljava/lang/String;)Ljava/lang/Class;",
169: true, false, true); // NOI18N
170:
171: return createInstrumentedMethodPack();
172: }
173:
174: public Object[] getMethodsToInstrumentUponClassLoad(
175: String className, int classLoaderId,
176: boolean threadInCallGraph) {
177: //System.out.println("*** MS2: instr. upon CL: " + className);
178: className = className.replace('.', '/').intern(); // NOI18N
179:
180: // If a class doesn't pass the current instrumentation filter, we can't immediately reject it, since there is a chance
181: // it contains some root methods. So we have to check that first.
182: boolean isRootClass = false;
183:
184: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
185: String rootClassName = rootMethods.classNames[rIdx];
186:
187: if (rootMethods.classesWildcard[rIdx]) {
188: if (matchesWildcard(Wildcards
189: .unwildPackage(rootClassName), className)) {
190: // System.out.println("(Instrumentor 2) Matched package wildcard - " + rootClassName);
191: isRootClass = true;
192:
193: break;
194: }
195: } else {
196: if (className.equals(rootClassName)) {
197: isRootClass = true;
198:
199: break;
200: }
201: }
202: }
203:
204: boolean normallyFilteredOut = !instrFilter
205: .passesFilter(className);
206:
207: if (!isRootClass) {
208: if (normallyFilteredOut) {
209: return null;
210: }
211: }
212:
213: DynamicClassInfo clazz = javaClassForName(className,
214: classLoaderId);
215:
216: if (clazz == null) {
217: return null; // Warning already issued
218: }
219:
220: initInstrMethodData();
221: instrumentClinit = threadInCallGraph;
222:
223: if (!clazz.isLoaded()) {
224: clazz.setLoaded(true);
225: addToSubclassList(clazz, normallyFilteredOut ? null : clazz); // This call may cause scanning of methods of this class, so all initialization should be done before
226:
227: // Check to see if this class has been marked as root by the user:
228: for (int rIdx = 0; rIdx < rootMethods.classNames.length; rIdx++) {
229: String rootClassName = rootMethods.classNames[rIdx];
230: boolean isMatch = false;
231:
232: if (rootMethods.classesWildcard[rIdx]) {
233: if (matchesWildcard(Wildcards
234: .unwildPackage(rootClassName), className)) {
235: // System.out.println("(Instrumentor 2) Matched package wildcard - " + rootClassName);
236: isMatch = true;
237: }
238: } else {
239: if (className.equals(rootClassName)) { // precise match
240: isMatch = true;
241: }
242: }
243:
244: if (isMatch) { //it is indeed a root class
245:
246: if (Wildcards.isPackageWildcard(rootClassName)
247: || Wildcards
248: .isMethodWildcard(rootMethods.methodNames[rIdx])) {
249: if (rootMethods.markerMethods[rIdx]) {
250: markAllMethodsMarker(clazz);
251: } else {
252: markAllMethodsRoot(clazz);
253: }
254:
255: String[] methodNames = clazz.getMethodNames();
256: String[] signatures = clazz
257: .getMethodSignatures();
258:
259: for (int methodIdx = 0; methodIdx < methodNames.length; methodIdx++) {
260: checkAndScanMethod(clazz,
261: methodNames[methodIdx],
262: signatures[methodIdx], false,
263: false, false);
264: }
265: } else {
266: markMethod(clazz, rIdx);
267: checkAndScanMethod(clazz,
268: rootMethods.methodNames[rIdx],
269: rootMethods.methodSignatures[rIdx],
270: false, false, false);
271: }
272: }
273: }
274:
275: if (!clazz.isInterface()) {
276: String[] methodNames = clazz.getMethodNames();
277: String[] methodSignatures = clazz.getMethodSignatures();
278:
279: for (int i = 0; i < methodNames.length; i++) {
280: if (clazz.isMethodReachable(i)
281: || (!normallyFilteredOut
282: && instrumentClinit && (methodNames[i] == "<clinit>"))) { // NOI18N
283: checkAndScanMethod(clazz, methodNames[i],
284: methodSignatures[i], false, false,
285: false);
286: }
287: }
288: }
289:
290: tryInstrumentSpawnedThreads(clazz);
291:
292: return createInstrumentedMethodPack();
293: } else {
294: return null;
295: }
296: }
297:
298: public Object[] getMethodsToInstrumentUponMethodInvocation(
299: String className, int classLoaderId, String methodName,
300: String methodSignature) {
301: return null; // This method is just not used with this flavour of MethodScanner
302: }
303:
304: public Object[] getMethodsToInstrumentUponReflectInvoke(
305: String className, int classLoaderId, String methodName,
306: String methodSignature) {
307: //System.out.println("*** MS2: instr. upon reflect MI: " + className + "." + methodName + methodSignature);
308: className = className.replace('.', '/').intern(); // NOI18N
309:
310: DynamicClassInfo clazz = javaClassForName(className,
311: classLoaderId);
312:
313: if (clazz == null) {
314: return null;
315: }
316:
317: initInstrMethodData();
318: instrumentClinit = true;
319:
320: methodName = methodName.intern();
321: methodSignature = methodSignature.intern();
322:
323: checkAndScanMethod(clazz, methodName, methodSignature, false,
324: false, false);
325:
326: return createInstrumentedMethodPack();
327: }
328:
329: protected void findAndMarkOverridingMethodsReachable(
330: DynamicClassInfo super Class, DynamicClassInfo subClass) {
331: String[] methodNames = super Class.getMethodNames();
332: String[] methodSignatures = super Class.getMethodSignatures();
333:
334: for (int i = 0; i < methodNames.length; i++) {
335: if (!(super Class.isMethodVirtual(i) && super Class
336: .isMethodReachable(i))) {
337: continue;
338: }
339:
340: // int idx = subClass.overridesVirtualMethod(superClass, i); - I once tried this, but with no visible effect. Strict check
341: // for whether a method with the same name and signature in subclass really overrrides a method in superclass, given all other
342: // conditions that we have already checked (e.g. that the method in superclass is not private), will only detect a pathological
343: // case when both method versions are package-private. This is rare, if ever happens at all.
344: checkAndScanMethod(subClass, methodNames[i],
345: methodSignatures[i], true, false, false);
346: }
347: }
348:
349: protected void processInvoke(DynamicClassInfo clazz,
350: boolean virtualCall, int index) {
351: byte[] savedCodeBytes = codeBytes;
352: int savedOffset = offset;
353:
354: String[] cms = clazz.getRefMethodsClassNameAndSig(index);
355:
356: if (cms == null) {
357: return; // That's how coming across our own stuff currently manifests itself, e.g. when scanning instrumented Method.invoke()
358: }
359:
360: String refClassName = cms[0];
361:
362: if (refClassName
363: .startsWith(PROFILER_SERVER_SLASHED_CLASS_PREFIX)) {
364: return; // We may come across our own stuff e.g. when scanning instrumented Method.invoke()
365: }
366:
367: int loaderId = clazz.getLoaderId();
368:
369: // Now let's check if the callee class is actually loaded at this time. If not, we just record the fact that the
370: // callee method is reachable using a placeholder class, and return
371: BaseClassInfo refClazz = loadedJavaClassOrExistingPlaceholderForName(
372: refClassName, loaderId);
373:
374: if ((refClazz == null)
375: || (refClazz instanceof PlaceholderClassInfo)) {
376: ReachableMethodPlaceholder pci = (refClazz == null) ? new ReachableMethodPlaceholder2(
377: refClassName, loaderId)
378: : (ReachableMethodPlaceholder) refClazz;
379: pci.registerReachableMethod(cms[1], cms[2]);
380:
381: if (refClazz == null) {
382: registerPlaceholder(pci);
383: }
384: } else {
385: checkAndScanMethod((DynamicClassInfo) refClazz, cms[1],
386: cms[2], virtualCall, true, true);
387: }
388:
389: offset = savedOffset;
390: codeBytes = savedCodeBytes;
391: }
392:
393: protected boolean tryInstrumentSpawnedThreads(DynamicClassInfo clazz) {
394: if (instrumentSpawnedThreads) {
395: if (clazz.implements Interface("java/lang/Runnable")
396: && (clazz.getName() != "java/lang/Thread")) { // NOI18N
397:
398: boolean res = markMethodRoot(clazz, "run", "()V"); // NOI18N
399: checkAndScanMethod(clazz, "run", "()V", false, false,
400: false); // NOI18N
401:
402: return res;
403: }
404: }
405:
406: return false;
407: }
408:
409: /**
410: * Mark the given method reachable. The boolean parameters affect this function in the following way:
411: * - virtualCall = true means that we reached this method via a "call virtual" instruction, or otherwise know that it's
412: * virtual, and want to treat it as virtual. So, for example, if checkSubclasses is true, a method with this name and
413: * signature should be looked up in subclasses of this class.
414: * - lookupInSuperIfNotFoundInThis = true means that if the given method is not found in this class, it should be looked
415: * up in its superclasses.
416: * - checkSubclasses = true means that if a method is virtual (either because virtualCall == true or because this method is
417: * really defined in this class and marked as virtual), methods that override it in subclasses of this class should also
418: * be marked reachable.
419: */
420: private boolean checkAndScanMethod(DynamicClassInfo clazz,
421: String methodName, String methodSignature,
422: boolean virtualCall, boolean lookupInSuperIfNotFoundInThis,
423: boolean checkSubclasses) {
424: String className = clazz.getName();
425: int idx = clazz.getMethodIndex(methodName, methodSignature);
426:
427: if (idx != -1) {
428: byte[] bytecode = null;
429:
430: if (!clazz.isMethodReachable(idx)) {
431: clazz.setMethodReachable(idx);
432:
433: if (!clazz.isMethodStatic(idx)
434: && !clazz.isMethodPrivate(idx)
435: && !clazz.isMethodFinal(idx)
436: && (methodName != "<init>")) {
437: clazz.setMethodVirtual(idx); // NOI18N
438: }
439:
440: if (clazz.isMethodNative(idx)
441: || clazz.isMethodAbstract(idx)
442: || (!clazz.isMethodRoot(idx)
443: && !clazz.isMethodMarker(idx) && !instrFilter
444: .passesFilter(className))
445: || (className == "java/lang/Object")) { // NOI18N // Actually, just the Object.<init> method?
446: clazz.setMethodUnscannable(idx);
447: } else {
448: bytecode = clazz.getMethodBytecode(idx);
449:
450: if ((dontInstrumentEmptyMethods && isEmptyMethod(bytecode))
451: || (dontScanGetterSetterMethods && isGetterSetterMethod(bytecode))) {
452: clazz.setMethodUnscannable(idx);
453: } else {
454: if (isLeafMethod(bytecode)) {
455: clazz.setMethodLeaf(idx);
456: }
457: }
458: }
459: } else { // Method is already marked reachable - could be done for a not yet loaded class
460:
461: if (clazz.isMethodScanned(idx)
462: || clazz.isMethodUnscannable(idx)) {
463: return true;
464: } else {
465: bytecode = clazz.getMethodBytecode(idx);
466: }
467: }
468:
469: if (!clazz.isLoaded()) {
470: return true; // No need to check subclasses because there are no loaded subclasses if this class itself is not loaded
471: // Class is loaded, method is reachable and not unscannable are sufficient conditions for instrumenting method
472: }
473:
474: if (!clazz.isMethodUnscannable(idx)) {
475: markClassAndMethodForInstrumentation(clazz, idx);
476: clazz.setMethodScanned(idx);
477: //if (!lookupInSuperIfNotFoundInThis && !checkSubclasses) System.out.println("Gonna scan potentially reachable " + className + "." + methodName + methodSignature);
478: scanBytecode(clazz, bytecode);
479: }
480: }
481:
482: if (checkSubclasses
483: && (((idx != -1) && clazz.isMethodVirtual(idx)) || ((idx == -1) && virtualCall))) {
484: ArrayList subclasses = clazz.getSubclasses();
485:
486: if (subclasses != null) {
487: for (int i = 0; i < subclasses.size(); i++) {
488: //System.out.println("Gonna scan subclass " + subclassNames.get(i) + " of class " + className + " for method " + methodName);
489: // DynamicClassInfo subClass = javaClassForName((String) subclassNames.get(i));
490: // if ((idx != -1 && subClass.overridesVirtualMethod(clazz, idx) != -1) || idx == -1) - see the comment in findAndMarkOverridingMethods()
491: // on why this seems to be of no use.
492: checkAndScanMethod((DynamicClassInfo) subclasses
493: .get(i), methodName, methodSignature,
494: virtualCall, false, false);
495: }
496: }
497: }
498:
499: if (idx != -1) {
500: return true;
501: }
502:
503: if (!lookupInSuperIfNotFoundInThis) {
504: return false;
505: }
506:
507: // Method not defined in this class - try superclass, and if not successful - all superinterfaces
508: if (!clazz.isInterface()) {
509: DynamicClassInfo super Clazz = clazz.getSuperClass();
510:
511: if ((super Clazz != null)
512: && (super Clazz.getName() != clazz.getName())) {
513: if (checkAndScanMethod(super Clazz, methodName,
514: methodSignature, virtualCall, true, false)) {
515: return true;
516: }
517: }
518: }
519:
520: DynamicClassInfo[] interfaces = clazz.getSuperInterfaces();
521:
522: if (interfaces == null) {
523: return false;
524: }
525:
526: for (int i = 0; i < interfaces.length; i++) {
527: DynamicClassInfo intfClazz = interfaces[i];
528:
529: if (intfClazz == null) {
530: continue;
531: }
532:
533: if (checkAndScanMethod(intfClazz, methodName,
534: methodSignature, virtualCall, true, false)) {
535: return true;
536: }
537: }
538:
539: return false;
540: }
541:
542: //----------------------------------- Private implementation ------------------------------------------------
543: private DynamicClassInfo[] preGetInitialMethodsToInstrument(
544: String[] loadedClasses, int[] loadedClassLoaderIds,
545: byte[][] cachedClassFileBytes) {
546: //System.out.println("*** MS2: instr. initial");
547: resetLoadedClassData();
548: storeClassFileBytesForCustomLoaderClasses(loadedClasses,
549: loadedClassLoaderIds, cachedClassFileBytes);
550: initInstrMethodData();
551:
552: DynamicClassInfo[] loadedClassInfos = null;
553: //if (instrumentSpawnedThreads) tmpClassInfos = new DynamicClassInfo[loadedClasses.length];
554: loadedClassInfos = new DynamicClassInfo[loadedClasses.length]; //EJB Work: removed the condition in the above line as we need to return the temp array anyway (used to check for multiple roots, see the overloaded getInitialMethodsToInstrument )
555:
556: for (int i = 0; i < loadedClasses.length; i++) {
557: String className = loadedClasses[i].replace('.', '/')
558: .intern(); // NOI18N
559: DynamicClassInfo clazz = javaClassForName(className,
560: loadedClassLoaderIds[i]);
561:
562: if (clazz == null) {
563: continue;
564: }
565:
566: clazz.setLoaded(true);
567: addToSubclassList(clazz, clazz);
568: loadedClassInfos[i] = clazz;
569: }
570:
571: return loadedClassInfos;
572: }
573: }
|