001 package edu.rice.cs.cunit.threadCheck.subAnnot;
002
003 import edu.rice.cs.cunit.classFile.ClassFileTools;
004 import edu.rice.cs.cunit.instrumentors.DoNotInstrument;
005 import edu.rice.cs.cunit.subAnnot.ClassEx;
006 import edu.rice.cs.cunit.subAnnot.MethodEx;
007 import edu.rice.cs.cunit.threadCheck.SuppressSubtypingWarning;
008 import edu.rice.cs.cunit.util.Pair;
009
010 import java.io.*;
011 import java.lang.annotation.Annotation;
012 import java.lang.reflect.InvocationTargetException;
013 import java.lang.reflect.Method;
014 import java.lang.reflect.Modifier;
015 import java.util.*;
016
017 /**
018 * Class that checks whether threads may execute certain classes at runtime.
019 *
020 * @author Mathias Ricken
021 */
022 @DoNotInstrument
023 public class SubAnnotThreadCheck {
024 public static class Violation implements Serializable {
025 public static final String LF = System.getProperty("line.separator");
026
027 public static enum TYPE {
028 NOTRUNBY_NAME,
029 NOTRUNBY_GROUP,
030 NOTRUNBY_ID,
031 ONLYRUNBY,
032 PREDICATE,
033 REFLECTION_PREDICATE
034 }
035
036 // offending thread information
037 public final TYPE type;
038
039 public final String threadName;
040
041 public final String threadGroupName;
042
043 public final long threadId;
044
045 public final StackTraceElement[] stackTrace;
046
047 public final long checkCount;
048
049 public final long violationCount;
050
051 public Violation(Violation.TYPE type,
052 String threadName,
053 String threadGroupName,
054 long threadId,
055 StackTraceElement[] ste) {
056 this.type = type;
057 this.threadName = threadName;
058 this.threadGroupName = threadGroupName;
059 this.threadId = threadId;
060 if (ste.length>3) {
061 this.stackTrace = new StackTraceElement[ste.length-3];
062 System.arraycopy(ste,3,this.stackTrace,0,ste.length-3);
063 }
064 else {
065 this.stackTrace = new StackTraceElement[0];
066 }
067 this.checkCount = _checkCount;
068 this.violationCount = _violationCount;
069 }
070
071 public boolean equals(Object o) {
072 if (this == o) {
073 return true;
074 }
075 if (o == null || getClass() != o.getClass()) {
076 return false;
077 }
078
079 Violation violation = (Violation)o;
080
081 if (threadId != violation.threadId) {
082 return false;
083 }
084 if (!Arrays.equals(stackTrace, violation.stackTrace)) {
085 return false;
086 }
087 if (threadGroupName != null ? !threadGroupName.equals(violation.threadGroupName) :
088 violation.threadGroupName != null) {
089 return false;
090 }
091 if (threadName != null ? !threadName.equals(violation.threadName) : violation.threadName != null) {
092 return false;
093 }
094 if (type != violation.type) {
095 return false;
096 }
097
098 return true;
099 }
100
101 public int hashCode() {
102 int result;
103 result = type.hashCode();
104 result = 31 * result + (threadName != null ? threadName.hashCode() : 0);
105 result = 31 * result + (threadGroupName != null ? threadGroupName.hashCode() : 0);
106 result = 31 * result + (int)(threadId ^ (threadId >>> 32));
107 result = 31 * result + Arrays.hashCode(stackTrace);
108 return result;
109 }
110 }
111
112 public static class PredicateViolation extends Violation {
113 public final String predicateClass;
114
115 public PredicateViolation(String threadName,
116 String threadGroupName,
117 long threadId,
118 StackTraceElement[] ste,
119 String predicateClass) {
120 super(TYPE.PREDICATE, threadName, threadGroupName, threadId, ste);
121 this.predicateClass = predicateClass;
122 }
123
124 /**
125 * Returns a string representation of the object.
126 *
127 * @return string representation
128 */
129 public String toString() {
130 StringBuilder sb = new StringBuilder();
131 sb.append("Thread Predicate Violation: (");
132 sb.append(checkCount);
133 sb.append(" ");
134 sb.append(form("check", checkCount));
135 sb.append(", ");
136 sb.append(violationCount);
137 sb.append(" ");
138 sb.append(form("violation", violationCount));
139 sb.append(")");
140 sb.append(LF);
141 sb.append("\tCurrent thread '");
142 sb.append(threadName);
143 sb.append("', id ");
144 sb.append(threadId);
145 sb.append(", group '");
146 sb.append(threadGroupName);
147 sb.append("'");
148 sb.append(LF);
149 sb.append("\tViolated predicate @");
150 sb.append(predicateClass.substring(1, predicateClass.length()-1).replace('/','.'));
151 for(int i = 0; i < stackTrace.length; ++i) {
152 sb.append(LF);
153 sb.append("\tat ");
154 sb.append(stackTrace[i].getClassName());
155 sb.append(".");
156 sb.append(stackTrace[i].getMethodName());
157 sb.append(" (");
158 sb.append(stackTrace[i].getFileName());
159 sb.append(":");
160 sb.append(stackTrace[i].getLineNumber());
161 sb.append(")");
162 }
163
164 return sb.toString();
165 }
166
167 public boolean equals(Object o) {
168 if (this == o) {
169 return true;
170 }
171 if (o == null || getClass() != o.getClass()) {
172 return false;
173 }
174 if (!super.equals(o)) {
175 return false;
176 }
177
178 PredicateViolation that = (PredicateViolation)o;
179
180 if (predicateClass != null ? !predicateClass.equals(that.predicateClass) : that.predicateClass != null) {
181 return false;
182 }
183
184 return true;
185 }
186
187 public int hashCode() {
188 int result = super.hashCode();
189 result = 31 * result + (predicateClass != null ? predicateClass.hashCode() : 0);
190 return result;
191 }
192 }
193
194 public static class ReflectionPredicateViolation extends Violation {
195 public final String predicateString;
196 public final Object[] methodArgs;
197
198 public ReflectionPredicateViolation(String threadName,
199 String threadGroupName,
200 long threadId,
201 StackTraceElement[] ste,
202 String predicateString,
203 Object[] methodArgs) {
204 super(Violation.TYPE.REFLECTION_PREDICATE, threadName, threadGroupName, threadId, ste);
205 this.predicateString = predicateString;
206 this.methodArgs = methodArgs;
207 }
208
209 /**
210 * Returns a string representation of the object.
211 *
212 * @return string representation
213 */
214 public String toString() {
215 StringBuilder sb = new StringBuilder();
216 sb.append("Thread Reflection Predicate Violation: (");
217 sb.append(checkCount);
218 sb.append(" ");
219 sb.append(form("check", checkCount));
220 sb.append(", ");
221 sb.append(violationCount);
222 sb.append(" ");
223 sb.append(form("violation", violationCount));
224 sb.append(")");
225 sb.append(LF);
226 sb.append("\tCurrent thread '");
227 sb.append(threadName);
228 sb.append("', id ");
229 sb.append(threadId);
230 sb.append(", group '");
231 sb.append(threadGroupName);
232 sb.append("'");
233 sb.append(LF);
234 sb.append("\tViolated predicate ");
235 sb.append(predicateString);
236 if (methodArgs!=null) {
237 sb.append(LF);
238 sb.append("\tMethod arguments: "+methodArgs.length);
239 for(Object a: methodArgs) {
240 sb.append(LF);
241 sb.append("\t\t'");
242 sb.append(a.toString());
243 sb.append("' : ");
244 sb.append(a.getClass().getName());
245 }
246 }
247 for(int i = 0; i < stackTrace.length; ++i) {
248 sb.append(LF);
249 sb.append("\tat ");
250 sb.append(stackTrace[i].getClassName());
251 sb.append(".");
252 sb.append(stackTrace[i].getMethodName());
253 sb.append(" (");
254 sb.append(stackTrace[i].getFileName());
255 sb.append(":");
256 sb.append(stackTrace[i].getLineNumber());
257 sb.append(")");
258 }
259
260 return sb.toString();
261 }
262
263 public boolean equals(Object o) {
264 if (this == o) {
265 return true;
266 }
267 if (o == null || getClass() != o.getClass()) {
268 return false;
269 }
270 if (!super.equals(o)) {
271 return false;
272 }
273
274 PredicateViolation that = (PredicateViolation)o;
275
276 if (predicateString != null ? !predicateString.equals(that.predicateClass) : that.predicateClass != null) {
277 return false;
278 }
279
280 return true;
281 }
282
283 public int hashCode() {
284 int result = super.hashCode();
285 result = 31 * result + (predicateString != null ? predicateString.hashCode() : 0);
286 return result;
287 }
288 }
289
290 /**
291 * Name of the Java property that determines the log filename.
292 */
293 public static final String LOG_FILENAME_PROPERTY = "edu.rice.cs.cunit.threadCheck.filename";
294
295 /**
296 * Default filename for the log, if none specified by Java property.
297 */
298 public static final String DEFAULT_LOG_FILENAME = "threadCheck.log";
299
300 /**
301 * Default filename for the data log, if none specified by Java property.
302 */
303 public static final String DEFAULT_DAT_SUFFIX = ".dat";
304
305 /**
306 * True if the code currently executing is due to thread checking; used to avoid infinite recursions.
307 */
308 private static volatile HashSet<Thread> _inCheckCode = new HashSet<Thread>();
309
310 /**
311 * PrintWriter with the warning log.
312 */
313 private static volatile PrintWriter _log = null;
314
315 /**
316 * ObjectOutputStream with the warning log as data.
317 */
318 private static volatile ObjectOutputStream _logData = null;
319
320 /**
321 * Number of checks performed.
322 */
323 private static volatile long _checkCount = 0;
324
325 /**
326 * Number of violationms found.
327 */
328 private static volatile long _violationCount = 0;
329
330 /**
331 * Write to the error log, and potentially initialize it first.
332 * @param v violation to log
333 */
334 public static synchronized void writeLog(Violation v) {
335 initLog();
336 _log.println(v.toString());
337 _log.flush();
338 try {
339 _logData.writeObject(v);
340 _logData.flush();
341 }
342 catch(IOException e) {
343 e.printStackTrace();
344 System.exit(1);
345 }
346 }
347
348 /**
349 * Initialize the error log.
350 */
351 public static synchronized void initLog() {
352 if (_log == null) {
353 String filename = System.getProperty(LOG_FILENAME_PROPERTY);
354 if (filename == null) {
355 filename = DEFAULT_LOG_FILENAME;
356 }
357 try {
358 _log = new PrintWriter(new FileOutputStream(filename, true));
359 _log.println("========================================");
360 _log.format("Log opened %04d-%02d-%02d, %02d:%02d:%02d %s (GMT%d)", Calendar.getInstance().get(
361 Calendar.YEAR), Calendar.getInstance().get(Calendar.MONTH), Calendar.getInstance().get(
362 Calendar.DAY_OF_MONTH), Calendar.getInstance().get(Calendar.HOUR_OF_DAY),
363 Calendar.getInstance().get(Calendar.MINUTE), Calendar.getInstance().get(
364 Calendar.SECOND), Calendar.getInstance().getTimeZone().getDisplayName(Calendar.getInstance().get(
365 Calendar.DST_OFFSET) == 0, TimeZone.SHORT), ((Calendar.getInstance().get(Calendar.ZONE_OFFSET)
366 + Calendar.getInstance().get(Calendar.DST_OFFSET))
367 / 1000 / 60 / 60));
368 _log.println();
369 _log.println("----------------------------------------");
370 _log.flush();
371 }
372 catch(FileNotFoundException e) {
373 e.printStackTrace();
374 System.exit(1);
375 }
376
377 filename = filename + DEFAULT_DAT_SUFFIX;
378 try {
379 _logData = new ObjectOutputStream(new FileOutputStream(filename, true));
380 }
381 catch(IOException e) {
382 e.printStackTrace();
383 System.exit(1);
384 }
385 }
386 else {
387 _log.println("----------------------------------------");
388 _log.flush();
389 }
390 }
391
392 /**
393 * Log to the log file.
394 * @param s string
395 */
396 public static synchronized void log(String s) { initLog(); _log.println(s); }
397
398 /**
399 * Flush the log.
400 */
401 public static synchronized void flushLog() { _log.flush(); }
402
403 /**
404 * Checks if the current thread may execute the method from where this method was called purely using reflection.
405 * If it may not execute, a violation is logged.
406 * @param callerClass class of the caller
407 * @param methodName name of the method
408 * @param methodDesc descriptor of the method
409 * @param thisO value of this, or null in a static context
410 * @param methodArgs array of method arguments, or null if none passed
411 */
412 public static synchronized void checkCurrentThreadReflection(Class callerClass, String methodName, String methodDesc, Object thisO, Object[] methodArgs) {
413 final Thread ct = Thread.currentThread();
414 if (_inCheckCode.contains(ct)) {
415 return;
416 }
417 _inCheckCode.add(ct);
418 try {
419 // System.out.println("Class = "+callerClass.getName());
420 // System.out.println("Method = "+methodName);
421 // System.out.println("Desc = "+methodDesc);
422 // System.out.println("this = "+thisO);
423
424 // determine the calling method
425 @SuppressWarnings("unchecked")
426 ClassEx callerClassEx = new ClassEx(callerClass, callerClass.getClassLoader());
427 MethodEx callerMethod = getMethodWithNameAndDescriptor(callerClassEx, methodName, methodDesc);
428 if (callerMethod==null) {
429 initLog();
430 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
431 _log.println("Thread Checker could not find method "+methodName+methodDesc+" in class "+callerClass.getName());
432 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
433 _log.flush();
434 return;
435 }
436
437 // System.out.println("Caller method = "+callerMethod);
438
439 // handle @PredicateLink annotations
440 ThreadCheckAnnotationRecord ar = getMethodAnnotations(callerClassEx, callerMethod);
441
442 // System.out.println("@PredicateLinks: "+ar.predicateLinkAnnotations.size());
443
444 for(Pair<Annotation,PredicateLink> p: ar.predicateLinkAnnotations) {
445 // System.out.println("@PredicateLink: "+p.first()+", "+p.second());
446 ++_checkCount;
447 Boolean ret = checkPredicateLinkAnnotation(p.first(), p.second(), thisO, methodArgs);
448 if ((ret!=null) && (ret==false)) {
449 // we have a violation
450 // System.out.println("\tViolation!");
451 ++_violationCount;
452 Violation v = new ReflectionPredicateViolation(ct.getName(), ct.getThreadGroup().getName(), ct.getId(), ct.getStackTrace(),
453 p.first().toString(), (p.second().arguments()?methodArgs:null));
454 writeLog(v);
455 }
456 }
457 }
458 finally {
459 _inCheckCode.remove(ct);
460 }
461 }
462
463 /**
464 * Convenience method to get the result of an invariant annotation check.
465 * @param value the invariant annotation
466 * @param thisObject value of this, or null if static
467 * @param methodArgs array of method arguments
468 * @return true if check succeeded
469 */
470 @SuppressWarnings("unchecked")
471 public static boolean checkInvariantAnnotation(InvariantAnnotation value, Object thisObject, Object[] methodArgs) {
472 ClassEx<? extends InvariantAnnotation> cex = new ClassEx(value.annotationType(), value.annotationType().getClassLoader());
473 List<PredicateLink> pls = cex.getAnnotations(PredicateLink.class);
474 if (pls.size()!=1) {
475 SubAnnotThreadCheck.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
476 SubAnnotThreadCheck.log("Thread Checker found an invariant annotation that did not have exactly one @PredicateLink: "+value);
477 SubAnnotThreadCheck.log("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
478 SubAnnotThreadCheck.flushLog();
479 return false;
480 }
481 return (Boolean.TRUE==checkPredicateLinkAnnotation(value, pls.get(0), thisObject, methodArgs));
482 }
483
484 /**
485 * Handle a @PredicateLink-style annotation.
486 * @param ann annotation
487 * @param pl associated @PredicateLink
488 * @param thisO value of this, or null
489 * @return false if there was a violation, or null if there was an error
490 */
491 public static Boolean checkPredicateLinkAnnotation(Annotation ann, PredicateLink pl, Object thisO, Object[] methodArgs) {
492 Class predicateClass = pl.value();
493 String predicateMethodName = pl.method();
494 Class annClass = ann.annotationType();
495
496 List<Method> annotMethods = new ArrayList<Method>();
497 Queue<Class> toConsider = new LinkedList<Class>();
498 toConsider.add(annClass);
499 while(toConsider.size()>0) {
500 Class cur = toConsider.remove();
501 for(Method m: cur.getDeclaredMethods()) { annotMethods.add(m); }
502 for(Class interf: cur.getInterfaces()) {
503 if ((!interf.equals(java.lang.annotation.Annotation.class))) {
504 toConsider.add(interf);
505 }
506 }
507 }
508
509 Method[] annMethods = annotMethods.toArray(new Method[annotMethods.size()]);
510 int parenIndex = predicateMethodName.indexOf('(');
511 if (parenIndex>=0) {
512 if (!predicateMethodName.endsWith(")")) {
513 initLog();
514 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
515 _log.println("Thread Checker found a bad predicate method name with parameter list that did not end in ')': "+predicateMethodName);
516 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
517 _log.flush();
518 return null;
519 }
520 // strip parameter name list in parentheses from name: "methodName(param1,param2)"
521 String s = predicateMethodName.substring(parenIndex + 1, predicateMethodName.length() - 1);
522 String[] paramNames = s.split(",");
523 ArrayList<Method> tempMethods = new ArrayList<Method>();
524 predicateMethodName = predicateMethodName.substring(0,parenIndex);
525 if (s.length()>0) {
526 for(int i=0; i<annMethods.length; ++i) {
527 if (paramNames[i].length()>0) {
528 for(Method m: annMethods) {
529 if (m.getName().equals(paramNames[i])) {
530 tempMethods.add(m);
531 break;
532 }
533 }
534 if (annMethods[i]==null) {
535 initLog();
536 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
537 _log.println("Thread Checker found a bad predicate method name with parameter list: that contained");
538 _log.println("the parameter "+paramNames[i]+", which wasn't found in the annotation.");
539 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
540 _log.flush();
541 return null;
542 }
543 }
544 }
545 }
546 annMethods = tempMethods.toArray(new Method[0]);
547 }
548 Method predicateMethod = null;
549 int start = 1;
550 if (pl.arguments()) {
551 ++start;
552 }
553 for(Method m: predicateClass.getMethods()) {
554 // initLog();
555 // _log.println("Considering "+m+"...");
556 if ((m.getName().equals(predicateMethodName)) &&
557 ((m.getModifiers() & Modifier.STATIC)!=0) &&
558 (m.getReturnType().isPrimitive()) &&
559 (m.getReturnType().getName().equals("boolean")) &&
560 (m.getParameterTypes().length >= start) &&
561 (m.getParameterTypes()[0].equals(Object.class))) {
562 // _log.println("- static, boolean, takes Object");
563 // _log.println("- "+m.getParameterTypes()[1].getName());
564 if (pl.arguments()) {
565 if (!m.getParameterTypes()[1].getName().equals("[Ljava.lang.Object;")) {
566 continue;
567 }
568 }
569 // _log.println("- takes Object[]");
570 // name matches, is static, returns boolean, and takes Object as first parameter
571 // check parameters
572 // _log.println("- m.getParameterTypes().length = "+m.getParameterTypes().length+", start = "+start+
573 // ", annMethods.length = "+annMethods.length);
574 if (m.getParameterTypes().length-start!=annMethods.length) {
575 // wrong number of parameters
576 // _log.println("- wrong number of parameters");
577 continue;
578 }
579 boolean found = true;
580 for (int i=start; i<m.getParameterTypes().length; ++i) {
581 Class pt = m.getParameterTypes()[i];
582 if (!pt.equals(annMethods[i-start].getReturnType())) {
583 found = false;
584 break;
585 }
586 }
587 if (found) {
588 // parameters matched
589 predicateMethod = m;
590 break;
591 }
592 }
593 }
594 if (predicateMethod==null) {
595 initLog();
596 // TODO: allow for subclassing
597 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
598 _log.println("Thread Checker could not find predicate method with name "+predicateMethodName+
599 " in class "+predicateClass.getName()+" suitable for annotation "+annClass.getName());
600 _log.println("To be suitable, a predicate method has to have the following parameters in the right order:");
601 _log.println("\tjava.lang.Object");
602 if (pl.arguments()) {
603 _log.println("\tjava.lang.Object[]");
604 }
605 for(int i=0; i<annMethods.length; ++i) {
606 Method annMethod = annMethods[i];
607 _log.println("\t"+annMethod.getReturnType());
608 }
609 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
610 _log.flush();
611 return null;
612 }
613
614 // System.out.println("Predicate method: "+predicateMethod);
615
616 Object[] params = new Object[predicateMethod.getParameterTypes().length];
617 params[0] = thisO;
618 if (pl.arguments()) {
619 params[1] = methodArgs;
620 }
621 for(int i=start; i<params.length; ++i) {
622 Method annMethod = annMethods[i-start];
623 // System.out.println("Invoking annotation method "+annMethod);
624 try {
625 annMethod.setAccessible(true);
626 params[i] = annMethod.invoke(ann);
627 }
628 catch(IllegalAccessException e) {
629 initLog();
630 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
631 _log.println("Thread Checker could not invoke annotation method "+annMethod+":");
632 e.printStackTrace(_log);
633 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
634 _log.flush();
635 return null;
636 }
637 catch(InvocationTargetException e) {
638 initLog();
639 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
640 _log.println("Thread Checker could not invoke annotation method "+annMethod+":");
641 e.printStackTrace(_log);
642 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
643 _log.flush();
644 return null;
645 }
646 }
647 try {
648 predicateMethod.setAccessible(true);
649 Object retval = predicateMethod.invoke(null, params);
650 if (!retval.getClass().equals(Boolean.class)) {
651 initLog();
652 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
653 _log.println("Thread Checker invoked predicate method "+predicateMethod+" in class "+predicateClass.getName()+
654 ", but the result was not a boolean.");
655 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
656 _log.flush();
657 return null;
658 }
659 return (Boolean)retval;
660 }
661 catch(IllegalAccessException e) {
662 initLog();
663 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
664 _log.println("Thread Checker could not invoke invoked predicate method "+predicateMethod+" in class "+predicateClass.getName()+
665 ":");
666 e.printStackTrace(_log);
667 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
668 _log.flush();
669 return null;
670 }
671 catch(InvocationTargetException e) {
672 initLog();
673 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
674 _log.println("Thread Checker could not invoke invoked predicate method "+predicateMethod+" in class "+predicateClass.getName()+
675 ":");
676 e.printStackTrace(_log);
677 _log.println("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
678 _log.flush();
679 return null;
680 }
681 }
682
683 /**
684 * Return the method with the specified name and descriptor.
685 * @param methodClass class
686 * @param methodName name
687 * @param methodDesc descriptor
688 * @return method, or null
689 */
690 public static MethodEx getMethodWithNameAndDescriptor(ClassEx methodClass, String methodName, String methodDesc) {
691 MethodEx callerMethod = null;
692 for(MethodEx m: methodClass.getDeclaredMethods()) {
693 if (m.getName().equals(methodName)) {
694 String desc = getMethodDescriptor(m);
695 if (desc.toString().equals(methodDesc)) {
696 callerMethod = m;
697 break;
698 }
699 }
700 }
701 return callerMethod;
702 }
703
704 /**
705 * Return the method descriptor for the method.
706 * @param m method
707 * @return descriptor
708 */
709 public static String getMethodDescriptor(MethodEx m) {
710 StringBuilder sb = new StringBuilder();
711 sb.append('(');
712 for(ClassEx pt: m.getParameterTypes()) {
713 String n = pt.getName();
714 if (!pt.isPrimitive()) {
715 // reference
716 sb.append(n.replace('.','/'));
717 }
718 else {
719 // primitive value
720 char c = ClassFileTools.getPrimitiveTypeChar(n);
721 if (c==0) {
722 System.err.println("Unknown primitive type in Thread Checker: "+n);
723 System.exit(1);
724 }
725 else { sb.append(c); }
726 }
727 }
728 sb.append(')');
729 String n = m.getReturnType().getName();
730 if (!m.getReturnType().isPrimitive()) {
731 // reference return type
732 sb.append(n.replace('.','/'));
733 }
734 else {
735 // primitive return type
736 char c = ClassFileTools.getPrimitiveTypeChar(n);
737 if (c==0) {
738 System.err.println("Unknown primitive type in Thread Checker: "+n);
739 System.exit(1);
740 }
741 else { sb.append(c); }
742 }
743 return sb.toString();
744 }
745
746 /**
747 * Return the correct form, singular or plural, of the word, depending on the count.
748 * @param word word
749 * @param count count
750 * @return word+"s" if count!=1, or word if count==1
751 */
752 public static String form(String word, long count) { return (count==1)?word:(word+'s'); }
753
754 /**
755 * Storage class for predicate annotations.
756 */
757 public static class ThreadCheckAnnotationRecord {
758 public HashSet<Pair<Annotation,PredicateLink>> predicateLinkAnnotations;
759 public boolean suppressSubtypingWarning = false;
760
761 public ThreadCheckAnnotationRecord() {
762 predicateLinkAnnotations = new HashSet<Pair<Annotation,PredicateLink>>();
763 }
764
765 public void add(ThreadCheckAnnotationRecord otherAR) {
766 predicateLinkAnnotations.addAll(otherAR.predicateLinkAnnotations);
767 }
768 }
769
770 /**
771 * Get the annotations for the specified method in the specified class file.
772 * @param cf class file
773 * @param mi method information
774 * @return annotations
775 */
776 public static ThreadCheckAnnotationRecord getMethodAnnotations(ClassEx cf, MethodEx mi) {
777 ThreadCheckAnnotationRecord methodAR = getPredicateSets(mi.getAnnotations());
778
779 // a set of method signatures of the form "<method name><descriptor>" that describe methods that have
780 // been defined in superclasses or implemented interfaces already and to which the class level annotations
781 // on THIS class should therefore NOT apply
782 HashSet<String> definedAbove = new HashSet<String>();
783
784 if (cf.getSuperclass()!=null) {
785 // find the annotations for the same method in the superclass
786 boolean found = false;
787 ClassEx curcf = cf;
788
789 while(!found && curcf.getSuperclass()!=null) {
790 ClassEx scf = curcf.getSuperclass();
791 for(MethodEx smi : scf.getMethods()) {
792 definedAbove.add(smi.getName()+getMethodDescriptor(smi));
793 if ((smi.getName().equals(mi.getName()) &&
794 (getMethodDescriptor(smi).equals(getMethodDescriptor(mi))))) {
795 ThreadCheckAnnotationRecord superClassMethodAR = getMethodAnnotations(scf, smi);
796 if (!methodAR.suppressSubtypingWarning) {
797 // also check for subtyping warnings with predicate annotations?
798 checkForSubtypingClassWarnings(cf, mi, scf, methodAR, superClassMethodAR);
799 }
800 methodAR.add(superClassMethodAR);
801 found = true;
802 }
803 }
804 if (!found) {
805 curcf = scf;
806 }
807 }
808
809 // find the annotations for all the interfaces
810 for(ClassEx icf: cf.getInterfaces()) {
811 found = false;
812 ClassEx scf = icf;
813 while(!found && scf!=null) {
814 for(MethodEx smi : scf.getMethods()) {
815 definedAbove.add(smi.getName()+getMethodDescriptor(smi));
816 if ((smi.getName().equals(mi.getName()) &&
817 (getMethodDescriptor(smi).equals(getMethodDescriptor(mi))))) {
818 ThreadCheckAnnotationRecord interfClassMethodAR = getMethodAnnotations(scf, smi);
819 if (!methodAR.suppressSubtypingWarning) {
820 // also check for subtyping warnings with predicate annotations?
821 checkForSubtypingClassWarnings(cf, mi, scf, methodAR, interfClassMethodAR);
822 }
823 methodAR.add(interfClassMethodAR);
824 found = true;
825 }
826 }
827 if (!found) {
828 scf = scf.getSuperclass();
829 }
830 }
831 }
832 }
833
834 // add class level annotations, but only for those methods not mentioned in definedAbove
835 if (!definedAbove.contains(mi.getName()+getMethodDescriptor(mi))) {
836 methodAR.add(getClassAnnotations(cf));
837 }
838
839 return methodAR;
840 }
841
842 /**
843 * Get the annotations for the specified class file.
844 * @param cf class file
845 * @return annotations
846 */
847 public static ThreadCheckAnnotationRecord getClassAnnotations(ClassEx cf) {
848 ThreadCheckAnnotationRecord classAR = getPredicateSets(cf.getAnnotations());
849
850 if (cf.getSuperclass()!=null) {
851 ThreadCheckAnnotationRecord superClassAR = getClassAnnotations(cf.getSuperclass());
852 classAR.add(superClassAR);
853
854 // find the annotations for all the interfaces
855 for(ClassEx icf: cf.getInterfaces()) {
856 ThreadCheckAnnotationRecord interfClassAR = getClassAnnotations(icf);
857 classAR.add(interfClassAR);
858 }
859 }
860
861 return classAR;
862 }
863
864
865 /**
866 * Get the predicate sets from the array of annotations.
867 * @param annotations array of annotations
868 * @return annotation record.
869 */
870 public static ThreadCheckAnnotationRecord getPredicateSets(Annotation[] annotations) {
871 ThreadCheckAnnotationRecord ar = new ThreadCheckAnnotationRecord();
872 for (Annotation ann: annotations) {
873 PredicateLink pl = ann.annotationType().getAnnotation(PredicateLink.class);
874 if (pl!=null) {
875 ar.predicateLinkAnnotations.add(new Pair<Annotation,PredicateLink>(ann, pl));
876 }
877 if (ann.annotationType().getAnnotation(SuppressSubtypingWarning.class)!=null) {
878 ar.suppressSubtypingWarning = true;
879 }
880 }
881 return ar;
882 }
883
884 /**
885 * Check for subtyping warnings.
886 * @param cf class file
887 * @param mi method information, or null if on class level
888 * @param scf superclass/interface class file
889 * @param classAR subclass annotations
890 * @param superClassAR superclass annotations
891 */
892 public static void checkForSubtypingClassWarnings(ClassEx cf,
893 MethodEx mi,
894 ClassEx scf,
895 ThreadCheckAnnotationRecord classAR,
896 ThreadCheckAnnotationRecord superClassAR) {
897 String methodName = null, methodDesc = null;
898 if (mi!=null) {
899 methodName = mi.getName();
900 methodDesc = getMethodDescriptor(mi);
901 }
902
903 // check for subtyping warnings with predicate annotations
904 for(Pair<Annotation,PredicateLink> pair: classAR.predicateLinkAnnotations) {
905 if (!superClassAR.predicateLinkAnnotations.contains(pair)) {
906 initLog();
907 _log.print("ThreadChecker Reflection Subtyping Warnings: ");
908 if (mi==null) {
909 // class level warning
910 _log.print(cf.getName());
911 _log.print(" has predicate annotation ");
912 _log.print(pair.first());
913 _log.print(" but ");
914 _log.print(scf.getName());
915 }
916 else {
917 // method level warning
918 _log.print(cf.getName());
919 _log.print('.');
920 _log.print(mi.getName());
921 _log.print(getMethodDescriptor(mi));
922 _log.print(" has predicate annotation ");
923 _log.print(pair.first());
924 _log.print(" but ");
925 _log.print(scf.getName());
926 _log.print('.');
927 _log.print(mi.getName());
928 _log.print(getMethodDescriptor(mi));
929 }
930 _log.println(" does not");
931 _log.flush();
932 }
933 }
934 }
935
936
937 }