├── FitbitFBBinderVisitor.cpp ├── FitbitFBBinderVisitor.h ├── KVC Warning Test ├── Department.h ├── Department.m ├── Employee.h ├── Employee.m ├── KVC Warning Test.xcodeproj │ └── project.pbxproj ├── clang_warning_wrapper.sh └── main.m ├── KeyPathValidationConsumer.cpp ├── KeyPathValidationConsumer.h ├── KeyPathValidator.exports ├── KeyPathsAffectingVisitor.cpp ├── KeyPathsAffectingVisitor.h ├── LICENSE.txt ├── Makefile ├── README.md ├── ValidateKeyPathsAction.cpp ├── ValueForKeyVisitor.cpp ├── ValueForKeyVisitor.h └── test ├── basic.m └── binder.m /FitbitFBBinderVisitor.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // FitbitFBBinderVisitor.cpp 3 | // Created by Jonathon Mah on 2014-01-25. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #include "FitbitFBBinderVisitor.h" 10 | 11 | using namespace clang; 12 | 13 | 14 | FBBinderVisitor::FBBinderVisitor(KeyPathValidationConsumer *Consumer, const CompilerInstance &Compiler) 15 | : Consumer(Consumer) 16 | , Compiler(Compiler) 17 | { 18 | ASTContext &Ctx = Compiler.getASTContext(); 19 | IdentifierTable &IDs = Ctx.Idents; 20 | IdentifierInfo *BindIIs[3] = {&IDs.get("bindToModel"), &IDs.get("keyPath"), &IDs.get("change")}; 21 | BindSelector = Ctx.Selectors.getSelector(3, BindIIs); 22 | 23 | IdentifierInfo *BindMultipleIIs[3] = {&IDs.get("bindToModels"), &IDs.get("keyPaths"), &IDs.get("change")}; 24 | BindMultipleSelector = Ctx.Selectors.getSelector(3, BindMultipleIIs); 25 | BindMultipleCountMismatchDiagID = Compiler.getDiagnostics().getCustomDiagID(DiagnosticsEngine::Error, "model and key path arrays must have same number of elements"); 26 | } 27 | 28 | bool FBBinderVisitor::VisitObjCMessageExpr(ObjCMessageExpr *E) { 29 | if (E->getNumArgs() != 3 || !E->isInstanceMessage()) 30 | return true; 31 | 32 | if (E->getSelector() == BindSelector) 33 | Consumer->emitDiagnosticsForReceiverAndKeyPath(E->getArg(0), E->getArg(1)); 34 | 35 | if (E->getSelector() == BindMultipleSelector) { 36 | ObjCArrayLiteral *ModelsLiteral = dyn_cast(E->getArg(0)->IgnoreImplicit()); 37 | ObjCArrayLiteral *KeyPathsLiterals = dyn_cast(E->getArg(1)->IgnoreImplicit()); 38 | 39 | if (ModelsLiteral && KeyPathsLiterals) { 40 | if (ModelsLiteral->getNumElements() == KeyPathsLiterals->getNumElements()) { 41 | for (unsigned ModelIdx = 0, ModelCount = ModelsLiteral->getNumElements(); 42 | ModelIdx < ModelCount; ++ModelIdx) { 43 | Expr *ModelExpr = ModelsLiteral->getElement(ModelIdx); 44 | Expr *KeyPathsExpr = KeyPathsLiterals->getElement(ModelIdx); 45 | 46 | if (ObjCArrayLiteral *KeyPathsLiteral = dyn_cast(KeyPathsExpr->IgnoreImplicit())) { 47 | for (unsigned KeyPathIdx = 0, KeyPathCount = KeyPathsLiteral->getNumElements(); 48 | KeyPathIdx < KeyPathCount; ++KeyPathIdx) { 49 | Expr *KeyPathExpr = KeyPathsLiteral->getElement(KeyPathIdx); 50 | Consumer->emitDiagnosticsForReceiverAndKeyPath(ModelExpr, KeyPathExpr); 51 | } 52 | } 53 | } 54 | 55 | } else { 56 | Compiler.getDiagnostics().Report(ModelsLiteral->getLocStart(), BindMultipleCountMismatchDiagID) 57 | << ModelsLiteral << KeyPathsLiterals; 58 | } 59 | } 60 | } 61 | 62 | return true; 63 | } 64 | -------------------------------------------------------------------------------- /FitbitFBBinderVisitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // FitbitFBBinderVisitor.h 3 | // Created by Jonathon Mah on 2014-01-25. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #ifndef LLVM_CLANG_FITBIT_FBBINDER_VISITOR_H 10 | #define LLVM_CLANG_FITBIT_FBBINDER_VISITOR_H 11 | 12 | #include "KeyPathValidationConsumer.h" 13 | #include "clang/AST/RecursiveASTVisitor.h" 14 | #include "clang/Frontend/CompilerInstance.h" 15 | 16 | using namespace clang; 17 | 18 | 19 | class FBBinderVisitor : public RecursiveASTVisitor { 20 | private: 21 | KeyPathValidationConsumer *Consumer; 22 | const CompilerInstance &Compiler; 23 | Selector BindSelector; 24 | Selector BindMultipleSelector; 25 | unsigned BindMultipleCountMismatchDiagID; 26 | 27 | public: 28 | FBBinderVisitor(KeyPathValidationConsumer *Consumer, const CompilerInstance &Compiler); 29 | 30 | bool shouldVisitTemplateInstantiations() const { return false; } 31 | bool shouldWalkTypesOfTypeLocs() const { return false; } 32 | 33 | bool VisitObjCMessageExpr(ObjCMessageExpr *E); 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /KVC Warning Test/Department.h: -------------------------------------------------------------------------------- 1 | // 2 | // Department.h 3 | // KVC Warning Test 4 | // 5 | // Created by Jonathon Mah on 2014-05-15. 6 | // Copyright (c) 2014 Jonathon Mah. All rights reserved. 7 | // 8 | 9 | #import 10 | @class Employee; 11 | 12 | @interface Department : NSObject 13 | 14 | @property (nonatomic, copy) NSString *name; 15 | @property (nonatomic) Employee *leadEmployee; 16 | 17 | @property (nonatomic, getter = isActive) BOOL active; 18 | @property (nonatomic, getter = fooNotValueForKeyCompliant) id foo; 19 | 20 | @property (nonatomic, readonly, getter = employeesKVCProxy) NSMutableSet *employees; 21 | 22 | @end 23 | -------------------------------------------------------------------------------- /KVC Warning Test/Department.m: -------------------------------------------------------------------------------- 1 | // 2 | // Department.m 3 | // KVC Warning Test 4 | // 5 | // Created by Jonathon Mah on 2014-05-15. 6 | // Copyright (c) 2014 Jonathon Mah. All rights reserved. 7 | // 8 | 9 | #import "Department.h" 10 | #import "Employee.h" 11 | 12 | 13 | @implementation Department { 14 | NSMutableSet *_backingEmployees; 15 | } 16 | 17 | - (id)init 18 | { 19 | if (!(self = [super init])) 20 | return nil; 21 | _backingEmployees = [NSMutableSet new]; 22 | return self; 23 | } 24 | 25 | 26 | - (NSString *)description 27 | { 28 | NSSet *employeesProxy = self.employees; 29 | employeesProxy = [self valueForKeyPath:@"employees"]; // ok, despite no -employees method 30 | 31 | NSNumber *active = [self valueForKey:@"active"]; // ok, uses -isActive 32 | 33 | id foo = self.foo; 34 | foo = [self valueForKey:@"foo"]; // oops 35 | foo = [(id)self valueForKey:@"foo"]; // cast to id suppresses warning 36 | foo = [self valueForKey:@"fooNotValueForKeyCompliant"]; 37 | 38 | return [NSString stringWithFormat:@"<%@ %p employees=%@ active=%@>", 39 | [self class], self, employeesProxy, active]; 40 | } 41 | 42 | 43 | #pragma mark Employees Collection 44 | 45 | - (NSMutableSet *)employeesKVCProxy 46 | { return [self valueForKey:@"employees"]; } 47 | 48 | // "Unordered Accessor Pattern" 49 | - (NSUInteger)countOfEmployees 50 | { return _backingEmployees.count; } 51 | 52 | - (NSEnumerator *)enumeratorOfEmployees 53 | { return [_backingEmployees objectEnumerator]; } 54 | 55 | - (Employee *)memberOfEmployees:(Employee *)employee 56 | { return [_backingEmployees member:employee]; } 57 | 58 | - (void)addEmployeesObject:(Employee *)employee 59 | { [_backingEmployees addObject:employee]; } 60 | 61 | - (void)removeEmployeesObject:(Employee *)employee 62 | { [_backingEmployees removeObject:employee]; } 63 | 64 | @end 65 | -------------------------------------------------------------------------------- /KVC Warning Test/Employee.h: -------------------------------------------------------------------------------- 1 | // 2 | // Employee.h 3 | // KVC Warning Test 4 | // 5 | // Created by Jonathon Mah on 2014-05-15. 6 | // Copyright (c) 2014 Jonathon Mah. All rights reserved. 7 | // 8 | 9 | #import 10 | @class Department; 11 | 12 | @interface Employee : NSObject 13 | 14 | @property (nonatomic, copy) NSString *firstName, *lastName; 15 | @property (nonatomic, readonly) NSString *fullName; 16 | 17 | @property (nonatomic) Department *department; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /KVC Warning Test/Employee.m: -------------------------------------------------------------------------------- 1 | // 2 | // Employee.m 3 | // KVC Warning Test 4 | // 5 | // Created by Jonathon Mah on 2014-05-15. 6 | // Copyright (c) 2014 Jonathon Mah. All rights reserved. 7 | // 8 | 9 | #import "Employee.h" 10 | #import "Department.h" 11 | 12 | @implementation Employee 13 | 14 | - (NSString *)description 15 | { 16 | NSString *leadName; 17 | leadName = [self valueForKeyPath:@"department.laedEmployee.fulllName"]; // oops 18 | 19 | leadName = [self valueForKeyPath:@"department.leadEmployee.fulllName"]; // oops 20 | 21 | leadName = [self valueForKeyPath:@"department.leadEmployee.fullName"]; 22 | 23 | leadName = [self valueForKey:@"department.leadEmployee.fullName"]; // oops 24 | 25 | leadName = [self valueForKeyPath:@"department.leadEmployee.fullName.lenght"]; // oops 26 | 27 | return [NSString stringWithFormat:@"<%@ %p '%@' departmentLead=%@>", 28 | [self class], self, [self valueForKey:@"fullName"], leadName]; 29 | } 30 | 31 | 32 | + (NSSet *)keyPathsForValuesAffectingFullName 33 | { 34 | return [NSSet setWithObjects: 35 | @"firstName", 36 | @"fristName", // oops 37 | @"lastName", 38 | nil]; 39 | } 40 | 41 | - (NSString *)fullName 42 | { 43 | return [@[self.firstName, self.lastName] componentsJoinedByString:@" "]; 44 | } 45 | 46 | 47 | + (NSSet *)keyPathsForValuesAffectingTestDeps 48 | { 49 | return [NSSet setWithObjects: 50 | @"self", 51 | @"privateDeclaredBeneath", 52 | @"fulllName", // oops 53 | nil]; 54 | } 55 | - (id)testDeps 56 | { return nil; } 57 | 58 | - (id)privateDeclaredBeneath 59 | { return nil; } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /KVC Warning Test/KVC Warning Test.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0B34F1961925D54400C4E87B /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0B34F1951925D54400C4E87B /* Foundation.framework */; }; 11 | 0B34F1991925D54400C4E87B /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B34F1981925D54400C4E87B /* main.m */; }; 12 | 0B34F1A81925D73000C4E87B /* Department.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B34F1A71925D73000C4E87B /* Department.m */; }; 13 | 0B34F1AB1925D73700C4E87B /* Employee.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B34F1AA1925D73700C4E87B /* Employee.m */; }; 14 | /* End PBXBuildFile section */ 15 | 16 | /* Begin PBXCopyFilesBuildPhase section */ 17 | 0B34F1901925D54400C4E87B /* CopyFiles */ = { 18 | isa = PBXCopyFilesBuildPhase; 19 | buildActionMask = 2147483647; 20 | dstPath = /usr/share/man/man1/; 21 | dstSubfolderSpec = 0; 22 | files = ( 23 | ); 24 | runOnlyForDeploymentPostprocessing = 1; 25 | }; 26 | /* End PBXCopyFilesBuildPhase section */ 27 | 28 | /* Begin PBXFileReference section */ 29 | 0B34F1921925D54400C4E87B /* KVC Warning Test */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "KVC Warning Test"; sourceTree = BUILT_PRODUCTS_DIR; }; 30 | 0B34F1951925D54400C4E87B /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; 31 | 0B34F1981925D54400C4E87B /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; usesTabs = 0; }; 32 | 0B34F1A61925D73000C4E87B /* Department.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Department.h; sourceTree = ""; }; 33 | 0B34F1A71925D73000C4E87B /* Department.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Department.m; sourceTree = ""; }; 34 | 0B34F1A91925D73700C4E87B /* Employee.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Employee.h; sourceTree = ""; }; 35 | 0B34F1AA1925D73700C4E87B /* Employee.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Employee.m; sourceTree = ""; }; 36 | /* End PBXFileReference section */ 37 | 38 | /* Begin PBXFrameworksBuildPhase section */ 39 | 0B34F18F1925D54400C4E87B /* Frameworks */ = { 40 | isa = PBXFrameworksBuildPhase; 41 | buildActionMask = 2147483647; 42 | files = ( 43 | 0B34F1961925D54400C4E87B /* Foundation.framework in Frameworks */, 44 | ); 45 | runOnlyForDeploymentPostprocessing = 0; 46 | }; 47 | /* End PBXFrameworksBuildPhase section */ 48 | 49 | /* Begin PBXGroup section */ 50 | 0B34F1891925D54400C4E87B = { 51 | isa = PBXGroup; 52 | children = ( 53 | 0B34F1A91925D73700C4E87B /* Employee.h */, 54 | 0B34F1AA1925D73700C4E87B /* Employee.m */, 55 | 0B34F1A61925D73000C4E87B /* Department.h */, 56 | 0B34F1A71925D73000C4E87B /* Department.m */, 57 | 0B34F1971925D54400C4E87B /* KVC Warning Test */, 58 | 0B34F1941925D54400C4E87B /* Frameworks */, 59 | 0B34F1931925D54400C4E87B /* Products */, 60 | ); 61 | sourceTree = ""; 62 | }; 63 | 0B34F1931925D54400C4E87B /* Products */ = { 64 | isa = PBXGroup; 65 | children = ( 66 | 0B34F1921925D54400C4E87B /* KVC Warning Test */, 67 | ); 68 | name = Products; 69 | sourceTree = ""; 70 | }; 71 | 0B34F1941925D54400C4E87B /* Frameworks */ = { 72 | isa = PBXGroup; 73 | children = ( 74 | 0B34F1951925D54400C4E87B /* Foundation.framework */, 75 | ); 76 | name = Frameworks; 77 | sourceTree = ""; 78 | }; 79 | 0B34F1971925D54400C4E87B /* KVC Warning Test */ = { 80 | isa = PBXGroup; 81 | children = ( 82 | 0B34F1981925D54400C4E87B /* main.m */, 83 | ); 84 | name = "KVC Warning Test"; 85 | sourceTree = ""; 86 | }; 87 | /* End PBXGroup section */ 88 | 89 | /* Begin PBXNativeTarget section */ 90 | 0B34F1911925D54400C4E87B /* KVC Warning Test */ = { 91 | isa = PBXNativeTarget; 92 | buildConfigurationList = 0B34F1A01925D54400C4E87B /* Build configuration list for PBXNativeTarget "KVC Warning Test" */; 93 | buildPhases = ( 94 | 0B34F18E1925D54400C4E87B /* Sources */, 95 | 0B34F18F1925D54400C4E87B /* Frameworks */, 96 | 0B34F1901925D54400C4E87B /* CopyFiles */, 97 | ); 98 | buildRules = ( 99 | ); 100 | dependencies = ( 101 | ); 102 | name = "KVC Warning Test"; 103 | productName = "KVC Warning Test"; 104 | productReference = 0B34F1921925D54400C4E87B /* KVC Warning Test */; 105 | productType = "com.apple.product-type.tool"; 106 | }; 107 | /* End PBXNativeTarget section */ 108 | 109 | /* Begin PBXProject section */ 110 | 0B34F18A1925D54400C4E87B /* Project object */ = { 111 | isa = PBXProject; 112 | attributes = { 113 | LastUpgradeCheck = 0510; 114 | ORGANIZATIONNAME = "Jonathon Mah"; 115 | }; 116 | buildConfigurationList = 0B34F18D1925D54400C4E87B /* Build configuration list for PBXProject "KVC Warning Test" */; 117 | compatibilityVersion = "Xcode 3.2"; 118 | developmentRegion = English; 119 | hasScannedForEncodings = 0; 120 | knownRegions = ( 121 | en, 122 | ); 123 | mainGroup = 0B34F1891925D54400C4E87B; 124 | productRefGroup = 0B34F1931925D54400C4E87B /* Products */; 125 | projectDirPath = ""; 126 | projectRoot = ""; 127 | targets = ( 128 | 0B34F1911925D54400C4E87B /* KVC Warning Test */, 129 | ); 130 | }; 131 | /* End PBXProject section */ 132 | 133 | /* Begin PBXSourcesBuildPhase section */ 134 | 0B34F18E1925D54400C4E87B /* Sources */ = { 135 | isa = PBXSourcesBuildPhase; 136 | buildActionMask = 2147483647; 137 | files = ( 138 | 0B34F1991925D54400C4E87B /* main.m in Sources */, 139 | 0B34F1AB1925D73700C4E87B /* Employee.m in Sources */, 140 | 0B34F1A81925D73000C4E87B /* Department.m in Sources */, 141 | ); 142 | runOnlyForDeploymentPostprocessing = 0; 143 | }; 144 | /* End PBXSourcesBuildPhase section */ 145 | 146 | /* Begin XCBuildConfiguration section */ 147 | 0B34F19E1925D54400C4E87B /* Debug */ = { 148 | isa = XCBuildConfiguration; 149 | buildSettings = { 150 | ALWAYS_SEARCH_USER_PATHS = NO; 151 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 152 | CLANG_CXX_LIBRARY = "libc++"; 153 | CLANG_ENABLE_MODULES = YES; 154 | CLANG_ENABLE_OBJC_ARC = YES; 155 | CLANG_WARN_BOOL_CONVERSION = YES; 156 | CLANG_WARN_CONSTANT_CONVERSION = YES; 157 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 158 | CLANG_WARN_EMPTY_BODY = YES; 159 | CLANG_WARN_ENUM_CONVERSION = YES; 160 | CLANG_WARN_INT_CONVERSION = YES; 161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 162 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 163 | COPY_PHASE_STRIP = NO; 164 | GCC_C_LANGUAGE_STANDARD = gnu99; 165 | GCC_DYNAMIC_NO_PIC = NO; 166 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 167 | GCC_OPTIMIZATION_LEVEL = 0; 168 | GCC_PREPROCESSOR_DEFINITIONS = ( 169 | "DEBUG=1", 170 | "$(inherited)", 171 | ); 172 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 173 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 174 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 175 | GCC_WARN_UNDECLARED_SELECTOR = YES; 176 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 177 | GCC_WARN_UNUSED_FUNCTION = YES; 178 | GCC_WARN_UNUSED_VARIABLE = YES; 179 | MACOSX_DEPLOYMENT_TARGET = 10.9; 180 | ONLY_ACTIVE_ARCH = YES; 181 | SDKROOT = macosx; 182 | }; 183 | name = Debug; 184 | }; 185 | 0B34F19F1925D54400C4E87B /* Release */ = { 186 | isa = XCBuildConfiguration; 187 | buildSettings = { 188 | ALWAYS_SEARCH_USER_PATHS = NO; 189 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 190 | CLANG_CXX_LIBRARY = "libc++"; 191 | CLANG_ENABLE_MODULES = YES; 192 | CLANG_ENABLE_OBJC_ARC = YES; 193 | CLANG_WARN_BOOL_CONVERSION = YES; 194 | CLANG_WARN_CONSTANT_CONVERSION = YES; 195 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 196 | CLANG_WARN_EMPTY_BODY = YES; 197 | CLANG_WARN_ENUM_CONVERSION = YES; 198 | CLANG_WARN_INT_CONVERSION = YES; 199 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 201 | COPY_PHASE_STRIP = YES; 202 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 203 | ENABLE_NS_ASSERTIONS = NO; 204 | GCC_C_LANGUAGE_STANDARD = gnu99; 205 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 208 | GCC_WARN_UNDECLARED_SELECTOR = YES; 209 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 210 | GCC_WARN_UNUSED_FUNCTION = YES; 211 | GCC_WARN_UNUSED_VARIABLE = YES; 212 | MACOSX_DEPLOYMENT_TARGET = 10.9; 213 | SDKROOT = macosx; 214 | }; 215 | name = Release; 216 | }; 217 | 0B34F1A11925D54400C4E87B /* Debug */ = { 218 | isa = XCBuildConfiguration; 219 | buildSettings = { 220 | CC = "$(SRCROOT)/clang_warning_wrapper.sh"; 221 | PRODUCT_NAME = "$(TARGET_NAME)"; 222 | }; 223 | name = Debug; 224 | }; 225 | 0B34F1A21925D54400C4E87B /* Release */ = { 226 | isa = XCBuildConfiguration; 227 | buildSettings = { 228 | CC = "$(SRCROOT)/clang_warning_wrapper.sh"; 229 | PRODUCT_NAME = "$(TARGET_NAME)"; 230 | }; 231 | name = Release; 232 | }; 233 | /* End XCBuildConfiguration section */ 234 | 235 | /* Begin XCConfigurationList section */ 236 | 0B34F18D1925D54400C4E87B /* Build configuration list for PBXProject "KVC Warning Test" */ = { 237 | isa = XCConfigurationList; 238 | buildConfigurations = ( 239 | 0B34F19E1925D54400C4E87B /* Debug */, 240 | 0B34F19F1925D54400C4E87B /* Release */, 241 | ); 242 | defaultConfigurationIsVisible = 0; 243 | defaultConfigurationName = Release; 244 | }; 245 | 0B34F1A01925D54400C4E87B /* Build configuration list for PBXNativeTarget "KVC Warning Test" */ = { 246 | isa = XCConfigurationList; 247 | buildConfigurations = ( 248 | 0B34F1A11925D54400C4E87B /* Debug */, 249 | 0B34F1A21925D54400C4E87B /* Release */, 250 | ); 251 | defaultConfigurationIsVisible = 0; 252 | defaultConfigurationName = Release; 253 | }; 254 | /* End XCConfigurationList section */ 255 | }; 256 | rootObject = 0B34F18A1925D54400C4E87B /* Project object */; 257 | } 258 | -------------------------------------------------------------------------------- /KVC Warning Test/clang_warning_wrapper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | IFS= 4 | 5 | clang $@ 6 | STATUS=$? 7 | 8 | # When clang is built for Release: 9 | #CUSTOM_CLANG_ROOT=${CUSTOM_CLANG_ROOT:-$(dirname $0)/../../../../../Release} 10 | 11 | # For easy trying: 12 | CUSTOM_CLANG_ROOT=${CUSTOM_CLANG_ROOT:-$HOME/Downloads/kvc-clang-rel34-2014-05-17} 13 | 14 | if test "$STATUS" -eq 0 && test -d $CUSTOM_CLANG_ROOT && ! ( echo $@ | grep -wF -- -filelist ); then 15 | # Xcode uses --serialize-diagnostics, which writes diagnostics to a file, overwriting the previous contents. 16 | # Emit our additional diagnostics after the original has run. 17 | DISABLE_BAD_WARNINGS_IN_CUSTOM_CLANG=-Wno-unused-property-ivar 18 | $CUSTOM_CLANG_ROOT/bin/clang -Xclang -load -Xclang $CUSTOM_CLANG_ROOT/lib/libKeyPathValidator.dylib -Xclang -plugin -Xclang validate-key-paths -fsyntax-only -Qunused-arguments $@ $DISABLE_BAD_WARNINGS_IN_CUSTOM_CLANG 19 | STATUS=$? 20 | fi 21 | 22 | exit $STATUS 23 | -------------------------------------------------------------------------------- /KVC Warning Test/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // KVC Warning Test 4 | // 5 | // Created by Jonathon Mah on 2014-05-15. 6 | // Copyright (c) 2014 Jonathon Mah. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | 12 | int main(int argc, const char *argv[]) 13 | { 14 | return 0; 15 | } 16 | -------------------------------------------------------------------------------- /KeyPathValidationConsumer.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPathValidationConsumer.cpp 3 | // Created by Jonathon Mah on 2014-01-24. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #include "KeyPathValidationConsumer.h" 10 | 11 | using namespace clang; 12 | 13 | 14 | void KeyPathValidationConsumer::cacheNSTypes() { 15 | TranslationUnitDecl *TUD = Context.getTranslationUnitDecl(); 16 | 17 | { DeclContext::lookup_result R = TUD->lookup(&Context.Idents.get("NSNumber")); 18 | if (R.size() > 0) { 19 | ObjCInterfaceDecl *NSNumberDecl = dyn_cast(R[0]); 20 | NSNumberPtrType = Context.getObjCObjectPointerType(Context.getObjCInterfaceType(NSNumberDecl)); 21 | } } 22 | 23 | { DeclContext::lookup_result R = TUD->lookup(&Context.Idents.get("NSDictionary")); 24 | if (R.size() > 0) 25 | NSDictionaryInterface = dyn_cast(R[0]); } 26 | 27 | { DeclContext::lookup_result R = TUD->lookup(&Context.Idents.get("NSArray")); 28 | if (R.size() > 0) 29 | NSArrayInterface = dyn_cast(R[0]); } 30 | 31 | { DeclContext::lookup_result R = TUD->lookup(&Context.Idents.get("NSSet")); 32 | if (R.size() > 0) 33 | NSSetInterface = dyn_cast(R[0]); } 34 | 35 | { DeclContext::lookup_result R = TUD->lookup(&Context.Idents.get("NSOrderedSet")); 36 | if (R.size() > 0) 37 | NSOrderedSetInterface = dyn_cast(R[0]); } 38 | } 39 | 40 | 41 | bool KeyPathValidationConsumer::CheckKeyType(QualType &ObjTypeInOut, StringRef &Key, bool AllowPrivate) { 42 | if (isKVCContainer(ObjTypeInOut)) { 43 | ObjTypeInOut = Context.getObjCIdType(); 44 | return true; 45 | } 46 | 47 | // Special case keys 48 | if (Key.equals("self")) 49 | return true; // leave ObjTypeInOut unchanged 50 | 51 | 52 | std::vector ContainerDecls; 53 | if (const ObjCObjectPointerType *ObjType = ObjTypeInOut->getAs()) { 54 | if (const ObjCInterfaceDecl *Interface = ObjType->getInterfaceDecl()) { 55 | ContainerDecls.push_back(Interface); 56 | } 57 | std::copy(ObjType->qual_begin(), ObjType->qual_end(), std::back_inserter(ContainerDecls)); 58 | } 59 | 60 | IdentifierInfo *ID = &Context.Idents.get(Key); 61 | Selector Sel = Context.Selectors.getNullarySelector(ID); 62 | StringRef IsKey = ("is" + Key.substr(0, 1).upper() + Key.substr(1)).str(); 63 | Selector IsSel = Context.Selectors.getNullarySelector(&Context.Idents.get(IsKey)); 64 | 65 | QualType Type; 66 | for (std::vector::iterator Decl = ContainerDecls.begin(), DeclEnd = ContainerDecls.end(); 67 | Decl != DeclEnd; ++Decl) { 68 | // Call the "same" (textually) method on both protocols and interfaces, not declared by the superclass 69 | if (const ObjCProtocolDecl *ProtoDecl = dyn_cast(*Decl)) { 70 | if (const ObjCMethodDecl *Method = ProtoDecl->lookupMethod(Sel, true)) { 71 | Type = Method->getResultType(); 72 | break; 73 | } 74 | if (const ObjCMethodDecl *Method = ProtoDecl->lookupMethod(IsSel, true)) { 75 | Type = Method->getResultType(); 76 | break; 77 | } 78 | if (const ObjCPropertyDecl *Property = ProtoDecl->FindPropertyDeclaration(ID)) 79 | if (isKVCCollectionType(Property->getType())) { 80 | Type = Property->getType(); 81 | break; 82 | } 83 | } 84 | if (const ObjCInterfaceDecl *InterfaceDecl = dyn_cast(*Decl)) { 85 | if (const ObjCMethodDecl *Method = InterfaceDecl->lookupMethod(Sel, true)) { 86 | Type = Method->getResultType(); 87 | break; 88 | } 89 | if (const ObjCMethodDecl *Method = InterfaceDecl->lookupMethod(IsSel, true)) { 90 | Type = Method->getResultType(); 91 | break; 92 | } 93 | if (const ObjCPropertyDecl *Property = InterfaceDecl->FindPropertyDeclaration(ID)) 94 | if (isKVCCollectionType(Property->getType())) { 95 | Type = Property->getType(); 96 | break; 97 | } 98 | 99 | if (AllowPrivate) { 100 | if (const ObjCMethodDecl *Method = InterfaceDecl->lookupPrivateMethod(Sel, true)) { 101 | Type = Method->getResultType(); 102 | break; 103 | } 104 | if (const ObjCMethodDecl *Method = InterfaceDecl->lookupPrivateMethod(IsSel, true)) { 105 | Type = Method->getResultType(); 106 | break; 107 | } 108 | } 109 | } 110 | } 111 | if (Type.isNull()) 112 | return false; 113 | 114 | if (Type->isObjCObjectPointerType()) 115 | ObjTypeInOut = Type; 116 | else if (NSAPIObj->getNSNumberFactoryMethodKind(Type).hasValue()) 117 | ObjTypeInOut = NSNumberPtrType; 118 | 119 | // TODO: Primitives to NSValue 120 | return true; 121 | } 122 | 123 | 124 | bool KeyPathValidationConsumer::isKVCContainer(QualType Type) { 125 | if (Type->isObjCIdType()) 126 | return true; 127 | 128 | const ObjCInterfaceDecl *ObjInterface = NULL; 129 | if (const ObjCObjectPointerType *ObjPointerType = Type->getAsObjCInterfacePointerType()) 130 | ObjInterface = ObjPointerType->getInterfaceDecl(); 131 | 132 | // Foundation built-ins 133 | if (NSDictionaryInterface->isSuperClassOf(ObjInterface) || 134 | NSArrayInterface->isSuperClassOf(ObjInterface) || 135 | NSSetInterface ->isSuperClassOf(ObjInterface)|| 136 | NSOrderedSetInterface->isSuperClassOf(ObjInterface)) 137 | return true; 138 | 139 | // Check for attribute 140 | while (ObjInterface) { 141 | for (Decl::attr_iterator Attr = ObjInterface->attr_begin(), AttrEnd = ObjInterface->attr_end(); 142 | Attr != AttrEnd; ++Attr) { 143 | if (AnnotateAttr *AA = dyn_cast(*Attr)) 144 | if (AA->getAnnotation().equals("objc_kvc_container")) 145 | return true; 146 | } 147 | 148 | ObjInterface = ObjInterface->getSuperClass(); 149 | } 150 | return false; 151 | } 152 | 153 | 154 | bool KeyPathValidationConsumer::isKVCCollectionType(QualType Type) { 155 | const ObjCInterfaceDecl *ObjInterface = NULL; 156 | if (const ObjCObjectPointerType *ObjPointerType = Type->getAsObjCInterfacePointerType()) 157 | ObjInterface = ObjPointerType->getInterfaceDecl(); 158 | 159 | return (NSArrayInterface->isSuperClassOf(ObjInterface) || 160 | NSSetInterface->isSuperClassOf(ObjInterface) || 161 | NSOrderedSetInterface->isSuperClassOf(ObjInterface)); 162 | } 163 | 164 | 165 | void KeyPathValidationConsumer::emitDiagnosticsForTypeAndMaybeReceiverAndKeyPath(QualType Type, const Expr *ModelExpr, const Expr *KeyPathExpr, bool AllowPrivate) { 166 | const ObjCStringLiteral *KeyPathLiteral = dyn_cast(KeyPathExpr->IgnoreImplicit()); 167 | 168 | if (!KeyPathLiteral) 169 | return; 170 | 171 | SourceRange ModelRange; 172 | if (ModelExpr) 173 | ModelRange = ModelExpr->getSourceRange(); 174 | 175 | QualType ObjType = Type; 176 | size_t Offset = 2; // @" 177 | typedef std::pair StringPair; 178 | for (StringPair KeyAndPath = KeyPathLiteral->getString()->getString().split('.'); KeyAndPath.first.size() > 0; KeyAndPath = KeyAndPath.second.split('.')) { 179 | StringRef Key = KeyAndPath.first; 180 | bool Valid = emitDiagnosticsForTypeAndMaybeReceiverAndKey(ObjType, ModelRange, Key, KeyPathExpr->getSourceRange(), Offset, AllowPrivate); 181 | if (!Valid) 182 | break; 183 | Offset += Key.size() + 1; 184 | } 185 | } 186 | 187 | bool KeyPathValidationConsumer::emitDiagnosticsForTypeAndMaybeReceiverAndKey(QualType &ObjTypeInOut, SourceRange ModelRange, StringRef Key, SourceRange KeyRange, size_t Offset, bool AllowPrivate) { 188 | bool Valid = CheckKeyType(ObjTypeInOut, Key, AllowPrivate); 189 | if (Valid) 190 | return Valid; 191 | 192 | SourceLocation KeyStart = KeyRange.getBegin().getLocWithOffset(Offset); 193 | KeyRange.setBegin(KeyStart); 194 | KeyRange.setEnd(KeyStart.getLocWithOffset(1)); 195 | 196 | DiagnosticBuilder DB = Compiler.getDiagnostics().Report(KeyStart, KeyDiagID); 197 | DB << Key << ObjTypeInOut->getPointeeType().getAsString() << KeyRange; 198 | if (ModelRange.isValid()) 199 | DB << ModelRange; 200 | 201 | return Valid; 202 | } 203 | -------------------------------------------------------------------------------- /KeyPathValidationConsumer.h: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPathValidationConsumer.h 3 | // Created by Jonathon Mah on 2014-01-24. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #ifndef CLANG_KPV_KEY_PATH_VALIDATION_CONSUMER_H 10 | #define CLANG_KPV_KEY_PATH_VALIDATION_CONSUMER_H 11 | 12 | #include "clang/Frontend/FrontendPluginRegistry.h" 13 | #include "clang/AST/AST.h" 14 | #include "clang/AST/ASTConsumer.h" 15 | #include "clang/AST/NSAPI.h" 16 | #include "clang/AST/Attr.h" 17 | #include "clang/Frontend/CompilerInstance.h" 18 | 19 | using namespace clang; 20 | 21 | class KeyPathValidationConsumer : public ASTConsumer { 22 | public: 23 | KeyPathValidationConsumer(const CompilerInstance &Compiler) 24 | : ASTConsumer() 25 | , Compiler(Compiler) 26 | , Context(Compiler.getASTContext()) 27 | { 28 | NSAPIObj.reset(new NSAPI(Context)); 29 | DiagnosticsEngine::Level L = DiagnosticsEngine::Warning; 30 | if (Compiler.getDiagnostics().getWarningsAsErrors()) 31 | L = DiagnosticsEngine::Error; 32 | KeyDiagID = Compiler.getDiagnostics().getCustomDiagID(L, "key '%0' not found on type %1"); 33 | } 34 | 35 | virtual void HandleTranslationUnit(ASTContext &Context); 36 | 37 | bool CheckKeyType(QualType &ObjTypeInOut, StringRef &Key, bool AllowPrivate); 38 | 39 | void emitDiagnosticsForReceiverAndKeyPath(const Expr *ModelExpr, const Expr *KeyPathExpr, bool AllowPrivate=false) { 40 | emitDiagnosticsForTypeAndMaybeReceiverAndKeyPath(ModelExpr->IgnoreImplicit()->getType(), ModelExpr, KeyPathExpr, AllowPrivate); 41 | } 42 | 43 | void emitDiagnosticsForTypeAndKey(QualType Type, const Expr *KeyExpr, bool AllowPrivate=false) { 44 | const ObjCStringLiteral *KeyPathLiteral = dyn_cast(KeyExpr); 45 | if (KeyPathLiteral) 46 | emitDiagnosticsForTypeAndMaybeReceiverAndKey(Type, SourceRange(), KeyPathLiteral->getString()->getString(), KeyExpr->getSourceRange(), 0, AllowPrivate); 47 | } 48 | 49 | void emitDiagnosticsForTypeAndKeyPath(QualType Type, const Expr *KeyPathExpr, bool AllowPrivate=false) { 50 | emitDiagnosticsForTypeAndMaybeReceiverAndKeyPath(Type, NULL, KeyPathExpr, AllowPrivate); 51 | } 52 | 53 | unsigned KeyDiagID; 54 | 55 | private: 56 | const CompilerInstance &Compiler; 57 | ASTContext &Context; 58 | OwningPtr NSAPIObj; 59 | 60 | QualType NSNumberPtrType; 61 | 62 | // Hard-coded set of KVC containers (can't add attributes in a category) 63 | ObjCInterfaceDecl *NSDictionaryInterface, *NSArrayInterface, *NSSetInterface, *NSOrderedSetInterface; 64 | 65 | void cacheNSTypes(); 66 | bool isKVCContainer(QualType type); 67 | bool isKVCCollectionType(QualType type); 68 | 69 | void emitDiagnosticsForTypeAndMaybeReceiverAndKeyPath(QualType Type, const Expr *ModelExpr, const Expr *KeyPathExpr, bool AllowPrivate); 70 | bool emitDiagnosticsForTypeAndMaybeReceiverAndKey(QualType &ObjTypeInOut, SourceRange ModelRange, StringRef Key, SourceRange KeyRange, size_t Offset, bool AllowPrivate); 71 | }; 72 | 73 | #endif 74 | -------------------------------------------------------------------------------- /KeyPathValidator.exports: -------------------------------------------------------------------------------- 1 | _ZN4llvm8Registry* 2 | -------------------------------------------------------------------------------- /KeyPathsAffectingVisitor.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPathsAffectingVisitor.cpp 3 | // Created by Jonathon Mah on 2014-05-11. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #include "KeyPathsAffectingVisitor.h" 10 | 11 | using namespace clang; 12 | 13 | 14 | class ReturnSetVisitor : public RecursiveASTVisitor { 15 | KeyPathValidationConsumer *Consumer; 16 | QualType Type; 17 | llvm::SmallSet *SetConstructorSelectors; 18 | 19 | public: 20 | ReturnSetVisitor(KeyPathValidationConsumer *Consumer, QualType Type, llvm::SmallSet *Selectors) 21 | : Consumer(Consumer) 22 | , Type(Type) 23 | , SetConstructorSelectors(Selectors) 24 | {} 25 | 26 | bool VisitStmt(const Stmt *Node); 27 | }; 28 | 29 | 30 | bool KeyPathsAffectingVisitor::VisitObjCMethodDecl(ObjCMethodDecl *D) { 31 | if (!D->isClassMethod()) 32 | return true; 33 | 34 | std::string Name = D->getNameAsString(); 35 | if (Name.length() <= Prefix.length() || !std::equal(Prefix.begin(), Prefix.end(), Name.begin())) 36 | return true; 37 | 38 | ASTContext &Context = Compiler.getASTContext(); 39 | QualType Type = Context.getObjCObjectPointerType(Context.getObjCInterfaceType(D->getClassInterface())); 40 | ReturnSetVisitor(Consumer, Type, &SetConstructorSelectors).TraverseDecl(D); 41 | 42 | return true; 43 | } 44 | 45 | 46 | bool ReturnSetVisitor::VisitStmt(const Stmt *Node) { 47 | const ReturnStmt *Return = dyn_cast(Node); 48 | if (!Return) 49 | return true; 50 | 51 | const ObjCMessageExpr *E = dyn_cast(Return->getRetValue()->IgnoreImplicit()); 52 | if (!E) 53 | return true; 54 | 55 | QualType ClassReceiver = E->getClassReceiver(); 56 | if (ClassReceiver.isNull() || ClassReceiver.getAsString() != "NSSet") 57 | return true; 58 | 59 | if (!SetConstructorSelectors->count(E->getSelector())) 60 | return true; 61 | 62 | for (unsigned I = 0, N = E->getNumArgs(); I < N; ++I) 63 | { 64 | const Expr *Arg = E->getArg(I)->IgnoreImplicit(); 65 | const ObjCStringLiteral *KeyPathLiteral = dyn_cast(Arg); 66 | if (!KeyPathLiteral) 67 | continue; 68 | 69 | Consumer->emitDiagnosticsForTypeAndKeyPath(Type, Arg, true); 70 | 71 | const StringRef KeyPathString = KeyPathLiteral->getString()->getString(); 72 | llvm::outs() << KeyPathString << ", "; 73 | } 74 | 75 | return true; 76 | } 77 | -------------------------------------------------------------------------------- /KeyPathsAffectingVisitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // KeyPathsAffectingVisitor.h 3 | // Created by Jonathon Mah on 2014-05-11. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #ifndef CLANG_KPV_KEY_PATHS_AFFECTING_VISITOR_H 10 | #define CLANG_KPV_KEY_PATHS_AFFECTING_VISITOR_H 11 | 12 | #include "KeyPathValidationConsumer.h" 13 | #include "clang/AST/RecursiveASTVisitor.h" 14 | #include "clang/Frontend/CompilerInstance.h" 15 | 16 | using namespace clang; 17 | 18 | 19 | class KeyPathsAffectingVisitor : public RecursiveASTVisitor { 20 | KeyPathValidationConsumer *Consumer; 21 | const CompilerInstance &Compiler; 22 | std::string Prefix; 23 | llvm::SmallSet SetConstructorSelectors; 24 | //RecursiveASTVisitor Visitor; 25 | 26 | public: 27 | KeyPathsAffectingVisitor(KeyPathValidationConsumer *Consumer, const CompilerInstance &Compiler) 28 | : Consumer(Consumer) 29 | , Compiler(Compiler) 30 | , Prefix("keyPathsForValuesAffecting") 31 | { 32 | ASTContext &Ctx = Compiler.getASTContext(); 33 | SetConstructorSelectors.insert(Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("setWithObject"))); 34 | SetConstructorSelectors.insert(Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("setWithObjects"))); 35 | } 36 | 37 | bool shouldVisitTemplateInstantiations() const { return false; } 38 | bool shouldWalkTypesOfTypeLocs() const { return false; } 39 | 40 | bool VisitObjCMethodDecl(ObjCMethodDecl *D); 41 | }; 42 | 43 | #endif 44 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ============================================================================== 2 | LLVM Release License 3 | ============================================================================== 4 | University of Illinois/NCSA 5 | Open Source License 6 | 7 | Copyright (c) 2007-2013 University of Illinois at Urbana-Champaign. 8 | All rights reserved. 9 | 10 | Developed by: 11 | 12 | LLVM Team 13 | 14 | University of Illinois at Urbana-Champaign 15 | 16 | http://llvm.org 17 | 18 | Permission is hereby granted, free of charge, to any person obtaining a copy of 19 | this software and associated documentation files (the "Software"), to deal with 20 | the Software without restriction, including without limitation the rights to 21 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 22 | of the Software, and to permit persons to whom the Software is furnished to do 23 | so, subject to the following conditions: 24 | 25 | * Redistributions of source code must retain the above copyright notice, 26 | this list of conditions and the following disclaimers. 27 | 28 | * Redistributions in binary form must reproduce the above copyright notice, 29 | this list of conditions and the following disclaimers in the 30 | documentation and/or other materials provided with the distribution. 31 | 32 | * Neither the names of the LLVM Team, University of Illinois at 33 | Urbana-Champaign, nor the names of its contributors may be used to 34 | endorse or promote products derived from this Software without specific 35 | prior written permission. 36 | 37 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 38 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 39 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 40 | CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 41 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 42 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE 43 | SOFTWARE. 44 | 45 | ============================================================================== 46 | The LLVM software contains code written by third parties. Such software will 47 | have its own individual LICENSE.TXT file in the directory in which it appears. 48 | This file will describe the copyrights, license, and restrictions which apply 49 | to that code. 50 | 51 | The disclaimer of warranty in the University of Illinois Open Source License 52 | applies to all code in the LLVM Distribution, and nothing in any of the 53 | other licenses gives permission to use the names of the LLVM Team or the 54 | University of Illinois to endorse or promote products derived from this 55 | Software. 56 | 57 | The following pieces of software have additional or alternate copyrights, 58 | licenses, and/or restrictions: 59 | 60 | Program Directory 61 | ------- --------- 62 | 63 | 64 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # This file is distributed under the University of Illinois Open Source 2 | # License. See LICENSE.TXT for details. 3 | 4 | CLANG_LEVEL := ../.. 5 | LIBRARYNAME = KeyPathValidator 6 | 7 | # If we don't need RTTI or EH, there's no reason to export anything 8 | # from the plugin. 9 | ifneq ($(REQUIRES_RTTI), 1) 10 | ifneq ($(REQUIRES_EH), 1) 11 | EXPORTED_SYMBOL_FILE = $(PROJ_SRC_DIR)/KeyPathValidator.exports 12 | endif 13 | endif 14 | 15 | LINK_LIBS_IN_SHARED = 0 16 | SHARED_LIBRARY = 1 17 | 18 | include $(CLANG_LEVEL)/Makefile 19 | 20 | ifeq ($(OS),Darwin) 21 | LDFLAGS=-Wl,-undefined,dynamic_lookup 22 | endif 23 | 24 | 25 | run: all 26 | $(LEVEL)/Release/bin/clang -Xclang -load -Xclang $(LEVEL)/Release/lib/libKeyPathValidator.dylib -Xclang -plugin -Xclang validate-key-paths -fsyntax-only -fobjc-arc test/basic.m test/binder.m 27 | 28 | .PHONY: run 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clang Key Path Validator 2 | 3 | This is a clang plug-in that performs checking on literal key paths passed to `valueForKeyPath:` and others. 4 | 5 | More info in [dev etc: Safe and Sane Key Paths](http://devetc.org/code/2014/05/17/safe-and-sane-key-paths.html) 6 | 7 | To download and try it out, go to the [releases page](https://github.com/jmah/Clang-KeyPathValidator/releases). 8 | 9 | Key path warnings in Xcode 10 | 11 | ## Development 12 | 13 | The current version of the plug-in works with release_34 of LLVM and clang. 14 | Clone the repository to `llvm/tools/clang/examples/Clang-KeyPathValidator` and run `make` to compile the plugin, and `make run` to do a diagnostic pass over the files in the `tests` directory. 15 | 16 | ## TODO 17 | 18 | There are a bunch of tasks tracked in GitHub Issues. 19 | 20 | ## License 21 | 22 | The KeyPathValidator plug-in is released under the same license as LLVM, the [University of Illinois/NCSA Open Source License](http://llvm.org/releases/3.4/LICENSE.TXT). 23 | 24 | Copyright (c) 2014 Jonathon Mah. 25 | All rights reserved. 26 | 27 | Permission is hereby granted, free of charge, to any person obtaining a copy of 28 | this software and associated documentation files (the "Software"), to deal with 29 | the Software without restriction, including without limitation the rights to 30 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 31 | of the Software, and to permit persons to whom the Software is furnished to do 32 | so, subject to the following conditions: 33 | 34 | * Redistributions of source code must retain the above copyright notice, 35 | this list of conditions and the following disclaimers. 36 | 37 | * Redistributions in binary form must reproduce the above copyright notice, 38 | this list of conditions and the following disclaimers in the 39 | documentation and/or other materials provided with the distribution. 40 | 41 | * Neither the names of the LLVM Team, University of Illinois at 42 | Urbana-Champaign, nor the names of its contributors may be used to 43 | endorse or promote products derived from this Software without specific 44 | prior written permission. 45 | 46 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 47 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 48 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 49 | CONTRIBUTORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 50 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 51 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS WITH THE 52 | SOFTWARE. 53 | -------------------------------------------------------------------------------- /ValidateKeyPathsAction.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ValidateKeyPathsAction.cpp 3 | // Created by Jonathon Mah on 2014-01-24. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #include "clang/Frontend/FrontendPluginRegistry.h" 10 | #include "KeyPathValidationConsumer.h" 11 | #include "ValueForKeyVisitor.h" 12 | #include "KeyPathsAffectingVisitor.h" 13 | #include "FitbitFBBinderVisitor.h" 14 | 15 | using namespace clang; 16 | 17 | 18 | void KeyPathValidationConsumer::HandleTranslationUnit(ASTContext &Context) { 19 | cacheNSTypes(); 20 | 21 | ValueForKeyVisitor(this, Compiler).TraverseDecl(Context.getTranslationUnitDecl()); 22 | KeyPathsAffectingVisitor(this, Compiler).TraverseDecl(Context.getTranslationUnitDecl()); 23 | FBBinderVisitor(this, Compiler).TraverseDecl(Context.getTranslationUnitDecl()); 24 | } 25 | 26 | 27 | namespace { 28 | 29 | class NullConsumer : public ASTConsumer { 30 | public: 31 | NullConsumer() 32 | : ASTConsumer() 33 | { } 34 | }; 35 | 36 | 37 | class ValidateKeyPathsAction : public PluginASTAction { 38 | protected: 39 | ASTConsumer *CreateASTConsumer(CompilerInstance &compiler, llvm::StringRef) { 40 | LangOptions const opts = compiler.getLangOpts(); 41 | if (opts.ObjC1 || opts.ObjC2) 42 | return new KeyPathValidationConsumer(compiler); 43 | else 44 | return new NullConsumer(); 45 | } 46 | 47 | bool ParseArgs(const CompilerInstance &compiler, 48 | const std::vector& args) { 49 | return true; 50 | } 51 | }; 52 | 53 | } 54 | 55 | 56 | static FrontendPluginRegistry::Add 57 | X("validate-key-paths", "warn if static key paths seem invalid"); 58 | -------------------------------------------------------------------------------- /ValueForKeyVisitor.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // ValueForKeyVisitor.cpp 3 | // Created by Jonathon Mah on 2014-01-24. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #include "ValueForKeyVisitor.h" 10 | 11 | using namespace clang; 12 | 13 | bool ValueForKeyVisitor::VisitObjCMessageExpr(ObjCMessageExpr *E) { 14 | if (E->getNumArgs() != 1 || !E->isInstanceMessage()) 15 | return true; 16 | 17 | Selector Sel = E->getSelector(); 18 | if (Sel != VFKSelector && Sel != VFKPathSelector) 19 | return true; 20 | 21 | if (Sel == VFKPathSelector) 22 | Consumer->emitDiagnosticsForTypeAndKeyPath(E->getReceiverType(), E->getArg(0)); 23 | else 24 | Consumer->emitDiagnosticsForTypeAndKey(E->getReceiverType(), E->getArg(0)); 25 | 26 | return true; 27 | } 28 | -------------------------------------------------------------------------------- /ValueForKeyVisitor.h: -------------------------------------------------------------------------------- 1 | // 2 | // ValueForKeyVisitor.h 3 | // Created by Jonathon Mah on 2014-01-24. 4 | // 5 | // This file is distributed under the University of Illinois Open Source 6 | // License. See LICENSE.TXT for details. 7 | // 8 | 9 | #ifndef CLANG_KPV_VALUE_FOR_KEY_VISITOR_H 10 | #define CLANG_KPV_VALUE_FOR_KEY_VISITOR_H 11 | 12 | #include "KeyPathValidationConsumer.h" 13 | #include "clang/AST/RecursiveASTVisitor.h" 14 | #include "clang/Frontend/CompilerInstance.h" 15 | 16 | using namespace clang; 17 | 18 | 19 | class ValueForKeyVisitor : public RecursiveASTVisitor { 20 | KeyPathValidationConsumer *Consumer; 21 | Selector VFKSelector, VFKPathSelector; 22 | 23 | public: 24 | ValueForKeyVisitor(KeyPathValidationConsumer *Consumer, const CompilerInstance &Compiler) 25 | : Consumer(Consumer) 26 | { 27 | ASTContext &Ctx = Compiler.getASTContext(); 28 | VFKSelector = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("valueForKey")); 29 | VFKPathSelector = Ctx.Selectors.getUnarySelector(&Ctx.Idents.get("valueForKeyPath")); 30 | } 31 | 32 | bool shouldVisitTemplateInstantiations() const { return false; } 33 | bool shouldWalkTypesOfTypeLocs() const { return false; } 34 | 35 | bool VisitObjCMessageExpr(ObjCMessageExpr *E); 36 | }; 37 | 38 | #endif 39 | -------------------------------------------------------------------------------- /test/basic.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | __attribute__((annotate("objc_kvc_container"))) 4 | @interface MyDictLike : NSObject 5 | @end 6 | 7 | 8 | @protocol BarProtocol; 9 | 10 | @interface Variation : NSObject 11 | @property (getter = isFoo) BOOL foo; 12 | @property id barLike; 13 | @end 14 | 15 | @interface SubVariation : Variation 16 | @property NSString *sub; 17 | @property (readonly, getter = collectionKVCProxy) NSArray *collection; 18 | @property (readonly, getter = funkyGetter) NSObject *funky; 19 | @end 20 | 21 | @protocol BarProtocol 22 | @property NSString *bar; 23 | @end 24 | 25 | @protocol BazProtocol 26 | @property NSString *baz; 27 | @end 28 | 29 | 30 | static void testFn(void) 31 | { 32 | NSTimer *t = nil; 33 | [t valueForKey:@"fireDate"]; 34 | [t valueForKeyPath:@"fireDate.timeIntervalSinceNow"]; 35 | [t valueForKeyPath:@"fireDate.foo.bar"]; // warn 36 | [t valueForKey:@"fireDate.timeIntervalSinceNow"]; // warn 37 | [t valueForKey:@"doesNotExist"]; // warn 38 | 39 | id idObj = t; 40 | [idObj valueForKeyPath:@"fireDate.foo.bar"]; 41 | 42 | NSString *stringVar; 43 | [t valueForKey:stringVar]; 44 | 45 | NSDictionary *d; 46 | [d valueForKey:@"anythingIsOk"]; 47 | [(NSMutableDictionary *)d valueForKey:@"anythingIsOk"]; 48 | [(MyDictLike *)d valueForKey:@"anythingIsOk"]; 49 | 50 | NSArray *a; 51 | [a valueForKey:@"anythingIsOk"]; 52 | [a valueForKey:@"@count"]; 53 | 54 | Variation *v; 55 | [v valueForKey:@"foo"]; 56 | [v valueForKeyPath:@"barLike.bar"]; 57 | [v valueForKeyPath:@"barLike.doesNotExist"]; // warn 58 | 59 | SubVariation *sv; 60 | [sv valueForKey:@"foo"]; 61 | [sv valueForKeyPath:@"barLike.bar"]; 62 | [sv valueForKeyPath:@"sub"]; 63 | [sv valueForKeyPath:@"collection"]; 64 | [sv valueForKeyPath:@"collectionKVCProxy"]; 65 | [sv valueForKeyPath:@"funky"]; // warn 66 | [sv valueForKeyPath:@"funkyGetter"]; // warn 67 | 68 | NSObject *nsBar; 69 | [nsBar valueForKey:@"bar"]; 70 | [nsBar valueForKey:@"doeNotExist"]; // warn 71 | 72 | NSObject *barBaz; 73 | [barBaz valueForKey:@"bar"]; 74 | [barBaz valueForKey:@"baz"]; 75 | } 76 | 77 | 78 | @implementation Variation 79 | 80 | + (NSSet *)keyPathsForValuesAffectingBaz 81 | { 82 | return [NSSet setWithObjects: 83 | @"foo", 84 | @"barLike.bar", 85 | @"barLike.doesNotExist", // warn 86 | @"doesNotExist", // warn 87 | nil]; 88 | } 89 | - (id)baz 90 | { return @"dummy"; } 91 | 92 | + (NSSet *)keyPathsForValuesAffectingBob 93 | { 94 | return [NSSet setWithObject:@"self"]; 95 | } 96 | - (id)bob 97 | { return @"dummy"; } 98 | 99 | + (NSSet *)keyPathsForValuesAffectingAlice 100 | { 101 | return [NSSet setWithObject:@"secret"]; 102 | } 103 | - (id)alice 104 | { return @"dummy"; } 105 | 106 | - (id)secret 107 | { return nil; } 108 | 109 | + (NSSet *)keyPathsForValuesAffectingEve 110 | { 111 | return [NSSet setWithObject:@"doesNotExist"]; // warn 112 | } 113 | - (id)eve 114 | { return @"dummy"; } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /test/binder.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface Bindable : NSObject 4 | 5 | - (void)bindToModel:(id)m keyPath:(NSString *)kp change:(void (^)(void))blk; 6 | - (void)bindToModels:(NSArray *)ms keyPaths:(NSArray *)kps change:(void (^)(void))blk; 7 | 8 | @end 9 | 10 | static void testFn(void) 11 | { 12 | Bindable *obj; 13 | NSTimer *t = nil; 14 | [obj bindToModel:t keyPath:@"self.fireDate" change:^{}]; 15 | [obj bindToModel:t keyPath:@"fireDate.timeIntervalSinceNow" change:^{}]; 16 | [obj bindToModel:[NSRunLoop mainRunLoop] keyPath:@"currentMode.length.foo" change:^{}]; 17 | [obj bindToModel:[NSRunLoop mainRunLoop] keyPath:@"currentMode.length.integerValue" change:^{}]; 18 | [obj bindToModel:t keyPath:@"doesNotExst" change:^{}]; 19 | 20 | id idObj; 21 | [obj bindToModel:idObj keyPath:@"foo" change:^{}]; 22 | 23 | [obj bindToModels:@[t, idObj] keyPaths:@[@[@"fireDate", @"doesNotExist"], @[@"doesNotExist"]] change:^{}]; 24 | } 25 | --------------------------------------------------------------------------------