在使用 Objective-C 编写程序时,经常会因为 mistyping 使用内存管理语义修饰符 assign 去修饰对象类型 property,为后续 bad access 或其它运行时异常埋下祸根。
为了避免此类问题,需要通过一些手段检测出异常内存管理修饰符:
Objective-C 类在编译之后,会针对 property 产生 attribute string,这些字符串可以指示出该 property 的原子性、内存管理语义等特征。通过分析二进制所有类的属性特征,可以快速准确地找出所有的异常属性。
举例来说,为类 KTClass
作出如下定义:
@interface KTClass : NSObject
@property (assign) int iVar;
@property (assign) NSString *aStr;
@property (strong) NSString *sStrr;
@property (copy) NSString *cStr;
@property (weak) NSString *wStr;
@end
上述的五个属性的内存管理语义修饰符分别为 assign、assign、strong、copy、weak。编译器在编译后会为这五个属性生成对应的特征字符串,可以用过 Objective-C 运行时函数 property_getAttrubutes
获得,分别为:
Ti,V_iVar
T@"NSString",V_aStr
T@"NSString",&,V_sStr
T@"NSString",C,V_cStr
T@"NSString",W,V_wStr
通过测试及研究可以发现:
那么利用规则:「T」字符后跟着“@”字符且在最后的逗号前遇不到「&」、「C」、「W」的属性,判断为异常属性声明。
有了上述的规则,那么就可以应用到所有类的属性判定上。具体到遍历项目所有的类,需要用到 dyld 的 API,通过 _dyld_get_image_header
函数获得对应的二进制的文件头,然后再通过 getsectiondata
函数获得 __DATA 中的 __objc_classlist 段,这里返回的是编译期生成的所有类列表,遍历,再通过 class_copyPropertyList
获取到类的所有属性列表,遍历所有属性,应用上述所提到的规则。具体可以看下 KTPropertyCheck 的实现。
本案的检测方式在实践中成功定位项目中潜在的或偶现的内存隐患,一定程度上提高了项目质量。上面所提及的核心是利用编译代码后的二进制产物进行分析,除了可以在程序运行期间使用 Objective-C 运行时 API 获得该特征字符串,也可以静态地通过解析二进制文件获得。
— May 18, 2021