001: package tide.staticanalysis;
002:
003: import tide.project.ProjectSettings;
004: import tide.bytecode.asm.RecurseChecker;
005: import tide.classsyntax.RefClassUtils;
006: import javax.swing.JOptionPane;
007: import tide.project.ClassFilesManager;
008: import tide.editor.linemessages.MessagesTable;
009: import tide.bytecode.asm.ASMethod;
010: import tide.bytecode.asm.OverrideMustInvokeChecker;
011: import java.lang.annotation.Annotation;
012: import snow.utils.StringUtils;
013: import tide.editor.linemessages.LineMessagesManager;
014: import javaparser.TreeUtils;
015: import tide.utils.SyntaxUtils;
016: import javaparser.Parameter;
017: import tide.classsyntax.ClassUtils;
018: import tide.utils.BytecodeUtils;
019: import snow.utils.gui.ProgressModalDialog;
020: import tide.editor.MainEditorFrame;
021: import javaparser.ParserTreeNode;
022: import javaparser.MethodNode;
023: import javaparser.ClassNode;
024: import javaparser.TypeNode;
025: import tide.syntaxtree.SimplifiedSyntaxTree2;
026: import javaparser.RAWSyntaxTree;
027: import javaparser.javacc_gen.JavaParser;
028: import java.io.StringReader;
029: import java.lang.reflect.*;
030: import tide.classsyntax.SingleClassLoader;
031: import tide.sources.SourceFile;
032: import java.util.*;
033:
034: /**
035: * [Oct2007]: first motivation: enforce Override declarations !
036: * => after JLint Lint4J and Xlint: Tlint is born !
037: * no other tool find them. They must use source (cause of annotation retention that limit existence to source)
038: * TODO: also look in anonymous classes, typically adapters (or search for new XXAdapter(){
039: TODO2: check @edu.umd.cs.findbugs.annotations.OverrideMustInvoke
040: TODO3: make HTMLEditorKit.ParserCallback override correctly handled
041: */
042: public final class StaticAnalysis {
043:
044: static private final boolean debug = false;
045:
046: // Set in StaticAnalysisSettingsDialog, saved here, in the project settings
047:
048: static boolean autoApplyOnChangedClasses = false;
049:
050: static boolean detectMissingOverrides = true; // or overloads
051: static boolean detectStaticOverloads = true;
052: static boolean detectNearOverrides = true; // = overload
053: static boolean detectPrivateOverloads = true;
054:
055: static boolean detectOverrideMustInvoke = true;
056:
057: static boolean detectRecurseAnn = true;
058: static boolean detectMissingRecurseAnn = true;
059:
060: static boolean detectImplementsAnn = true; // attempt
061:
062: // Settings Persistance
063:
064: /** Should be called at project changes. (load)
065: */
066: public static void readSettings(final ProjectSettings proj) {
067: autoApplyOnChangedClasses = proj.getBooleanProperty(
068: "SA_autoApplyOnChangedClasses", false);
069:
070: detectMissingOverrides = proj.getBooleanProperty(
071: "SA_detectMissingOverrides", true);
072: detectStaticOverloads = proj.getBooleanProperty(
073: "SA_detectStaticOverloads", true);
074: detectNearOverrides = proj.getBooleanProperty(
075: "SA_detectNearOverrides", true);
076: detectPrivateOverloads = proj.getBooleanProperty(
077: "SA_detectPrivateOverloads", true);
078:
079: detectOverrideMustInvoke = proj.getBooleanProperty(
080: "SA_detectOverrideMustInvoke", true);
081:
082: detectRecurseAnn = proj.getBooleanProperty(
083: "SA_detectRecurseAnn", true);
084: detectMissingRecurseAnn = proj.getBooleanProperty(
085: "SA_detectMissingRecurseAnn", true);
086:
087: detectImplementsAnn = proj.getBooleanProperty(
088: "SA_detectImplementsAnn", true);
089: }
090:
091: /** Should be called on changes only => from the dialog.
092: */
093: public static void saveSettings(final ProjectSettings proj) {
094: proj.setBooleanProperty("SA_autoApplyOnChangedClasses",
095: autoApplyOnChangedClasses);
096:
097: proj.setBooleanProperty("SA_detectMissingOverrides",
098: detectMissingOverrides);
099: proj.setBooleanProperty("SA_detectStaticOverloads",
100: detectStaticOverloads);
101: proj.setBooleanProperty("SA_detectNearOverrides",
102: detectNearOverrides);
103: proj.setBooleanProperty("SA_detectPrivateOverloads",
104: detectPrivateOverloads);
105:
106: proj.setBooleanProperty("SA_detectOverrideMustInvoke",
107: detectOverrideMustInvoke);
108:
109: proj
110: .setBooleanProperty("SA_detectRecurseAnn",
111: detectRecurseAnn);
112: proj.setBooleanProperty("SA_detectMissingRecurseAnn",
113: detectMissingRecurseAnn);
114:
115: proj.setBooleanProperty("SA_detectImplementsAnn",
116: detectImplementsAnn);
117: }
118:
119: private StaticAnalysis() {
120: }
121:
122: @edu.umd.cs.findbugs.annotations.OverrideMustInvoke
123: public static void analyse(final List<SourceFile> srcs) {
124:
125: if (MainEditorFrame.instance.getActualProject()
126: .getClassFilesManager() == null) {
127: // some info for the impatients:
128: JOptionPane
129: .showMessageDialog(
130: null,
131: "The project classes are not already loaded, try again later..."
132: + "\n\nNote:\n The JDK and project classes are loaded lazily in background"
133: + "\n to make tIDE immediately responsive for editing at startup."
134: + "\n Advanced features such as the static analysis however requires full loading.",
135: "Warning", JOptionPane.ERROR_MESSAGE);
136: return;
137: }
138:
139: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
140: .clearDocument();
141: MainEditorFrame.instance.outputPanels.selectToolsTab(false);
142: String title = "TLint analysis of " + srcs.size() + " source"
143: + (srcs.size() == 1 ? "" : "s");
144: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
145: .appendLine(title);
146:
147: final ProgressModalDialog pmd = new ProgressModalDialog(
148: MainEditorFrame.instance, title, false);
149: pmd.setProgressBounds(srcs.size());
150: pmd.start();
151:
152: Thread t = new Thread() {
153: public void run() {
154: int w = 0;
155: try {
156: for (SourceFile sfi : srcs) {
157: if (pmd.getWasCancelled()) {
158: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
159: .appendErrorLine("\nAnalysis cancelled.");
160: return;
161: }
162: pmd.incrementProgress(1);
163:
164: if (w > 0)
165: pmd.setProgressComment("" + w + " issue"
166: + (w == 1 ? "" : "s")
167: + " so far...");
168:
169: try {
170: w += analyse(sfi);
171: } catch (Exception e) {
172: e.printStackTrace();
173: } catch (Error e) {
174: e.printStackTrace();
175: }
176: }
177: } finally {
178: pmd.closeDialog();
179: LineMessagesManager.getInstance().refreshView();
180: }
181:
182: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
183: .appendLine("\n\n"
184: + w
185: + " warning"
186: + (w == 1 ? "" : "s")
187: + " produced"
188: + (w > 0 ? " (see messages tab)." : "."));
189: if (w > 0) {
190: MessagesTable.getInstance().setShowLatest();
191: MainEditorFrame.instance.outputPanels
192: .select_LineMessages();
193: }
194: }
195: };
196: t.start();
197: }
198:
199: public static int analyse(final SourceFile si) throws Exception {
200: int w = 0;
201: StringReader sr = new StringReader(si.getContent());
202: JavaParser pa = new JavaParser(sr);
203: pa.disable_tracing();
204: RAWSyntaxTree st = new RAWSyntaxTree(si.getJavaName());
205: pa.parserOutputProcessor = st;
206: pa.CompilationUnit();
207:
208: final SimplifiedSyntaxTree2 sst = new SimplifiedSyntaxTree2(
209: st.root, si.getJavaName());
210:
211: LineMessagesManager.getInstance().removeMessagesFor(
212: si.getJavaName(), TintLineMessage.class);
213:
214: for (TypeNode it : sst.getAllTypes()) {
215: if (it instanceof ClassNode) {
216: w += analyse(si, (ClassNode) it, si.javaFile.getName());
217: }
218: /*else
219: {
220: //System.out.println("Not a class: "+it.getClass()); ==> "Not a class: class javaparser.EnumNode"
221:
222: }*/
223: }
224: return w;
225: }
226:
227: public static int getNumberOfArgsFromSimplified1Sign(String s) {
228: try {
229: int pos = s.lastIndexOf('%');
230: if (pos < 0)
231: return 0;
232: return Integer.parseInt(s.substring(pos + 1));
233: } catch (NumberFormatException nfe) {
234: nfe.printStackTrace();
235: return 0;
236: }
237: }
238:
239: /** Just use name + "%" + number of args.
240: * Useful to detect collisions, that may give hints on bad design.
241: */
242: public static String getSignatureSimplified1(Method mh) {
243: return mh.getName() + "%" + mh.getParameterTypes().length;
244: }
245:
246: /** Name +"("+ list of arg types simple names) (String,double[],..) no spaces, no generics.
247: */
248: public static String getSignatureSimplified2(Method mh) {
249: if (mh.getParameterTypes().length == 0)
250: return mh.getName() + "()";
251:
252: StringBuilder sb = new StringBuilder(mh.getName() + "(");
253: for (Class pari : mh.getParameterTypes()) {
254: sb.append(pari.getSimpleName());
255: sb.append(",");
256: }
257: sb.setLength(sb.length() - 1); // remove last ","
258: sb.append(")");
259:
260: return sb.toString();
261: }
262:
263: /** Name +"("+ list of arg types simple names) (String,double[],..) no spaces, no generics.
264: */
265: public static String getSignatureSimplified2Gen(Method mh,
266: Map<String, String> claParamTypesForName) {
267: //System.out.println(""+Arrays.asList(cla.getTypeParameters())); // [T]
268: //System.out.println(""+mh.toGenericString()); // "public abstract int java.lang.Comparable.compareTo(T)"
269:
270: if (mh.getParameterTypes().length == 0)
271: return mh.getName() + "()";
272:
273: StringBuilder sb = new StringBuilder(mh.getName() + "(");
274: for (Type pari : mh.getGenericParameterTypes()) {
275: if (pari instanceof Class) {
276: Class ci = (Class) pari;
277: sb.append(ci.getSimpleName());
278: } else {
279: String parN = "" + pari; // ex: T
280: sb.append(claParamTypesForName.get(parN));
281: }
282:
283: sb.append(",");
284: }
285: sb.setLength(sb.length() - 1); // remove last ","
286: sb.append(")");
287:
288: return sb.toString();
289: }
290:
291: /** Search for missing Overrides (also static !). (EASY simplif: only count args instead full sign.)
292: * Not possible in bytecode! java.lang.Override.class has source retention => never present in class !
293: */
294: public static int analyse(SourceFile si, ClassNode tn,
295: String simpleFileName) throws Exception {
296: int w = 0;
297: final List<MethodNode> methods = new ArrayList<MethodNode>();
298:
299: //ASM:
300: if (detectOverrideMustInvoke) {
301: List<ASMethod> miss = OverrideMustInvokeChecker
302: .searchMissingSuperCalls(tn.getJavaFullName());
303: if (miss != null && !miss.isEmpty()) {
304: for (final ASMethod asi : miss) {
305: //String mess =
306: TintLineMessage tm = new TintLineMessage(si
307: .getJavaName(), asi.getFailureMessage(),
308: asi.getFailureCategory(), asi.begLine,
309: System.currentTimeMillis(),
310: asi.issuePriority);
311: LineMessagesManager.add(tm);
312: w++;
313: }
314: }
315: }
316:
317: //ASM:
318: if (detectRecurseAnn) {
319: List<ASMethod> miss = RecurseChecker
320: .searchMissingRecurseCalls(tn.getJavaFullName(),
321: detectMissingRecurseAnn);
322: if (miss != null && !miss.isEmpty()) {
323: for (final ASMethod asi : miss) {
324: TintLineMessage tm = new TintLineMessage(si
325: .getJavaName(), asi.getFailureMessage(),
326: asi.getFailureCategory(), asi.begLine,
327: System.currentTimeMillis(),
328: asi.issuePriority);
329: LineMessagesManager.add(tm);
330: w++;
331: }
332: }
333: }
334:
335: Set<String> methodsDeclOverr = new HashSet<String>(); // name+"%"+nargs, used to detect more possible issues
336: Set<String> methodsDeclOverr2 = new HashSet<String>(); // name+"("+ simple javanames list)
337: Map<String, MethodNode> methodsDeclImpl2 = new HashMap<String, MethodNode>(); // name+"("+ simple javanames list)
338:
339: List<ParserTreeNode> methAndFields = TreeUtils
340: .getAllMethodsAndFieldsForType(tn);
341:
342: for (ParserTreeNode ti : methAndFields) {
343: if (ti instanceof MethodNode) {
344: MethodNode mi = (MethodNode) ti;
345: methods.add(mi);
346: if (mi.override_) {
347: methodsDeclOverr.add(mi.getSignatureSimplified1());
348: //System.out.println("T>"+getSignatureSimplified2(mi));
349: methodsDeclOverr2.add(mi.getSignatureSimplified2());
350: }
351:
352: if (mi.implements _) {
353: methodsDeclImpl2.put(mi.getSignatureSimplified2(),
354: mi);
355: //System.out.println("declare @Implements: "+mi.getSignatureSimplified2());
356: }
357: }
358: }
359:
360: String jn = tn.getJavaFullName();
361:
362: SingleClassLoader scl = SingleClassLoader
363: .createSingleClassLoader();
364: Class cl = scl.loadClass(jn);
365: if (cl == null)
366: throw new Exception("no class found for " + jn);
367:
368: List<Method> declared = new ArrayList<Method>();
369: for (Method mi : cl.getDeclaredMethods()) // also private
370: {
371: declared.add(mi);
372: }
373:
374: List<Method> herited = new ArrayList<Method>();
375:
376: Class par = cl.getSuperclass();
377: while (par != null) {
378: for (Method mi : par.getDeclaredMethods()) // also privates !
379: {
380: herited.add(mi);
381: }
382: par = par.getSuperclass();
383: }
384:
385: if (detectImplementsAnn && !methodsDeclImpl2.isEmpty()) {
386: // collect the interfaces
387: List<Type> allints = new ArrayList<Type>();
388: allints.addAll(Arrays.asList(cl.getGenericInterfaces()));
389: par = cl.getSuperclass();
390: while (par != null) {
391: allints.addAll(Arrays
392: .asList(par.getGenericInterfaces()));
393: par = par.getSuperclass();
394: }
395:
396: //System.out.println("All interfaces (incl parent): "+allints);
397: ft: for (Type t : allints) {
398: Class cla = null;
399: Map<String, String> claParamTypesForName = new HashMap<String, String>(); // {T,String}
400:
401: if (t instanceof ParameterizedType) {
402: ParameterizedType pt = (ParameterizedType) t;
403: // toString: "java.lang.Comparable<java.lang.String>"
404: /*System.out.println("PT "+t);
405: System.out.println("Targ: "+Arrays.asList(pt.getActualTypeArguments())); // [class java.lang.String]
406: System.out.println("rt="+pt.getRawType()); // interface java.lang.Comparable
407: System.out.println("ownt="+pt.getOwnerType()); // null
408: */
409: cla = (Class) pt.getRawType();
410:
411: List l1 = Arrays.asList(cla.getTypeParameters()); // [T]
412: List<Type> l2 = Arrays.asList(pt
413: .getActualTypeArguments()); // [class java.lang.String]
414: for (int i = 0; i < l1.size(); i++) {
415: claParamTypesForName.put("" + l1.get(i),
416: RefClassUtils.simpleName(l2.get(i)));
417: }
418:
419: } else if (t instanceof Class) {
420: cla = (Class) t;
421: System.out.println("\nClass " + cla);
422: } else {
423: System.out.println("?? " + t);
424: }
425:
426: if (cla != null) {
427:
428: for (Method dmi : cla.getDeclaredMethods()) // maybe empty, like SwingConstants
429: {
430: // Tostring => "public abstract void java.awt.event.MouseListener.mouseClicked(java.awt.event.MouseEvent)"
431: // simpl: "mouseClicked(MouseEvent)"
432:
433: String dmis2 = getSignatureSimplified2(dmi);
434: System.out.println(" decl " + dmis2);
435: if (methodsDeclImpl2.containsKey(dmis2)) {
436: System.out.println("Found!");
437: methodsDeclImpl2.remove(dmis2);
438: if (methodsDeclImpl2.isEmpty())
439: break ft;
440: }
441:
442: String dmis2g = getSignatureSimplified2Gen(dmi,
443: claParamTypesForName);
444: System.out.println(" declg " + dmis2g);
445: if (methodsDeclImpl2.containsKey(dmis2g)) {
446: System.out.println("Found!");
447: methodsDeclImpl2.remove(dmis2g);
448: if (methodsDeclImpl2.isEmpty())
449: break ft;
450: }
451: }
452:
453: }
454:
455: }
456:
457: if (!methodsDeclImpl2.isEmpty()) {
458: System.out.println("NOT FOUND for @Implements: "
459: + methodsDeclImpl2.keySet());
460: for (MethodNode mi : methodsDeclImpl2.values()) {
461: w++;
462: String mess = "@Implements '"
463: + mi.name
464: + "' seems not to implement some interface.";
465: if (debug) {
466: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
467: .append("\r\n\tat " + jn + "("
468: + simpleFileName + ":"
469: + mi.getStartLinCol()[0] + ")"
470: + mess);
471: }
472:
473: TintLineMessage tm = new TintLineMessage(jn, mess,
474: "Unresolved @Implements", mi
475: .getStartLinCol()[0], System
476: .currentTimeMillis(), 1);
477: LineMessagesManager.add(tm);
478: }
479: }
480: }
481:
482: // also "warnings"
483: Set<String> miss1 = new HashSet<String>(); // name+"%"+nargs
484: // very likely to be a real miss, signature with simple names is identical.
485: Set<String> miss2 = new HashSet<String>(); // name + (simple arg names)
486:
487: for (Method mh : herited) {
488: //System.out.println("heri: "+mh.toString());
489: String mhn1 = getSignatureSimplified1(mh);
490:
491: //look for collisions
492:
493: for (Method md : declared) {
494: String mdn1 = getSignatureSimplified1(md);
495:
496: if (mhn1.equals(mdn1)) {
497: if (!methodsDeclOverr.contains(mhn1)) {
498: miss1.add(mhn1);
499:
500: // deeper look:
501: String mhn2 = getSignatureSimplified2(mh);
502: String mdn2 = getSignatureSimplified2(md);
503: if (mhn2.equals(mdn2)) {
504: if (!methodsDeclOverr2.contains(mhn2)) {
505: miss2.add(mhn2);
506: }
507: }
508: }
509: // System.out.println("\tat "+jn+": overrides: "+mh);
510: }
511: }
512: }
513:
514: if (miss1.size() > 0) {
515: for (final MethodNode mi : methods) {
516: String mn = mi.getSignatureSimplified1();
517: if (miss1.contains(mn)) {
518: String mn2 = mi.getSignatureSimplified2();
519: //System.out.println("DBG: "+mn2+" in "+miss2);
520:
521: if (mi.isStatic()) {
522: // todo: ignore "public static main(String[])"
523: if (detectStaticOverloads) {
524: w++;
525: String mess = "Parent class defines a similar static method '"
526: + mi.name + "'.";
527: if (debug) {
528: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
529: .append("\r\n\tat "
530: + jn
531: + "("
532: + simpleFileName
533: + ":"
534: + mi.getStartLinCol()[0]
535: + ")" + mess);
536: }
537:
538: TintLineMessage tm = new TintLineMessage(
539: jn, mess,
540: "Static method shadowing", mi
541: .getStartLinCol()[0],
542: System.currentTimeMillis(), 2);
543: LineMessagesManager.add(tm);
544: }
545: } else if (mi.isPrivate()) {
546: if (detectPrivateOverloads) {
547: w++;
548: String mess = "Parent class defines a similar private method '"
549: + mi.name + "'.";
550:
551: if (debug) {
552: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
553: .append("\r\n\tat "
554: + jn
555: + "("
556: + simpleFileName
557: + ":"
558: + mi.getStartLinCol()[0]
559: + ")" + mess);
560: }
561:
562: TintLineMessage tm = new TintLineMessage(
563: jn, mess,
564: "Private method shadowing", mi
565: .getStartLinCol()[0],
566: System.currentTimeMillis(), 2);
567: LineMessagesManager.add(tm);
568: }
569: }
570: // TODO: protected that are not in the same package
571: // TODO: detect static/non stat collisions
572: else {
573: if (miss2.contains(mn2)) {
574: // find the herited method
575: Method md = null;
576: for (Method mdi : herited) {
577: if (getSignatureSimplified2(mdi)
578: .equals(mn2)) {
579: md = mdi;
580: System.out.println("" + md);
581: break;
582: }
583: }
584:
585: if (md != null) {
586: // todo: also detect package scope "violations"
587:
588: if (detectPrivateOverloads
589: && Modifier.isPrivate(md
590: .getModifiers())) {
591:
592: w++;
593: String mess = "A same private method '"
594: + mi.name
595: + "' exists in parent. This is NOT an override.";
596:
597: if (debug) {
598: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
599: .append("\r\n\tat "
600: + jn
601: + "("
602: + simpleFileName
603: + ":"
604: + mi
605: .getStartLinCol()[0]
606: + ")" + mess);
607: }
608:
609: TintLineMessage tm = new TintLineMessage(
610: jn, mess, "False override",
611: mi.getStartLinCol()[0],
612: System.currentTimeMillis(),
613: 2);
614: LineMessagesManager.add(tm);
615: continue;
616: }
617: }
618:
619: if (detectMissingOverrides) {
620: w++;
621: String mess = "Probably missing @Override for method '"
622: + mi.name + "' or overload.";
623:
624: if (debug) {
625: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
626: .append("\r\n\tat "
627: + jn
628: + "("
629: + simpleFileName
630: + ":"
631: + mi
632: .getStartLinCol()[0]
633: + ")" + mess);
634: }
635:
636: TintLineMessage tm = new TintLineMessage(
637: jn, mess, "Missing @Override",
638: mi.getStartLinCol()[0], System
639: .currentTimeMillis(), 2);
640: LineMessagesManager.add(tm);
641: }
642: } else {
643: if (detectNearOverrides) {
644: w++;
645: int na = getNumberOfArgsFromSimplified1Sign(mn);
646: String mess = "A similar method '"
647: + mi.name
648: + "' also with "
649: + na
650: + " argument"
651: + (na == 1 ? "" : "s")
652: + " but different type"
653: + (na == 1 ? "" : "s")
654: + " exists in parent."
655: + " It would be safer to use another name or ensure there's not a broken override.";
656:
657: if (debug) {
658: MainEditorFrame.instance.outputPanels.toolsOutputPanel.doc
659: .append("\r\n\tat "
660: + jn
661: + "("
662: + simpleFileName
663: + ":"
664: + mi
665: .getStartLinCol()[0]
666: + ")" + mess);
667: }
668:
669: TintLineMessage tm = new TintLineMessage(
670: jn, mess,
671: "Possible overloading", mi
672: .getStartLinCol()[0],
673: System.currentTimeMillis(), 2);
674: LineMessagesManager.add(tm);
675: }
676:
677: }
678: }
679:
680: //System.out.println("\tat "+jn+": Missing @Override: "+mi);
681: }
682: } // for MethodNode mi
683: }
684:
685: return w;
686: }
687:
688: }
|