仓颉语言静态检查工具使用指南

功能简介

cjlint是一款静态检查工具,该工具是基于 Cangjie Programming Language CodingStyle 开发。通过它可以识别代码中不符合编程规范的问题,帮助开发者发现代码中的漏洞,写出满足 Clean Source 要求的仓颉代码。

使用说明

cjlint -f fileDir [option] fileDir...

注意:-f 后面指定的是*.cj 文件所在src目录

正例:

cjlint -f xxx/xxx/src

反例:

cjlint -f xxx/xxx/src/xxx.cj

注:上述限制来自编译器模块编译限制, 因为当前编译器编译选项正在重构,相关 API 不稳定;而模块编译较为稳定,因此工具当前仅支持模块编译方式。最终工具支持的编译选项会与编译器一致,支持其他编译方式。

命令行选项 - [option]

Options:
   -h              Show usage
                       eg: ./cjlint -h
   -f <value>      Detected file directory, it can be absolute path or relative path, if it is directory, default file name is cjReport
                       eg: ./cjlint -f fileDir -c . -m .
   -e <v1:v2:...>  Excluded files, directories or configurations, splitted by ':'. Regular expressions are supported
                       eg: ./cjlint -f fileDir -e fileDir/a/:fileDir/b/*.cj
   -o <value>      Output file path, it can be absolute path or relative path
                       eg: ./cjlint -f fileDir -o ./out
   -r [csv|json]   Report file format, it can be csv or json, default is json
                       eg: ./cjlint -f fileDir -r csv -o ./out
   -c <value>      Directory path where the config directory is located, it can be absolute path or relative path to the executable file
                       eg: ./cjlint -f fileDir -c .
   -m <value>      Directory path where the modules directory is located, it can be absolute path or relative path to the executable file
                       eg: ./cjlint -f fileDir -m .

注意事项

  1. 用户可以通过-r指定生成扫描报告的格式,目前支持json格式和csv格式。-r需要与-o选项配合使用,如果没有-o指定输出到文件,即使指定了-r也不会生成扫描报告。如果指定了-o没有指定-r,那么默认生成json格式的扫描报告。

    例:通过 -r 选项,生成 report.csv 的扫描报告:

    cjlint -f ./src -r csv -o ./report         // 生成report.csv文件
    cjlint -f ./src -r csv -o ./output/report  // 在output目录下生成report.csv文件
    
  2. 可执行文件cjlint同目录下,有configmodules作为默认的配置目录和依赖目录。若有需要,用户可以用命令行选项 -c-m来指定configmodules所在的目录。

    例:指定的 config 和 modules 文件路径为: ./xxx/xxx/config./xxx/xxx/modules

    则命令应为:

    cjlint -f ./src -c ./xxx/xxx -m ./xxx/xxx
    
  3. 可执行文件cjlint同目录下的config配置目录中,有cjlint_rule_list.jsonexclude_lists.json两个配置文件。其中,cjlint_rule_list.json为规则列表配置文件,用户可以通过增减其中的配置信息来决定进行哪些规则的检查。exclude_lists.json为告警屏蔽配置文件,用户可以通过添加告警信息来屏蔽某一条规则的某一条告警。

    例: 若用户只想检查如下 5 条规则,则cjlint_rule_list.json配置文件中只添加要检查的 5 条规则。

    {
      "RuleList": [
        "G.FMT.01",
        "G.ENUM.01",
        "G.EXP.03",
        "G.OTH.01",
        "G.OTH.02"
      ]
    }
    

    例: 若用户想要屏蔽某一条规则的某一条告警,可以在exclude_lists.json配置文件中添加屏蔽信息。

    注意:path不必填写绝对路径,但必须有xxx.cj格式,为模糊匹配。line为告警行号,为精确匹配。colum为告警列号,可选择性填写进行列号精确匹配。

    {
      "G.OTH.01" : [
        {"path":"xxx/example.cj", "line":"42"},
        {"path":"xxx/example.cj", "line":"42", "colum": "2"},
        {"path":"example.cj", "line":"42", "colum": "2"}
      ]
    }
    
  4. 支持源代码注释屏蔽告警

特殊注释 BNF

<content of cjlint-ignore comment> ::=  "cjlint-ignore"  [-start] <ignore-rule>{...} [description] | cjlint-ignore  <-end> [description]
<ignore-rule> ::="!"<rule-name>
<rule-name> ::= <letters>

单行屏蔽正确示例 1,屏蔽 G.CHK.04 告警

void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return; /*cjlint-ignore !G.CHK.04 */
    }
}

单行屏蔽正确示例 2,屏蔽 G.CHK.04 告警

void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return; // cjlint-ignore !G.CHK.04 description
    }
}

多行屏蔽正确示例 1,屏蔽 G.CHK.04 告警

/*cjlint-ignore -start !G.CHK.04 description */
void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return;
    }
}
/* cjlint-ignore -end description */

多行屏蔽正确示例 2,屏蔽 G.CHK.04 告警

// cjlint-ignore -start !G.CHK.04 description
void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return;
    }
}
// cjlint-ignore -end description

多行屏蔽正确示例 3,屏蔽 G.CHK.04 告警

/**
 *  cjlint-ignore -start !G.CHK.04 description
 */
void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return;
    }
}
// cjlint-ignore -end description

单行屏蔽错误示例 1,屏蔽 G.CHK.04 告警

void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return; /*cjlint-ignore !G.CHK.04!G.CHK.02 */
                // ERROR: 规则间没用空格隔开,屏蔽告警失败
    }
}

单行屏蔽错误示例 2,屏蔽 G.CHK.04 告警

void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return; /*cjlint-ignore !G.CHK.04description */
                // ERROR: 规则与描述信息没用空格隔开,屏蔽告警失败
    }
}

多行屏蔽错误示例 1,屏蔽 G.CHK.04 告警

/* cjlint-ignore -start
 * !G.CHK.04 description */
void fetctl_process(const void *para)
{
    if (para == NULL)
    {
        return;
    }
}
/* cjlint-ignore -end description */
// ERROR: 屏蔽规则没与 'cjlint-ignore' 在同一行,屏蔽告警失败

注意

  1. 特殊注释的 cjlint-ignore 与选项 -start-end 以及屏蔽的规则需要写在同一行上,否则无法进行告警屏蔽。描述信息可以写在不同行。
  2. 单行屏蔽,屏蔽规则与屏蔽规则间需要用空格隔开,cjlint 会将特殊注释所在行的对应规则告警进行屏蔽。
  3. 多行屏蔽,cjlint 会以含有 -start 的特殊注释为起始行,以含有 -end 的特殊注释为结束行,将其间对应的规则进行屏蔽。含有 -end 的特殊注释会与其上方最近的含有 -start 的特殊注释相匹配。

文件级告警屏蔽

  1. cjlint 可以通过 -e 选项支持文件级别的告警屏蔽。

    通过在 -e 后添加屏蔽规则,即可将规则匹配的仓颉文件屏蔽,不会产生关于这些文件的告警。输入的规则为相对 -f 源码目录的相对路径(支持正则),输入字符串需要用双引号包含,多条屏蔽规则用空格分隔。例如,下面这条命令屏蔽了 src/dir1/ 目录内的所有仓颉文件, src/dir2/a.cj 文件和 src/ 目录下所有形如 test*.cj 的仓颉文件。

    cjlint -f src/ -e "dir1/ dir2/a.cj test*.cj"
    
  2. cjlint 可以通过后缀为 .cfg 的配置文件批量导入屏蔽规则。

    通过 -e 选项导入配置文件,与其他屏蔽规则或配置文件用空格分隔。例如,下面这条命令屏蔽了 src/dir1 目录和 src/exclude_config_1.cfgsrc/dir2/exclude_config_2.cfg 内配置的所有屏蔽规则对应的文件。

    cjlint -f src/ -e "dir1/ exclude_config_1.cfg dir2/exclude_config_2.cfg"
    

    .cfg 配置文件中可以配置多条屏蔽规则,每行均为一条屏蔽规则,屏蔽规则为相对该配置文件所在目录的相对路径(支持正则),无需双引号包含。例如在 src/dir2/exclude_config_2.cfg 中有以下配置,则上述的命令会将 src/dir2/subdir1/ 目录和 src/dir2/subdir2/a.cj 文件加入屏蔽。

    subdir1/
    subdir2/a.cj
    
  3. cjlint 可以通过默认配置文件批量导入屏蔽规则。

    cjlint 屏蔽功能的默认配置文件名为 cjlint_file_exclude.cfg,位置在 -f 源码目录下。例如,当 src/ 目录下存在 src/cjlint_file_exclude.cfg 这一配置文件时,cjlint -f src/ 命令会屏蔽 src/cjlint_file_exclude.cfg 内配置的屏蔽规则对应的文件。如果用户已经在 -e 选项中配置了其他有效的 .cfg 配置文件,则 cjlint 不会检查默认配置文件。

  4. cjlint 的屏蔽规则语法对标 gitignore,详见 gitignore 语法官方文档

支持检查的规范列表(持续新增中)

cjlint 默认启用的规范列表:

  • G.NAM.01 包名采用全小写单词,允许包含数字和下划线
  • G.NAM.02 源文件名采用全小写加下划线风格
  • G.NAM.03 接口,类,struct、枚举类型和枚举成员构造,类型别名,采用大驼峰命名
  • G.NAM.04 函数名称应采用小驼峰命名
  • G.NAM.05 let 的全局变量和 static let 变量的名称采用全大写
  • G.FMT.01 源文件编码格式(包括注释)必须是 UTF-8
  • G.FUNC.01 函数功能要单一
  • G.FUNC.02 禁止函数有未被使用的参数
  • G.CLS.01 override 父类函数时不要增加函数的可访问性
  • G.ITF.02 尽量在类型定义处就实现接口,而不是通过扩展实现接口
  • G.ITF.03 类型定义时避免同时声明实现父接口和子接口
  • G.ITF.04 尽量通过泛型约束使用接口,而不是直接将接口作为类型使用
  • G.OP.01 尽量避免违反使用习惯的操作符重载
  • G.OP.02 尽量避免在枚举类型内定义()操作符重载函数
  • G.ENUM.01 避免枚举的构造成员与顶层元素同名
  • G.ENUM.02 尽量避免不同的 enum 的 constructor 之间不必要的重载
  • G.VAR.01 优先使用不可变变量(CJVM 后端暂不支持)
  • G.EXP.01 match 表达式同一层尽量避免不同类别的 pattern 混用
  • G.EXP.02 不要期望浮点运算得到精确的值
  • G.EXP.03 && 、 ||、? 和 ?? 操作符的右侧操作数不要包含副作用
  • G.EXP.05 用括号明确表达式的操作顺序,避免过分依赖默认优先级
  • G.EXP.06 Bool 类型比较应避免多余的 == 或 !=
  • G.EXP.07 比较两个表达式时,左侧倾向于变化,右侧倾向于不变
  • G.ERR.02 防止通过异常抛出的内容泄露敏感信息
  • G.ERR.03 避免对 Option 类型使用 getorthrow 函数
  • G.PKG.01 避免在 import 声明中使用通配符*
  • G.CON.01 禁止将系统内部使用的锁对象暴露给不可信代码
  • P.01 使用相同的顺序请求锁,避免死锁
  • G.CON.02 在异常可能出现的情况下,保证释放已持有的锁
  • G.CON.03 禁止使用非线程安全的函数来覆写线程安全的函数
  • P.02 避免数据竞争(data race)
  • G.CHK.01 跨信任边界传递的不可信数据使用前必须进行校验
  • G.CHK.02 禁止直接使用外部数据记录日志
  • G.CHK.04 禁止直接使用不可信数据构造正则表达式
  • G.FIO.01 临时文件使用完毕必须及时删除
  • G.SER.01 禁止序列化未加密的敏感数据
  • G.SER.02 防止反序列化被利用来绕过构造方法中的安全操作
  • G.SER.03 保证序列化和反序列化的变量类型一致
  • G.SEC.01 进行安全检查的方法禁止声明为open
  • G.OTH.01 禁止在日志中保存口令、密钥和其他敏感数据
  • G.OTH.02 禁止将敏感信息硬编码在程序中
  • G.OTH.03 禁止代码中包含公网地址
  • G.OTH.04 不要使用 String 存储敏感数据,敏感数据使用结束后应立即清 0

cjlint 能够检测,但默认不启用的规范列表(用户可通过将规范添加至 cjlint_rule_list.json 以启用这类规则):

  • G.VAR.03 避免使用全局变量

规格说明

  • G.CON.02 在异常可能出现的情况下,保证释放已持有的锁

    lock() 函数和 unlock() 函数赋值给变量,赋值后的变量再去加解锁的场景,该规则检查不覆盖。

  • G.OTH.03 暂不支持宏检查

  • 只有当宏包在正确的路径下时,cjlint才能支持宏检查

    例:a.cj 为宏包源码,其正确路径应为 xxx/src/a/a.cj

  • cjlint只有在宏被调用时才能对其进行检查,且无法对宏包中的冗余代码进行检查

支持语法禁用检查

  1. cjlint 可以通过将 G.SYN.01 添加至 cjlint_rule_list.json 以启用禁用语法的检查。如果使用了禁用的语法元素,cjlint 将会报错。

  2. 当前所支持cjlint检查的禁用语法如表中所示:

    禁用语法关键词说明
    导入包Import不允许随意导入包
    let 变量Let只用 var 变量,不引入不可写变量的概念
    创建线程Spawn不允许创建线程
    线程同步Synchronized防止死锁
    主函数Main禁止提供入口主函数
    定义宏MacroQuote禁止定义宏(但允许使用宏)
    跨语言Foreign禁止跨语言混合编程
    while 循环While防止复杂循环和死循环
    扩展Extend禁止使用扩展语法
    类型别名Type禁止自行定义类型别名
    操作符重载Operator禁止重载操作符
    全局变量GlobalVariable禁止声明和使用全局变量,防止副作用和内存泄漏
    定义枚举Enum禁用 Enum,避免复杂代码
    定义类Class禁用 Class,避免复杂代码
    定义接口Interface禁用 Interface,避免复杂代码
    定义结构Struct禁用 Struct,避免复杂代码
    定义泛型Generic禁用 Generic,避免复杂代码
    条件编译When禁止平台相关代码
    自动微分Differentiable禁用 Differentiable
    模式匹配Match函数式编程范式,开发者不易掌握
    捕获异常TryCatch避免自行处理异常,易导致错误被忽略
    高阶函数HigherOrderFunc函数类型的参数或返回值, 避免复杂代码
    其他基础类型PrimitiveType不应使用 Int64、float64、bool 之外的其他基础类型
    其他容器类型ContainerType应使用 List,Map,Set
  3. 通过将上述表格中的关键字添加到 structural_rule_G_SYN_01.json 中启用对应语法的禁用检查。举例:禁用导入包

{
  "SyntaxKeyword": [
    "Import"
  ]
}