001: package tide.bytecode.asm;
002:
003: import snow.utils.storage.FileUtils;
004: import tide.project.ProjClass;
005: import tide.editor.MainEditorFrame;
006: import tide.project.ClassFilesManager;
007: import org.objectweb.asm.signature.*;
008: import java.util.*;
009: import java.io.*;
010: import org.objectweb.asm.*;
011: import edu.umd.cs.findbugs.annotations.When;
012:
013: /** Remarks: @edu.umd.cs.findbugs.annotations.OverrideMustInvoke has retention Class => present in class but not in runtime => not accessible via reflection ! (classloader)
014: * Remark: @Override has retention source => only present in source, cannot be detected here. => javaCC
015: *
016: */
017: public final class OverrideMustInvokeChecker extends ClassVisitorAd {
018: String classJavaName, sourceName;
019: String super ClassJavaName;
020: final boolean debug = false;
021: final boolean searchDeclarationsMode;
022:
023: // annotated with @edu.umd.cs.findbugs.annotations.OverrideMustInvoke and SubclassMustInvoke
024: // found in the search mode
025: final List<ASMethod> annotatedOverrideMustInvoke = new ArrayList<ASMethod>();
026: // own creation !
027: final List<ASMethod> annotatedSubclassMustInvoke = new ArrayList<ASMethod>();
028:
029: final List<String> parentsJavaNames = new ArrayList<String>();
030:
031: private int firstClassLine = -1;
032:
033: // missing or misplaced, see the failureMessage String in the ASMethod
034: final List<ASMethod> missingSuperInvocation = new ArrayList<ASMethod>();
035:
036: // set this for the second pass
037: final Map<String, ASMethod> methodsToLookFor_OverrideMustInvoke = new HashMap<String, ASMethod>();
038: final Map<String, ASMethod> methodsToLookFor_SubclassMustInvoke = new HashMap<String, ASMethod>();
039:
040: // parent and child...
041: final List<ASMethod> allMethods = new ArrayList<ASMethod>();
042:
043: OverrideMustInvokeChecker(boolean searchDeclarationsMode) {
044: this .searchDeclarationsMode = searchDeclarationsMode;
045: }
046:
047: @Override
048: public void visitSource(final String source, final String debugg) {
049: sourceName = source;
050: }
051:
052: @Override
053: public void visit(final int version, final int access,
054: final String name, final String signature,
055: final String super Name, final String[] interfaces) {
056: this .classJavaName = name.replace('/', '.');
057: this .super ClassJavaName = super Name != null ? super Name
058: .replace('/', '.') : null;
059: if (debug) {
060: System.out.println("analysing class " + name);
061: System.out.println("superName=" + super Name);
062: }
063: }
064:
065: @Override
066: public MethodVisitor visitMethod(final int access,
067: final String name, final String desc,
068: final String signature, final String[] exceptions) {
069: //System.out.println("method "+access+", "+name+", "+desc+", "+signature);
070: // creates a new one for each new visited method.
071: return new MethodVis(name, desc);
072: }
073:
074: @Override
075: public void visitEnd() {
076:
077: if (debug)
078: System.out.println("END class " + classJavaName);
079: }
080:
081: /* test
082: public static void main(String[] args) throws Exception
083: {
084:
085: System.out.println("Annotated with SubclassMustInvoke : "
086: + searchMethodsWithAnnotationSubclassMustInvoke(new FileInputStream("c:/classes/tide/tide/bytecode/Aaa.class")));
087: }
088:
089: /** Finds the methods marked with OverrideMustInvoke. Only the passed class will be analysed.
090: * Caller must iterate over all the parent classes chain, up to Object !
091: *
092: public static List<ASMethod> searchMethodsWithAnnotationOverrideMustInvoke(InputStream klassStream) throws Exception
093: {
094: ClassReader cr = new ClassReader(klassStream);
095: OverrideMustInvokeChecker ch = new OverrideMustInvokeChecker(true);
096: cr.accept(ch, 0);
097: return ch.annotatedOverrideMustInvoke;
098: }
099:
100: /**Finds the methods marked with SubclassMustInvoke. Only the passed class will be analysed.
101: * Caller must iterate over all the parent classes chain, up to Object !
102: *
103: public static List<ASMethod> searchMethodsWithAnnotationSubclassMustInvoke(InputStream klassStream) throws Exception
104: {
105: ClassReader cr = new ClassReader(klassStream);
106: OverrideMustInvokeChecker ch = new OverrideMustInvokeChecker(true);
107: cr.accept(ch, 0);
108: return ch.annotatedSubclassMustInvoke;
109: }*/
110:
111: /** Analyse.
112: */
113: public static List<ASMethod> searchMissingSuperCalls(
114: final String name) throws Exception {
115: //System.out.println("OMIC: Analysing "+name);
116: ClassFilesManager cfm = MainEditorFrame.instance
117: .getActualProject().getClassFilesManager();
118: // null at begining, when proj not already fully loaded
119: if (cfm == null)
120: return null;
121:
122: ProjClass pc = cfm.getClassExact(name);
123: if (pc == null) {
124: System.out.println("Class not found: " + name);
125: return null;
126: }
127:
128: // start from parent class !
129:
130: String super Name = null;
131:
132: InputStream is = pc.getClassContent();
133: try {
134: // only to fetch the super !
135: ClassReader cr = new ClassReader(is);
136: OverrideMustInvokeChecker ch = new OverrideMustInvokeChecker(
137: false); // either
138: cr.accept(ch, 0);
139:
140: super Name = ch.super ClassJavaName;
141: } catch (Exception e) {
142: System.out.println("Cannot search super of " + name + ": "
143: + e.getMessage());
144: } finally {
145: FileUtils.closeIgnoringExceptions(is);
146: }
147:
148: // and climb the whole chain, up to java.lang.Object
149: //
150: List<ASMethod> overridersMustInvoke = new ArrayList<ASMethod>();
151: List<ASMethod> subclassMustInvoke = new ArrayList<ASMethod>();
152:
153: String classname = super Name;
154: List<String> parentsJavaNames = new ArrayList<String>();
155: while (classname != null) {
156: parentsJavaNames.add(classname);
157:
158: try {
159: //System.out.println("Look in "+classname);
160: ProjClass pc2 = cfm.getClassExact(classname);
161: InputStream is2 = pc2.getClassContent();
162: try {
163: // only to fetch the super !
164: ClassReader cr = new ClassReader(is2);
165: OverrideMustInvokeChecker ch = new OverrideMustInvokeChecker(
166: true); // search
167: cr.accept(ch, 0);
168: overridersMustInvoke
169: .addAll(ch.annotatedOverrideMustInvoke);
170: subclassMustInvoke
171: .addAll(ch.annotatedSubclassMustInvoke);
172:
173: classname = ch.super ClassJavaName;
174:
175: if (classname == null)
176: break;
177: } catch (Exception e) {
178: System.out.println("Cannot search super of "
179: + classname + ": " + e.getMessage());
180: e.printStackTrace();
181: throw e;
182: } finally {
183: FileUtils.closeIgnoringExceptions(is2);
184: }
185: } catch (Exception e) {
186: break;
187: }
188: }
189:
190: if (overridersMustInvoke.isEmpty()) {
191: // nothing to check !
192: return null;
193: }
194:
195: //System.out.println("MethodDeclaringOverrideMustInvoke: "+overridersMustInvoke);
196:
197: // and now, look in the passed class
198: //
199: is = pc.getClassContent();
200: try {
201: ClassReader cr = new ClassReader(is);
202: OverrideMustInvokeChecker ch = new OverrideMustInvokeChecker(
203: false); // analyse
204: for (final ASMethod asm : overridersMustInvoke) {
205: ch.methodsToLookFor_OverrideMustInvoke.put(asm
206: .getSignature(false), asm);
207: }
208:
209: for (final ASMethod asm : subclassMustInvoke) {
210: ch.methodsToLookFor_SubclassMustInvoke.put(asm
211: .getSignature(false), asm);
212: }
213:
214: ch.parentsJavaNames.addAll(parentsJavaNames);
215:
216: // the job is done herein
217: cr.accept(ch, 0);
218:
219: if (!ch.missingSuperInvocation.isEmpty()) {
220: System.out
221: .println("MISSING super call, failed contract @OverrideMustInvoke:");
222: for (ASMethod asi : ch.missingSuperInvocation) {
223: System.out.println("" + asi
224: + " does not call super.");
225: }
226: }
227:
228: // look at the second test
229: for (ASMethod asm : subclassMustInvoke) {
230: if (!asm.isCalledInSubclass) {
231: asm.setFailure("Subclass " + ch.classJavaName
232: + " doesn't call " + asm.getSignature(true)
233: + " as requested by @SubclassMustInvoke",
234: "Failing @SubclassMustInvoke");
235: asm.begLine = ch.firstClassLine;
236: ch.missingSuperInvocation.add(asm);
237: }
238: }
239:
240: return ch.missingSuperInvocation;
241:
242: } catch (Exception e) {
243: System.out.println("Cannot analyse " + name + ": "
244: + e.getMessage());
245: } finally {
246: FileUtils.closeIgnoringExceptions(is);
247: }
248:
249: return null;
250:
251: }
252:
253: // this visitor, called for each method, writes and reads in the enclosing class
254: private class MethodVis extends MethodVisitorAd {
255: final ASMethod met;
256:
257: boolean mustCheckForCallToSuper = false;
258: boolean placeIsGood = false;
259: String callToCheckOnSuper = null;
260:
261: // when super should be called, within the method
262: When when = When.ANYTIME;
263:
264: public MethodVis(String name, String signature) // Rem: <init> for the constructor
265: {
266: super (name, signature);
267:
268: met = new ASMethod(classJavaName, sourceName, name,
269: signature);
270:
271: allMethods.add(met);
272:
273: if (!searchDeclarationsMode) {
274: String sig = met.getSignature(false);
275: if (methodsToLookFor_OverrideMustInvoke
276: .containsKey(sig)) {
277: //System.out.println("\nTo check: "+met+" in "+classJavaName);
278: //System.out.println("Overrides and must call: "+methodsToLookForOverriders.get(sig));
279: mustCheckForCallToSuper = true;
280: callToCheckOnSuper = methodsToLookFor_OverrideMustInvoke
281: .get(sig).getSignature(false); // hello(int)
282: // (true will be to check calls to exactely the method that overrides, failing on heritances with several generations...
283: when = methodsToLookFor_OverrideMustInvoke.get(sig).annotationWhen;
284: //setDebugMode(true);
285: }
286: }
287:
288: }
289:
290: @Override
291: public void visitLineNumber(final int line, final Label start) {
292:
293: if (firstClassLine < 0)
294: firstClassLine = line;
295:
296: if (line >= 0 && met.begLine < 0)
297: met.begLine = line;
298: }
299:
300: @Override
301: public AnnotationVisitor visitAnnotation(final String desc,
302: final boolean visible) {
303: //System.out.println("MP:visitAnnotation desc="+desc);
304:
305: if (desc.contains("OverrideMustInvoke")) {
306: annotatedOverrideMustInvoke.add(met);
307: // here: the annotation param When is optionally coming if present
308: return new WhenAnnParVis(this );
309: }
310:
311: if (desc.contains("SubclassMustInvoke")) {
312: annotatedSubclassMustInvoke.add(met);
313: }
314:
315: // no args read
316: return AnnotationVisitorAd.emptyVis;
317: }
318:
319: @Override
320: public void visitEnd() {
321: //System.out.println("when="+ when);
322: if (callToCheckOnSuper != null) {
323:
324: if (!met.callsOverridedMethod) {
325: //System.out.println("MISSING CALL !");
326: met
327: .setFailure(
328: "The parent method '"
329: + met.methodName
330: + "' is annotated with @overrideMustInvoke but overrider doesn't invoke it.",
331: "Failing @overrideMustInvoke");
332: missingSuperInvocation.add(met);
333: } else {
334: // it calls, but we must check the place...
335: //System.out.println("MATCH, place="+when+" "+callsAfter);
336:
337: if (when == When.LAST) {
338: placeIsGood = callsAfter == 0;
339: }
340:
341: if (!placeIsGood) {
342: met
343: .setFailure(
344: "Misplaced super call to '"
345: + met.methodName
346: + "', @overrideMustInvoke specifies where="
347: + when,
348: "Failing @overrideMustInvoke (misplacement)");
349: missingSuperInvocation.add(met);
350: }
351: }
352: }
353: }
354:
355: //boolean firstVisited = true;
356: int callsBefore = 0;
357: int callsAfter = 0;
358: boolean aCallWasFound = false;
359:
360: /** All method calls within the method are here.
361: * example: System.out.println("");
362: */
363: @Override
364: public void visitMethodInsn(final int opcode,
365: final String owner, final String name, final String desc) {
366: // constr call: visitMethodInsn <init> ()V java/lang/StringBuilder 183
367: // meth call: visitMethodInsn append (Ljava/lang/String;)Ljava/lang/StringBuilder; java/lang/StringBuilder 182
368:
369: //System.out.println("visitMethodInsn "+name+" "+desc+" "+owner+" "+opcode);
370: //System.out.println(" "+ASMUtils.createArgsListFromSig(desc));
371:
372: if (!methodsToLookFor_SubclassMustInvoke.isEmpty()) {
373:
374: String sig = name + "("
375: + ASMUtils.createArgsListFromSig(desc) + ")";
376: if (methodsToLookFor_SubclassMustInvoke
377: .containsKey(sig)) {
378: String jn = owner != null ? owner.replace('/', '.')
379: : "";
380: if (methodsToLookFor_SubclassMustInvoke.get(sig)
381: .getSignature(true).equals(jn + "." + sig)) {
382: // System.out.println("FOUND: "+sig);
383: methodsToLookFor_SubclassMustInvoke.get(sig).isCalledInSubclass = true;
384: }
385: }
386: }
387:
388: if (callToCheckOnSuper != null && !met.callsOverridedMethod) {
389: //
390: String jn = owner != null ? owner.replace('/', '.')
391: : "";
392: String cc = jn + "." + name + "("
393: + ASMUtils.createArgsListFromSig(desc) + ")";
394: //System.out.println("look "+cc);
395:
396: boolean found = false;
397: for (String pi : parentsJavaNames) {
398: if (cc.equals(pi + "." + callToCheckOnSuper)) {
399: found = true;
400: break;
401: }
402: }
403:
404: if (found) {
405: aCallWasFound = true;
406:
407: met.callsOverridedMethod = true;
408:
409: if (when == When.ANYTIME) {
410: placeIsGood = true; // always
411: } else if (when == When.FIRST) {
412: placeIsGood = (callsBefore == 0);
413: }
414:
415: //System.out.println("MATCH, place="+when);
416:
417: return; // otherwise, the counts are false
418: }
419: /* else
420: {
421: //System.out.println("not match "+callToCheck);
422: }*/
423: }
424:
425: if (aCallWasFound) {
426: callsAfter++;
427: } else {
428: callsBefore++;
429: }
430: }
431:
432: // also count field calls ?? no
433: /*
434: @Override public void visitFieldInsn(
435: final int opcode,
436: final String owner,
437: final String name,
438: final String desc)
439: {
440: if(debug) System.out.println("visitFieldInsn "+name);
441: if(aCallWasFound)
442: {
443: callsAfter++;
444: }
445: else
446: {
447: callsBefore++;
448: }
449: }
450: */
451:
452: }
453:
454: /** When Annotation Parameter Visitor.
455: */
456: class WhenAnnParVis extends AnnotationVisitorAd {
457: final MethodVis callback;
458:
459: public WhenAnnParVis(MethodVis callback) {
460: this .callback = callback;
461: debug = false;
462: }
463:
464: /**
465: * Visits an enumeration value of the annotation.
466: *
467: * @param name the value name.
468: * @param desc the class descriptor of the enumeration class.
469: * @param value the actual enumeration value.
470: */
471: @Override
472: public void visitEnum(String name, String desc, String value) {
473: // visit annotation enum value, Ledu/umd/cs/findbugs/annotations/When;: ANYTIME
474:
475: if (debug)
476: System.out.println("visit annotation enum " + name
477: + ", " + desc + ": " + value);
478: if ("ANYTIME".equalsIgnoreCase(value)) {
479: callback.met.annotationWhen = When.ANYTIME;
480: } else if ("FIRST".equalsIgnoreCase(value)) {
481: callback.met.annotationWhen = When.FIRST;
482: } else if ("LAST".equalsIgnoreCase(value)) {
483: callback.met.annotationWhen = When.LAST;
484: }
485: }
486: }
487:
488: }
|