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    }