利用 oclint 做静态代码分析

场景

有些情况下代码有问题,但编译器不会报警告,也不报错,运行期也不崩溃,但程序执行就会有bug。

举个例子:两个不同的category下有一个同名的方法,xcode9.2不会报警告,运行期不确定会调用哪个,导致bug出现。因为category会把自己的方法加入到主类的方法链表中,出现同名的话不确定是加入失败还是覆盖同名的。

代码如下,可以试试运行结果,编译器是不警告不报错的。

// 主类
@interface OCCategoryMethodTest : NSObject
@end
@implementation OCCategoryMethodTest
@end

// CategoryA
@interface OCCategoryMethodTest (CategoryA)
- (void)funcA;
@end
@implementation OCCategoryMethodTest (CategoryA)
- (void)funcA {
    NSLog(@"Category A");
}
@end

// CategoryB
@interface OCCategoryMethodTest (CategoryB)
- (void)funcA;
@end
@implementation OCCategoryMethodTest (CategoryB)
- (void)funcA {
    NSLog(@"Category B");
}
@end


// 在另一个类中测试调用代码
OCCategoryMethodTest *test = [[OCCategoryMethodTest alloc] init];
[test funcA];    // 此处代码会打印什么结果呢?我这是 “Category B”

假设 CategoryA 的 funcA,CategoryB 的 funcA 都是很重要的业务代码必须要执行到,不然就会出bug,CategoryA 和 CategoryB 是两个业务团队写的,彼此不知道对方怎么命名,此时就会出bug了,还非常难调试。

此时就有必要通过 oclint 静态代码检查来事先发现这种风险,提前解决。

oclint的使用

  • oclint 安装配置和调试,

上网搜索一下 “oclint 自定义规则" 就有,或者上oclint官网有安装配置方法,

安装完了之后,就可以编写自定义规则来做代码检查了,还是拿category举例。
新建一个规则后,会得到一个 cpp 文件,这里面就可以用 C++ 编写自己的规则
oclint 会调用 clang 把代码建立好抽象语法树,然后在遍历树的时候,每遇到一个节点就会产生一个回调,在 cpp 文件中就有各种回调方法,比如 property 回调,interface 回调,category 回调等等。

那么要检查category,需要在 interface 的回调中分析。为什么不在 category 中,因为 interface 的子节点有很多 category,但是到了一个具体的 category 就不能保证他的 interface 是统一的,假如 interface 不一样的,那方法完全可以重名。

  • category方法重名检测算法大致如下:

遇到一个 interface 回调,去遍历其下面的所有 category,的所有 method,把方法名的字符串作为 key,方法对象 ObjCMethodDecl 加入数组,数组作为 value,插入哈希表中。插入时,如果key存在,那么value数组中追加一个 ObjCMethodDecl,不存在则直接插入。

category 遍历完了之后,遍历哈希表,如果表中存在一个方法,他有2个以上的 ObjCMethodDecl,那么都弹出来,调用报错函数。

具体代码如下:

class MyCategoryMethodConflictRule : public AbstractASTVisitorRule {
// 省略部分无关代码
private:
    // 方法名, ObjCMethodDecl 数组的 map
    unordered_map > umap;
    unordered_map >::iterator map_it;
    
public:
    // InterfaceDecl 的回调方法
    bool VisitObjCInterfaceDecl(ObjCInterfaceDecl *node)
    {
        umap.clear();   // 初始化

        // 遍历主类 impl 的方法,因为有些函数没在 interface 中
        ObjCImplementationDecl *impl = node->getImplementation();
        ObjCContainerDecl::method_iterator met_it = impl->meth_begin();
        while (met_it != impl->meth_end()) {
            insert_map(met_it->getNameAsString(), met_it->getCanonicalDecl());
            met_it++;
        }
        
        // 遍历分类
        if (node->known_categories_empty() == false) {
            ObjCInterfaceDecl::known_categories_iterator cate_it = node->known_categories_begin();
            while (cate_it != node->known_categories_end()) {
                // 获取分类 impl, 遍历分类方法
                ObjCCategoryImplDecl *ca_impl = cate_it->getImplementation();
                ObjCContainerDecl::method_iterator came_it = ca_impl->meth_begin();
                while (came_it != ca_impl->meth_end()) {
                    insert_map(came_it->getNameAsString(), came_it->getCanonicalDecl());
                    came_it++;
                }
                cate_it++;
            }
        }
        
        // 出错处理和清空map
        if (umap.size() > 0) {
            for (map_it = umap.begin(); map_it != umap.end(); map_it++) {
                // 找出出现次数大于2的方法, 报错
                if (map_it->second.size() > 1) {
                    string msg = "Method \"" + map_it->first + "\" also be implemented by other category or primary class";
                    vector &vec = map_it->second;
                    for (unsigned int i=0; i vec;
            vec.emplace_back(methodDec);
            umap[name] = vec;
        } else {
            map_it->second.emplace_back(methodDec);
        }
    }

// 省略部分无关代码
}

写完后会生成动态库 dylib,oclint 会调用这个动态库去分析测试代码,然后就能得到很多警告了。

这里只拿一个例子做说明 - category 方法重名
此外还有很多有风险的代码规则可以检查。
比如 delegate 写成了 assign 的,分类中实现了 +initialize 方法的,block 中写了 self 的,等等。

静态代码分析能发现一部分代码有风险的地方,提前解决掉,避免线上出bug。当然运行期的问题静态分析是很难发现的。

你可能感兴趣的:(利用 oclint 做静态代码分析)