aboutsummaryrefslogtreecommitdiff
path: root/DebugUtils
diff options
context:
space:
mode:
Diffstat (limited to 'DebugUtils')
-rw-r--r--DebugUtils/GTMDebugSelectorValidation.h86
-rw-r--r--DebugUtils/GTMMethodCheck.h88
-rw-r--r--DebugUtils/GTMMethodCheck.m144
-rw-r--r--DebugUtils/GTMMethodCheckTest.m69
4 files changed, 387 insertions, 0 deletions
diff --git a/DebugUtils/GTMDebugSelectorValidation.h b/DebugUtils/GTMDebugSelectorValidation.h
new file mode 100644
index 0000000..b3f1e73
--- /dev/null
+++ b/DebugUtils/GTMDebugSelectorValidation.h
@@ -0,0 +1,86 @@
+//
+// GTMDebugSelectorValidation.h
+//
+// This file should only be included within an implimation file. In any
+// function that takes an object and selector to invoke, you should call:
+//
+// GTMAssertSelectorNilOrImplementedWithArguments(obj, sel, @encode(arg1type), ..., NULL)
+//
+// This will then validate that the selector is defined and using the right
+// type(s), this can help catch errors much earlier then waiting for the
+// selector to actually fire (and in the case of error selectors, might never
+// really be tested until in the field).
+//
+// Copyright 2007-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.
+//
+
+#if DEBUG
+
+#import <stdarg.h>
+#import "GTMDefines.h"
+
+static void GTMAssertSelectorNilOrImplementedWithArguments(id obj, SEL sel, ...) {
+
+ // verify that the object's selector is implemented with the proper
+ // number and type of arguments
+ va_list argList;
+ va_start(argList, sel);
+
+ if (obj && sel) {
+ // check that the selector is implemented
+ if (![obj respondsToSelector:sel]) {
+ _GTMDevAssert(NO,
+ @"\"%@\" selector \"%@\" is unimplemented or misnamed",
+ NSStringFromClass([obj class]),
+ NSStringFromSelector(sel));
+ } else {
+ const char *expectedArgType;
+ int argCount = 2; // skip self and _cmd
+ NSMethodSignature *sig = [obj methodSignatureForSelector:sel];
+
+ // check that each expected argument is present and of the correct type
+ while ((expectedArgType = va_arg(argList, const char*)) != 0) {
+
+ if ([sig numberOfArguments] > argCount) {
+ const char *foundArgType = [sig getArgumentTypeAtIndex:argCount];
+
+ _GTMDevAssert(0 == strncmp(foundArgType, expectedArgType, strlen(expectedArgType)),
+ @"\"%@\" selector \"%@\" argument %d should be type %s",
+ NSStringFromClass([obj class]),
+ NSStringFromSelector(sel),
+ (argCount - 2),
+ expectedArgType);
+ }
+ argCount++;
+ }
+
+ // check that the proper number of arguments are present in the selector
+ _GTMDevAssert(argCount == [sig numberOfArguments],
+ @"\"%@\" selector \"%@\" should have %d arguments",
+ NSStringFromClass([obj class]),
+ NSStringFromSelector(sel),
+ (argCount - 2));
+ }
+ }
+
+ va_end(argList);
+}
+
+#else // DEBUG
+
+// make it go away if not debug
+#define GTMAssertSelectorNilOrImplementedWithArguments(obj, sel, ...) do { } while (0)
+
+#endif // DEBUG
diff --git a/DebugUtils/GTMMethodCheck.h b/DebugUtils/GTMMethodCheck.h
new file mode 100644
index 0000000..0915c0b
--- /dev/null
+++ b/DebugUtils/GTMMethodCheck.h
@@ -0,0 +1,88 @@
+//
+// GTMMethodCheck.h
+//
+// 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.
+//
+
+#import <Foundation/Foundation.h>
+#import <stdio.h>
+#import <sysexits.h>
+
+/// A macro for enforcing debug time checks to make sure all required methods are linked in
+//
+// When using categories, it can be very easy to forget to include the
+// implementation of a category.
+// Let's say you had a class foo that depended on method bar of class baz, and
+// method bar was implemented as a member of a category.
+// You could add the following code:
+// @implementation foo
+// GTM_METHOD_CHECK(baz, bar)
+// @end
+// and the code would check to make sure baz was implemented just before main
+// was called. This works for both dynamic libraries, and executables.
+//
+// Classes (or one of their superclasses) being checked must conform to the
+// NSObject protocol. We will check this, and spit out a warning if a class does
+// not conform to NSObject.
+//
+// This is not compiled into release builds.
+
+#ifdef DEBUG
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// If you get an error for GTMMethodCheckMethodChecker not being defined,
+// you need to link in GTMMethodCheck.m. We keep it hidden so that we can have
+// it living in several separate images without conflict.
+// Functions with the ((constructor)) attribute are called after all +loads
+// have been called. See "Initializing Objective-C Classes" in
+// http://developer.apple.com/documentation/DeveloperTools/Conceptual/DynamicLibraries/Articles/DynamicLibraryDesignGuidelines.html#//apple_ref/doc/uid/TP40002013-DontLinkElementID_20
+
+__attribute__ ((constructor, visibility("hidden"))) void GTMMethodCheckMethodChecker(void);
+
+#ifdef __cplusplus
+};
+#endif
+
+// This is the "magic".
+// A) we need a multi layer define here so that the stupid preprocessor
+// expands __LINE__ out the way we want it. We need LINE so that each of
+// out GTM_METHOD_CHECKs generates a unique class method for the class.
+#define GTM_METHOD_CHECK(class, method) GTM_METHOD_CHECK_INNER(class, method, __LINE__)
+#define GTM_METHOD_CHECK_INNER(class, method, line) GTM_METHOD_CHECK_INNER_INNER(class, method, line)
+
+// B) Create up a class method called xxGMethodCheckMethod+class+line that the
+// GTMMethodCheckMethodChecker function can look for and call. We
+// look for GTMMethodCheckMethodChecker to enforce linkage of
+// GTMMethodCheck.m.
+#define GTM_METHOD_CHECK_INNER_INNER(class, method, line) \
++ (void)xxGMMethodCheckMethod ## class ## line { \
+ void (*addr)() = GTMMethodCheckMethodChecker; \
+ if (addr && ![class instancesRespondToSelector:@selector(method)] \
+ && ![class respondsToSelector:@selector(method)]) { \
+ fprintf(stderr, "%s:%d: error: We need method '%s' to be linked in for class '%s'\n", \
+ __FILE__, line, #method, #class); \
+ exit(EX_SOFTWARE); \
+ } \
+}
+
+#else // !DEBUG
+
+// Do nothing in debug
+#define GTM_METHOD_CHECK(class, method)
+
+#endif // DEBUG
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
diff --git a/DebugUtils/GTMMethodCheckTest.m b/DebugUtils/GTMMethodCheckTest.m
new file mode 100644
index 0000000..b5e7e6b
--- /dev/null
+++ b/DebugUtils/GTMMethodCheckTest.m
@@ -0,0 +1,69 @@
+//
+// GTMMethodCheckTest.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.
+//
+
+#import "GTMSenTestCase.h"
+#import "GTMMethodCheck.h"
+
+static BOOL gTestCheckVar = NO;
+
+// This is a contrived class that doesn't inherit from NSObject, but does
+// implement some of it's functionality to force test a case in
+// GTMMethodCheck.
+@interface GTMClassThatDoesntInheritFromNSObject
++ (BOOL)instancesRespondToSelector:(SEL)selector;
++ (BOOL)respondsToSelector:(SEL)selector;
+@end
+
+@implementation GTMClassThatDoesntInheritFromNSObject
+GTM_METHOD_CHECK(GTMClassThatDoesntInheritFromNSObject, GTMMethodCheckTestMethod);
+- (void)GTMMethodCheckTestMethod {
+}
++ (BOOL)instancesRespondToSelector:(SEL)selector {
+ return YES;
+}
+
++ (BOOL)respondsToSelector:(SEL)selector {
+ return YES;
+}
+@end
+
+@interface GTMMethodCheckTest : SenTestCase
+@end
+
+@implementation GTMMethodCheckTest
+GTM_METHOD_CHECK(GTMMethodCheckTest, GTMMethodCheckTestMethod);
+GTM_METHOD_CHECK(GTMMethodCheckTest, GTMMethodCheckTestClassMethod);
+
+- (void)GTMMethodCheckTestMethod {
+}
+
++ (void)GTMMethodCheckTestClassMethod {
+}
+
++ (void)xxGTMMethodCheckMethodTestCheck {
+ // This gets called because of its special name by GMMethodCheck
+ // Look at the Macros in GMMethodCheck.h for details.
+ gTestCheckVar = YES;
+}
+
+- (void)testGTMMethodCheck {
+#ifdef DEBUG
+ // GTMMethodCheck only runs in debug
+ STAssertTrue(gTestCheckVar, @"Should be true");
+#endif
+}
+@end