aboutsummaryrefslogtreecommitdiff
path: root/DebugUtils/GTMMethodCheck.m
blob: 0ff795e900513116713ecddde58ef1b8525fb6cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
//
//  GTMMethodCheck.m
//
//  Copyright 2006-2008 Google Inc.
//
//  Licensed under the Apache License, Version 2.0 (the "License"); you may not
//  use this file except in compliance with the License.  You may obtain a copy
//  of the License at
//
//  http://www.apache.org/licenses/LICENSE-2.0
//
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
//  WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
//  License for the specific language governing permissions and limitations under
//  the License.
//

// Don't want any of this in release builds
#ifdef DEBUG
#import "GTMDefines.h"
#import "GTMMethodCheck.h"
#import "GTMObjC2Runtime.h"
#import <dlfcn.h>

void GTMMethodCheckMethodChecker(void) {
  // Run through all the classes looking for class methods that are
  // prefixed with xxGMMethodCheckMethod. If it finds one, it calls it.
  // See GTMMethodCheck.h to see what it does.
#if !defined(__has_feature) || !__has_feature(objc_arc)
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
#else
  @autoreleasepool {
#endif
  // Since GTMMethodCheckMethodChecker is not exported, we should always find
  // the copy in our local image. This will give us access to our local image
  // in the methodCheckerInfo structure.
  Dl_info methodCheckerInfo;
  int foundMethodChecker = dladdr(GTMMethodCheckMethodChecker,
                                  &methodCheckerInfo);
  _GTMDevAssert(foundMethodChecker, @"GTMMethodCheckMethodChecker: Unable to "
                @"get dladdr for GTMMethodCheckMethodChecker");
  int numClasses = 0;
  int newNumClasses = objc_getClassList(NULL, 0);
  int i;
  Class *classes = NULL;
  while (numClasses < newNumClasses) {
    numClasses = newNumClasses;
    classes = (Class *)realloc(classes, sizeof(Class) * numClasses);
    _GTMDevAssert(classes, @"Unable to allocate memory for classes");
    newNumClasses = objc_getClassList(classes, numClasses);
  }
  for (i = 0; i < numClasses && classes; ++i) {
    Class cls = classes[i];
    const char *className = class_getName(cls);
    _GTMDevAssert(className, @"GTMMethodCheckMethodChecker: Unable to "
                  @"get className for class %d", i);
    // Since we are looking for a class method (+xxGMMethodCheckMethod...)
    // we need to query the isa pointer to see what methods it support, but
    // send the method (if it's supported) to the class itself.
    if (strcmp(className, "__ARCLite__") == 0) {
      // __ARCLite__ is "magic" and does not have a metaClass.
      continue;
    }
    Class metaClass = objc_getMetaClass(className);
    _GTMDevAssert(metaClass, @"GTMMethodCheckMethodChecker: Unable to "
                  @"get metaClass for %s", className);
    unsigned int count;
    Method *methods = class_copyMethodList(metaClass, &count);
    if (count == 0) {
      continue;
    }
    _GTMDevAssert(methods, @"GTMMethodCheckMethodChecker: Unable to "
                  @"get methods for class %s", className);

    unsigned int j;
    for (j = 0; j < count; ++j) {
      SEL selector = method_getName(methods[j]);
      _GTMDevAssert(selector, @"GTMMethodCheckMethodChecker: Unable to "
                    @"get selector for method %d of %s", j, className);
      const char *name = sel_getName(selector);
      _GTMDevAssert(selector, @"GTMMethodCheckMethodChecker: Unable to "
                    @"get name for method %d of %s", j, className);
      if (strstr(name, "xxGTMMethodCheckMethod") == name) {
        Dl_info methodInfo;
        IMP imp = method_getImplementation(methods[j]);
        _GTMDevAssert(selector, @"GTMMethodCheckMethodChecker: Unable to "
                      @"get IMP for method %s of %s", name, className);
        int foundMethod = dladdr(imp, &methodInfo);
        _GTMDevAssert(foundMethod, @"GTMMethodCheckMethodChecker: Unable to "
                      @"get dladdr for method %s of %s", name, className);

        // Check to make sure that the method we are checking comes from the
        // same image that we are in. We compare the address of the local
        // image (stored in |methodCheckerInfo| as noted above) with the
        // address of the image which implements the method we want to
        // check. If they match we continue. This does two things:
        // a) minimizes the amount of calls we make to the xxxGTMMethodCheck
        //    methods. They should only be called once.
        // b) prevents initializers for various classes being called too
        //    early
        if (methodCheckerInfo.dli_fbase == methodInfo.dli_fbase) {
          void (*func)(id, SEL) = (void *)imp;
          func(cls, selector);
        }
      }
    }
    free(methods);
  }
  free(classes);
#if !defined(__has_feature) || !__has_feature(objc_arc)
  [pool drain];
#else
  }  // @autoreleasepool
#endif
}

#endif  // DEBUG