001: package tide.bytecode.asm;
002:
003: import snow.utils.storage.FileUtils;
004: import org.objectweb.asm.*;
005: import java.io.*;
006: import java.util.*;
007: import tide.project.ClassFilesManager;
008: import tide.editor.MainEditorFrame;
009: import tide.project.ProjClass;
010:
011: /** Checks the @Recurse annotation contract. If present, a method must call itself in its block.
012: *
013: * Also checks missing annotations.
014: * We may also check for unchecked recursion, infinite loops, ... but this is already done by other tools...
015: */
016: public final class RecurseChecker extends ClassVisitorAd {
017: // set by this visitor.
018: String sourceName = "unknown sourcename";
019: String classJavaName = "unknown classJavaname";
020:
021: // missingRecurseCallOrMissingRecurseAnn
022: final List<ASMethod> problems = new ArrayList<ASMethod>();
023:
024: final boolean detectMissingRecurseAnn;
025:
026: public RecurseChecker(boolean detectMissingRecurseAnn) {
027: this .detectMissingRecurseAnn = detectMissingRecurseAnn;
028: }
029:
030: /** Analyse.
031: */
032: public static List<ASMethod> searchMissingRecurseCalls(
033: final String name, boolean detectMissingRecurseAnn)
034: throws Exception {
035: //System.out.println("RC: Analysing "+name);
036: ClassFilesManager cfm = MainEditorFrame.instance
037: .getActualProject().getClassFilesManager();
038:
039: // null at begining, when proj not already fully loaded
040: if (cfm == null)
041: return null;
042:
043: ProjClass pc = cfm.getClassExact(name);
044: if (pc == null) {
045: System.out.println("RC: class not found: " + name);
046: return null;
047: }
048:
049: InputStream is = pc.getClassContent();
050: try {
051: ClassReader cr = new ClassReader(is);
052: RecurseChecker ch = new RecurseChecker(
053: detectMissingRecurseAnn);
054: cr.accept(ch, 0);
055:
056: return ch.problems;
057:
058: //superName = ch.superClassJavaName;
059: } catch (Exception e) {
060: System.out.println("RC: cannot ASM analyse " + name + ": "
061: + e.getMessage());
062: } finally {
063: FileUtils.closeIgnoringExceptions(is);
064: }
065:
066: return null;
067: }
068:
069: @Override
070: public void visitSource(final String source, final String debugg) {
071: sourceName = source;
072: }
073:
074: @Override
075: public void visit(final int version, final int access,
076: final String name, final String signature,
077: final String super Name, final String[] interfaces) {
078: this .classJavaName = name.replace('/', '.');
079: //this.superClassJavaName = superName !=null ? superName.replace('/','.') : null;
080: if (debug) {
081: System.out.println("RC: analysing class " + name);
082: //System.out.println("superName="+superName);
083: }
084: }
085:
086: @Override
087: public MethodVisitor visitMethod(
088: final int access,
089: final String name, // Rem: <init> for the constructor
090: final String desc, final String signature,
091: final String[] exceptions) {
092: //System.out.println("method "+access+", "+name+", "+desc+", "+signature);
093: // creates a new one for each new visited method.
094:
095: if (!"<init>".equals(name)) {
096: return new MethodVis(name, desc);
097: } else {
098: // ignore constructors
099: return MethodVisitorAd.emptyAd;
100: }
101: }
102:
103: /** This visitor, called for each method,
104: reads in the enclosing class
105: */
106: private class MethodVis extends MethodVisitorAd {
107: final ASMethod met;
108: final String callToCheck;
109: boolean isMarkedWithRecurse = false;
110: boolean isMarkedWithNonRecurse = false;
111: boolean isRecurseCalled = false;
112:
113: public MethodVis(String name, String signature) // Rem: <init> for the constructor
114: {
115: super (name, signature);
116:
117: met = new ASMethod(classJavaName, sourceName, name,
118: signature);
119:
120: if (classJavaName != null && classJavaName.length() > 0) {
121: callToCheck = classJavaName + "."
122: + met.getSignature(false);
123: } else {
124: // unnamed scope
125: callToCheck = met.getSignature(false);
126: }
127:
128: }
129:
130: @Override
131: public void visitLineNumber(final int line, final Label start) {
132:
133: //if(firstClassLine<0) firstClassLine = line;
134:
135: if (line >= 0 && met.begLine < 0)
136: met.begLine = line;
137: }
138:
139: @Override
140: public AnnotationVisitor visitAnnotation(final String desc,
141: final boolean visible) {
142: if (desc.contains("NonRecurse")) {
143: isMarkedWithNonRecurse = true;
144: MainEditorFrame.debugOut("ANNOTATED NonRecurse: "
145: + met.getSignature(false));
146: } else if (desc.contains("Recurse")) {
147: isMarkedWithRecurse = true;
148: MainEditorFrame.debugOut("ANNOTATED Recurse: "
149: + met.getSignature(false));
150: }
151:
152: // no args read
153: return AnnotationVisitorAd.emptyVis;
154: }
155:
156: @Override
157: public void visitEnd() {
158: if (isMarkedWithRecurse && isMarkedWithNonRecurse) {
159: met
160: .setFailure(
161: "The method '"
162: + met.methodName
163: + "' is annotated both with @NonRecurse and @Recurse !",
164: "Bad @Recurse annotation");
165: met.issuePriority = 1;
166: problems.add(met);
167: } else if (isMarkedWithNonRecurse) {
168: if (met.callsRecursively) {
169: met
170: .setFailure(
171: "The method '"
172: + met.methodName
173: + "' is annotated with @NonRecurse but it call itself.",
174: "Failing @NonRecurse");
175: met.issuePriority = 1;
176: problems.add(met);
177: }
178: } else if (isMarkedWithRecurse) {
179: if (!met.callsRecursively) {
180: met
181: .setFailure(
182: "The method '"
183: + met.methodName
184: + "' is annotated with @Recurse but doesn't call itself.",
185: "Failing @Recurse");
186: met.issuePriority = 1;
187: problems.add(met);
188: }
189: } else {
190: if (detectMissingRecurseAnn && met.callsRecursively) {
191:
192: met
193: .setFailure(
194: "The method '"
195: + met.methodName
196: + "' could be annotated with @Recurse to make it more robust",
197: "Missing @Recurse");
198: met.issuePriority = 3;
199: problems.add(met);
200: }
201: }
202: }
203:
204: /** All method calls within the method are here.
205: * example: System.out.println("");
206: * Here we detect if the method does call itself recursively
207: */
208: @Override
209: public void visitMethodInsn(final int opcode,
210: final String owner, final String name, final String desc) {
211: // constr call: visitMethodInsn <init> ()V java/lang/StringBuilder 183
212: // meth call: visitMethodInsn append (Ljava/lang/String;)Ljava/lang/StringBuilder; java/lang/StringBuilder 182
213:
214: //System.out.println("visitMethodInsn "+name+" "+desc+" "+owner+" "+opcode);
215: //System.out.println(" "+ASMUtils.createArgsListFromSig(desc));
216:
217: if (!met.callsRecursively) {
218: //
219: String jn = owner != null ? owner.replace('/', '.')
220: : "";
221: String cc = jn + "." + name + "("
222: + ASMUtils.createArgsListFromSig(desc) + ")";
223:
224: // ex: // snow.annotations.Test.compareTo(java.lang.String) =?= compareTo(java.lang.String)
225: //System.out.println("look "+cc+" =?= "+callToCheck);
226:
227: if (cc.equals(callToCheck)) {
228: met.callsRecursively = true;
229: MainEditorFrame.debugOut("Recurse call for " + cc);
230: }
231: }
232: }
233: }
234:
235: }
|