aboutsummaryrefslogtreecommitdiff
path: root/DebugUtils/GTMMethodCheck.m
diff options
context:
space:
mode:
authorGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-04-14 17:21:02 +0000
committerGravatar thomasvl <thomasvl@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2008-04-14 17:21:02 +0000
commitcdf070c8d76ffc4eaa24e8671756cbbe9ceb2890 (patch)
treefaa9ae3a72a6591d6a6add7ceed7f91e92ade11f /DebugUtils/GTMMethodCheck.m
parent0aaecac6ff2bc89e58a0c8c6d6ad62e02fb2b011 (diff)
See the ReleaseNotes for the full details, highlights:
- bug fixes - code coverage support - more complete unittests - full support for unittesting UIs - support for the iphone sdk (include ui unittesting)
Diffstat (limited to 'DebugUtils/GTMMethodCheck.m')
-rw-r--r--DebugUtils/GTMMethodCheck.m144
1 files changed, 144 insertions, 0 deletions
diff --git a/DebugUtils/GTMMethodCheck.m b/DebugUtils/GTMMethodCheck.m
new file mode 100644
index 0000000..379f006
--- /dev/null
+++ b/DebugUtils/GTMMethodCheck.m
@@ -0,0 +1,144 @@
+//
+// 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.
+ if ((strncmp(className, "NS", 2) == 0) ||
+ (strncmp(className, "_NS", 3) == 0) ||
+ (strcmp(className, "CFObject") == 0)) {
+ return YES;
+ }
+
+ // Iterate through all the protocols |cls| supports looking for NSObject.
+ if (cls == [Object class]
+ || class_conformsToProtocol(cls, @protocol(NSObject))) {
+ return YES;
+ }
+
+ // 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.
+ int numClasses = 0;
+ int newNumClasses = objc_getClassList(NULL, 0);
+ int i;
+ Class *classes = NULL;
+ while (numClasses < newNumClasses) {
+ numClasses = newNumClasses;
+ classes = realloc(classes, sizeof(Class) * numClasses);
+ _GTMDevAssert(classes, @"Unable to allocate memory for classes");
+ newNumClasses = objc_getClassList(classes, numClasses);
+ }
+ for (i = 0; i < numClasses; ++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)) {
+ _GTMDevLog(@"GTMMethodCheckMethodChecker: Class %s does not conform to "
+ "@protocol(NSObject), so won't be checked",
+ class_getName(cls));
+ continue;
+ }
+ // 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 +[%@ %@]]",
+ 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 %@ while introspecting +[%@ %@]]", name,
+ class_getName(cls), name);
+ continue;
+ // COV_NF_END
+ }
+ if (methodCheckerInfo.dli_fbase == methodInfo.dli_fbase) {
+ objc_msgSend(cls, selector);
+ }
+ }
+ }
+ if (methods) {
+ free(methods);
+ }
+ }
+ if (classes) {
+ free(classes);
+ }
+}
+
+#endif // DEBUG