aboutsummaryrefslogtreecommitdiff
path: root/DebugUtils/GTMMethodCheck.m
blob: 09cadc95925e70aa855a2c7d5fb3fa38a5010521 (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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
//
//  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>

// Checks to see if the cls passed in (or one of it's superclasses) conforms
// to NSObject protocol. Inheriting from NSObject is the easiest way to do this
// but not all classes (i.e. NSProxy) inherit from NSObject. Also, some classes
// inherit from Object instead of NSObject which is fine, and we'll count as
// conforming to NSObject for our needs.
static BOOL ConformsToNSObjectProtocol(Class cls) {
  // If we get nil, obviously doesn't conform.
  if (!cls) return NO;
  const char *className = class_getName(cls);
  if (!className) return NO;

  // We're going to assume that all Apple classes will work
  // (and aren't being checked)
  // Note to apple: why doesn't obj-c have real namespaces instead of two
  // letter hacks? If you name your own classes starting with NS this won't
  // work for you.
  // Some classes (like _NSZombie) start with _NS.
  // On Leopard we have to look for CFObject as well.
  // On iPhone we check Object as well
  if ((strncmp(className, "NS", 2) == 0)
       || (strncmp(className, "_NS", 3) == 0)
       || (strncmp(className, "__NS", 4) == 0)
       || (strcmp(className, "CFObject") == 0)
       || (strcmp(className, "__IncompleteProtocol") == 0)
       || (strcmp(className, "__ARCLite__") == 0)
       || (strcmp(className, "WebMIMETypeRegistry") == 0)
       || (strcmp(className, "Object") == 0)
#if GTM_IPHONE_SDK
       || (strcmp(className, "UIKeyboardCandidateUtilities") == 0)
       || (strcmp(className, "JSExport") == 0)
#endif
    ) {
    return YES;
  }

  // iPhone and Mac OS X 10.8 with Obj-C 2 SDKs do not define the |Object|
  // class, so we instead test for the |NSObject| class.
#if GTM_IPHONE_SDK || \
    (__OBJC2__ && defined(MAC_OS_X_VERSION_10_8) && \
     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_8)
  // Iterate through all the protocols |cls| supports looking for NSObject.
  if (cls == [NSObject class]
      || class_conformsToProtocol(cls, @protocol(NSObject))) {
    return YES;
  }
#else
  // Iterate through all the protocols |cls| supports looking for NSObject.
  if (cls == [Object class]
      || class_conformsToProtocol(cls, @protocol(NSObject))) {
    return YES;
  }
#endif

  // Recursively check the superclasses.
  return ConformsToNSObjectProtocol(class_getSuperclass(cls));
}

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
  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];

    // Since we are directly calling objc_msgSend, we need to conform to
    // @protocol(NSObject), or else we will tumble into a _objc_msgForward
    // recursive loop when we try and call a function by name.
    if (!ConformsToNSObjectProtocol(cls)) {
      // COV_NF_START
      _GTMDevLog(@"GTMMethodCheckMethodChecker: Class %s does not conform to "
                 "@protocol(NSObject), so won't be checked",
                 class_getName(cls));
      continue;
      // COV_NF_END
    }
    // 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.
    unsigned int count;
    Class metaClass = objc_getMetaClass(class_getName(cls));
    Method *methods = class_copyMethodList(metaClass, &count);
    unsigned int j;
    for (j = 0; j < count; ++j) {
      SEL selector = method_getName(methods[j]);
      const char *name = sel_getName(selector);
      if (strstr(name, "xxGTMMethodCheckMethod") == name) {
        // Check to make sure that the method we are checking comes
        // from the same image that we are in. Since GTMMethodCheckMethodChecker
        // is not exported, we should always find the copy in our local
        // image. We compare the address of it's image 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
        Dl_info methodCheckerInfo;
        if (!dladdr(GTMMethodCheckMethodChecker,
                    &methodCheckerInfo)) {
          // COV_NF_START
          // Don't know how to force this case in a unittest.
          // Certainly hope we never see it.
          _GTMDevLog(@"GTMMethodCheckMethodChecker: Unable to get dladdr info "
                "for GTMMethodCheckMethodChecker while introspecting +[%s %s]]",
                class_getName(cls), name);
          continue;
          // COV_NF_END
        }
        Dl_info methodInfo;
        if (!dladdr(method_getImplementation(methods[j]),
                    &methodInfo)) {
          // COV_NF_START
          // Don't know how to force this case in a unittest
          // Certainly hope we never see it.
          _GTMDevLog(@"GTMMethodCheckMethodChecker: Unable to get dladdr info "
                     "for %s while introspecting +[%s %s]]", name,
                     class_getName(cls), name);
          continue;
          // COV_NF_END
        }
        if (methodCheckerInfo.dli_fbase == methodInfo.dli_fbase) {
          typedef void (*GTMMethodCheckMethod)(Class, SEL);
          GTMMethodCheckMethod func = (GTMMethodCheckMethod)objc_msgSend;
          func(cls, selector);
        }
      }
    }
    free(methods);
  }
  free(classes);
#if !defined(__has_feature) || !__has_feature(objc_arc)
  [pool drain];
#else
  }  // @autoreleasepool
#endif
}

#endif  // DEBUG