# 源码集成代码分析工具

## 初识TCA任务执行机制

1. TCA server在接收到开启分析的请求后根据所选规则生成对应的task_request，每个task_request对应一个工具的任务
2. TCA server将`task_request`分发到能够执行该工具的机器
3. TCA client在收到task_request后提取出本次任务的工具名也就是其中的`task_name`字段，字段对应于工具的`name`字段
4. TCA client按照`task_name`在client中的tool目录查找对应python启动脚本
5. 执行python启动脚本中的内容

## 添加分析工具（以 tca_ql_php 为例）

根据上述的任务机制添加工具需要做到以下几点

1. 让server知道存在`tca_ql_php`工具及其所含的规则
2. 让server知道哪些客户端可以执行`tca_ql_php`工具
3. client下载/找到工具所在目录及需要的环境
4. 让client知道`tca_ql_php`对应的启动脚本是什么

### 如何让Server知道存在相应工具

1. 找到`server/projects/main/apps/scan_conf/management/commands/open_source`目录

2. 创建工具json文件，json文件名尽量对应工具名称方便查看

3. json文件内容为（以 tca_ql_php 为例)

  ```python
  [
      {
          "name": "tca_ql_php",
          "display_name": "Hades_PHP（展示名称用于前端展示使用）",
          "description": "工具描述",
          "license": "工具license",
          "libscheme_set": [], # 暂时不需要
          "task_processes": [
              "analyze",
              "datahandle",
              "compile"
          ],  # 工具进程，包含compile编译, analyze分析, datahandle数据处理
          "scan_app": "codelint",  # 代码分析统一为codelint
          "scm_url": "", # 暂时为空
          "run_cmd": "",
          "envs": null, # 是否需要特殊环境，这里无需填写
          "build_flag": false, # 是否需要编译命令才能运行
          "checkrule_set": [  # 工具包含的规则
              {
                  "real_name": "deser",  # 规则名
                  "display_name": "反序列化漏洞",  # 规则前端展示，考虑各工具规则名可能晦涩难懂，设置展示名称方便查找
                  "severity": "error",  # 规则等级 从上到下分为 fatal, error, warning, info 四个等级
                  "category": "security",  # 规则类别。correctness 功能 security安全 performance性能 usability可用性 accessibility无障碍化 i18n国际化 convention代码风格 other其他
                  "rule_title": "反序列化漏洞",  # 一句话概括规则简介
                  "rule_params": null,  # 规则参数
                  "languages": [  # 支持语言
                      "php"
                  ],
                  "solution": "",  # 建议的解决方法
                  "owner": "",
                  "labels": [],
                  "description": "",  # 规则详细介绍
              }
          ]
      }
  ]
  ```
  
4. 在`server/projects/main/`目录执行`python manage.py loadcheckers --dir open_source tca_ql_php` 加载工具进入数据库

## 让server知道哪些客户端可以执行`tca_ql_php`工具

1. 进入节点管理页面

![节点管理](../../images/Nodemanagement.png)

2. 选择其中一台机器 工具进程配置，勾选其工具进程

![工具进程](../../images/ProcessConfiguration2.png)

## client下载/找到工具所在目录及需要的环境

1. 找到puppy-tool-config若没有额外配置则为默认代码库<https://github.com/TCATools/puppy-tools-config.git>
2. 修改其中的 ini 配置文件，每个操作系统对应一个ini
3. 以 tca_ql_php 为例需要做以下修改

```
; env_path 主要填写存放工具文件所在的相对目录，一般都存放/拉取在tools下，会在工具执行前加载到环境变量中提供使用
[env_path]
ZEUS_HOME   : Zeus
HADES_HOME  : Hades

; toolz_url
[tool_url] 主要填写工具的git仓库，这里因为 tca_ql_php 直接使用tools下的目录所以不用再进行额外拉取也无需再写
CPPCHECK    : ${base_value:git_url}/linux-cppcheck-1.78

; 各工具配置 以 tca_ql_php 为例
; env_path 填写上面需要加载的环境变量
; env_value 通用环境变量，一般无需填写如果有需求需要现在 [env_value] 中定义好再填写
; path 工具所在目录填写上面的定义
; tool_url 工具git仓库，使用本地相对目录故为空
[tca_ql_php]
env_path  : ZEUS_HOME;HADES_HOME
env_value :
path      : ${env_path:ZEUS_HOME};${env_path:HADES_HOME}
tool_url  : 

```

## 让client知道`tca_ql_php`对应的启动脚本是什么

1. 以上述步骤在`client/tool`目录添加脚本`tca_ql_php.py`作为启动脚本 注：启动脚本必须与工具名称相同

2. 编写脚本

### 脚本编写规范

以`tca_ql_php`为例

```

from task.codelintmodel import CodeLintModel
from util.logutil import LogPrinter
from util.subprocc import SubProcController

logger = LogPrinter()


class TcaQlPHP(CodeLintModel):
    # 代码分析工具集成基类CodeLintModel
    def __init__(self, params):
        logger.info("找到工具了Q_Q")
        super().__init__(params)

    def compile(self, params):
        logger.info("开始编译了Q_Q")
        build_cmd = params.get('build_cmd', None)  # 从params中获取编译命令, params内容可以在最后附录查看
        lang = "php"
        do_some_things()

    def analyze(self, params):
        logger.info("开始分析了Q_Q")
        lang = "php"
        HADES_HOME = envs.get("HADES_HOME", None)
        output_json = "result.json"
        sp = SubProcController(
            command=["Hades", "analyze", "test.php", "-o", output_json],
            cwd=HADES_HOME,
            stdout_line_callback=subprocc_log,
            stderr_line_callback=subprocc_log,
        )
        sp.wait()  # 执行工具分析命令
        issues = []
        # 工具结果输出到output_json，具体工具可能有所不同
        if os.path.exists(output_json):
            with open(output_json, "r") as result_reader:
                result = json.load(result_reader)
                issues.extend(result)
        return issues

tool = TcaQlPHP  # 必须，必须包含tool变量并且为该工具的类
```

1. 脚本必须包含analyze方法，如果有配置编译进程也需要相应的compile方法来做编译相关工作，datahandle函数不用自定义基类方法已经够用了。方法执行顺序为 compile -> analyze -> datahandle
2. params参数为`task_request`中的`task_params`字段，具体字段将在最后附录进行说明
3. anlyze方法必须有返回值，返回值为issue列表，issue格式为

```
{
    "path": "文件相对路径",
    "line": "行号，int类型",
    "column": "列号, int类型，如果工具没有输出列号信息，可以用0代替",
    "msg": "提示信息",
    "rule": "规则名称,可以根据需要输出不同的规则名",
    "refs": [
        {
            "line": "回溯行号", 
            "msg": "提示信息", 
            "tag": "用一个词简要标记该行信息，比如uninit_member,member_decl等，如果没有也可以都写成一样的", 
            "path": "回溯行所在文件绝对路径"
        },
        ...
    ]
}
说明：
    refs：可选，记录问题回溯路径信息。比如当前文件的回溯路径其他的3行代码，可以将这三行的路径及提示信息，按顺序添加到refs数组中。
```

# PR

如果有意公开您添加的工具欢迎发起PR

注：别忘了puppy-tool-config 也需要PR

# 附录

## params 表格

| 字段 | 说明 | 类型 |
| --- | --- | --- |
| scan_languages | 语言 | 字符串列表如 ["python", "php"] |
| pre_cmd | 编译前置命令 | 字符串 |
| build_cmd | 编译命令 | 字符串 |
| envs | 额外环境变量 | 字符串 |
| scm_last_revision | 上次成功分析的代码版本，增量使用 | 字符串 |
| incr_scan | 是否为增量分析 | bool |
| rules | 规则名称列表，只有规则名 | 字符串列表 |
| rule_list | 详细的规则列表包含规则名和规则参数等 | 字典列表 |
| checktool | 工具详细信息，执行一般用不到 | 字典 |
| path_filters | 过滤路径 | 字典 |
| scm_url | 代码库url | 字符串 |
| source_dir | 代码库本地目录 | 字符串 |
| work_dir | 本次任务的work_dir目录 | 字符串 |
| project_id | 分析项目id | int |
| repo_id | 仓库id | int |
| task_id | 任务id | int |
| job_id | 本次分析的id | int |
