美文网首页程序员
利用 oclint 做静态代码分析

利用 oclint 做静态代码分析

作者: ck2016 | 来源:发表于2018-06-02 18:07 被阅读0次

    场景

    有些情况下代码有问题,但编译器不会报警告,也不报错,运行期也不崩溃,但程序执行就会有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<MTCategoryMethodConflictRule> {
    // 省略部分无关代码
    private:
        // 方法名, ObjCMethodDecl 数组的 map
        unordered_map<string, vector<ObjCMethodDecl *> > umap;
        unordered_map<string, vector<ObjCMethodDecl *> >::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<ObjCMethodDecl *> &vec = map_it->second;
                        for (unsigned int i=0; i<vec.size(); i++) {
                            addViolation(vec[i], this, msg);    // 出错处理
                        }
                    }
                }
                umap.clear();
            }
            return true;
        }
        
        // 向 map 中加入一个元素
        void insert_map(string name, ObjCMethodDecl *methodDec) {
            // 如果存在,数组追加一,不存在,则插入
            map_it = umap.find(name);
            if (map_it == umap.end()) {
                vector<ObjCMethodDecl *> 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 做静态代码分析

        本文链接:https://www.haomeiwen.com/subject/sjonbxtx.html