我复现了EMNLP那篇CodeReviewer思路,在VS Code里跑Llama 3.2做代码审查,然后连夜改了三处SQL注入规则

我在读CodeReviewer那篇论文时,脑子里一直闪回自己给同事做PR review的场景:同样的SQL拼接问题,我已经在comments里复制粘贴过不下20次了。这活儿本质上就是个模式匹配加一点上下文推理,理论上很适合让语言模型来干。论文里的思路很清晰,用T5架构预训练,在代码diff和review comment对上做微调,F1冲到0.82,看起来数据很漂亮。

但真正把这一套想法搬进VS Code,在真实的Spring Boot项目里跑起来,我才发现学术benchmark和日常工程之间隔了整整一个太平洋。最典型的问题:论文评测用的是清理过的开源代码,变量命名规范、函数边界清晰;而我的项目里,一个DAO层方法里揉了三个if-else、动态表名拼接、还有从HttpServletRequest里直接扒出来的参数。模型直接懵了,要么漏掉真漏洞,要么把做了白名单校验的地方也标成高危。

下面我按自己探索的路径,从配置开源工具、检测异味、补上安全规则到塞进CI流水线,把整个踩坑过程说清楚。所有操作都在VS Code里完成,不跳出编辑器,也不需要什么企业级SaaS后台。

30秒速览

  • - 在VS Code内用Continue+本地Llama 3.2模型做代码审查,论文里的高F1在真实项目中会因缺少业务上下文大幅下降
  • - 单独靠AI检测SQL注入不可靠,用Semgrep写确定性规则捕获注入点,再让AI把告警翻译成重构建议,效果显著提升
  • - CI/CD里只跑Semgrep规则不跑模型,把AI审查留在本地开发环节,流水线增加时间不超过15秒
  • - 持续迭代Semgrep规则库比微调模型更有积累价值,审查质量随着规则库增长而提高

把论文里的自动审查塞进VS Code,我选的是Continue加本地Llama,而不是Copilot的原生功能

为什么我没直接用GitHub Copilot Code Review

GitHub Copilot在2025年终于推出了Code Review功能,入口就在VS Code的PR面板里,能对diff生成分点review summary。但我在公司环境里一开,立刻就撞到两个软钉子:一是它必须把代码diff上传到云端模型,内网项目没法用;二是它生成的review comment风格极其“客气”,对明显的SQL注入风险,它给的措辞是“建议考虑参数化查询”,而不是标红阻断。(延伸阅读:我给产线看板切了Next.js 15,构建从47秒掉到4秒,但缓存策略差点让200个工件报废

我需要的是更激进的审查,能直接在编辑器里对当前文件或选中代码按我的规则打分,而且必须全部本地运行。所以我转向了Continue这个开源扩展,后端接上Ollama拉下来的Llama 3.2模型,再配合Semgrep做结构化检测。这一套全是VS Code内的工具,不需要跳出IDE,配置也集中在两个JSON/YAML文件里。

Continue配置里那几个让我反复调教的参数

Continue本身是个聊天框,但可以定义slash command,比如/review就能触发审查prompt。我在config.json里加了一个自定义命令,把当前打开的文件内容作为上下文,然后拼接一段非常具体的system prompt,要求模型只关注安全漏洞和性能问题,并且必须给出代码修改建议,不要讲废话。

下面是我踩过坑之后稳定下来的配置片段,注意model、temperature和maxTokens这三个值,我后面会解释为什么这么设:(延伸阅读:GitHub Copilot Chat的上下文感知就像论文里的RepoCoder,但生产环境里它用了一套让索引工程师沉默的捷径

{
  "models": [
    {
      "title": "Local Llama 3.2",
      "provider": "ollama",
      "model": "llama3.2:3b",
      "apiBase": "http://localhost:11434"
    }
  ],
  "customCommands": [
    {
      "name": "review",
      "prompt": "# Role: Senior Security-Focused Code ReviewernnReview the following code for:n1. SQL injection risks (especially string concatenation in queries)n2. N+1 query patterns that may cause performance degradationn3. Unclosed resources or connection leaksn4. Hardcoded credentials or secretsnnFor each finding, provide:n- Severity level (BLOCK / WARNING)n- Exact line(s) of concernn- A concrete refactoring suggestion in code diff formatnnFocus only on actionable issues. Do not comment on code style or naming unless it directly contributes to a bug.nnCode to review:n```n{{code}}n```",
      "description": "Review current file for security and performance issues"
    }
  ],
  "tabAutocompleteModel": {
    "provider": "ollama",
    "model": "codellama:7b"
  }
}

我一开始把temperature设成0.7,想让模型在生成建议时有点“创造性”,结果它对同一个SQL拼接问题,连续三次给出了三种不同的修复方案:参数化查询、PreparedStatement、还有JPA Criteria API。这在一个真实PR里会直接制造争议,同事会问我“你到底推荐哪种写法?”所以我把temperature直接砸到0,强制模型每次都输出最确定的那套方案。maxTokens设成1024也足够它讲清楚每一个issue的修复细节,再大就容易胡扯。

我让本地Llama 3.2审查了一个200行的DAO类,揪出了论文里的盲区

代码异味检测:模型对“看起来危险”的模式很敏感,但对业务安全上下文完全失明

我故意在一个订单查询的DAO里写了三种典型的SQL拼接:直接用+号拼参数、用StringBuilder动态构造WHERE子句、以及用MyBatis的${}取值。同时,我在Service层做了严格的参数白名单校验,只有传入的orderStatus在固定枚举值里才会进到DAO。这段代码从业务角度看是安全的。

我把整个类喂给/review命令,模型只用了不到3秒就返回了结果。它准确标记了三处拼接点,都打了BLOCK级别,建议全部改成#{}或PreparedStatement。问题在于,它完全忽略了上游的白名单逻辑,给出的修复建议会让代码变得啰嗦,甚至有人会直接照抄,去掉Service层的校验,反而降低了整体安全性。(延伸阅读:我把汽车零部件厂的质检系统升级Next.js 15:构建从55秒降到4秒,但一次路由缓存失误差点引发批量召回

这正好呼应了CodeReviewer论文里一个我当初没太在意的局限:模型审查的是孤立的diff或代码片段,没有完整的调用链上下文。论文里用CodeReview数据集训练,但那个数据集本身就是从开源PR里提取的diff,天然丢失了工程里才有的多层防御结构。实际用的时候,如果盲目信任模型的BLOCK,很容易让开发者养成“只修告警、不看逻辑”的习惯。

性能问题检测:它发现了N+1查询,但没意识到这是个读写分离的从库

另一段代码里,我在循环里对每个订单调了查询用户名的RPC。模型准确标记出N+1问题,建议改成批量查询。这在大多数场景下是对的,但在这个项目里,那条查询走的是Redis缓存,压力极小。我把这点反馈给模型——在继续聊天里补充了上下文——它才改口说“在缓存命中的情况下影响有限”。这说明静态代码审查缺少运行时信息,而真正的代码审查,审查者脑子里装的其实是这些运行时知识。

论文里那些0.8以上的F1分数,是在限定好问题类型、清理过数据的评测集上跑出来的。一旦把代码投入真实生产项目,模型的“准确率”会随着业务逻辑复杂度的上升而断崖式下跌。这也是我为什么在Continue的prompt里把审查范围严格限定在四个具体问题上,而不是让模型自由发挥。缩小问题域,是让自动审查变得可用的第一步。(延伸阅读:Vercel AI SDK 3.0 这一步棋,下在了所有 LLM 应用开发者的心坎上

我没有让模型直接写安全规则,而是用Semgrep定义SQL注入模式,再把结果喂给AI解释

自定义Semgrep规则才能抓住那些框架相关的注入点

对于SQL注入这种有明确特征的问题,靠模型的模式识别不够可靠——它今天能识别,明天稍微变个写法就可能漏掉。我需要确定性的检测,然后让AI在检测结果上做“翻译”——把冷冰冰的规则描述变成开发者一眼能懂的修复建议。

Semgrep的VS Code扩展会在你保存文件时自动匹配规则,并在问题行用黄色波浪线标出。我写了一个专门针对MyBatis和JDBC场景的规则集,下面这条规则捕捉XML mapper里的${}和拼接字符串的Statement:

rules:
  - id: mybatis-sql-injection
    patterns:
      - pattern-either:
          - pattern: |
              ${...}
          - pattern: |
              Statement $ST = $CONN.createStatement();
              ...
              $ST.executeQuery("..." + $PARAM + "...")
      - pattern-not: |
          #{} 
    message: |
      检测到不安全的SQL值拼接。${}不会进行预编译转义,请改用#{}。  
      对于动态表名/列名,请使用白名单校验后再安全拼接。
    languages:
      - xml
      - java
    severity: ERROR
    metadata:
      category: security
      cwe: "CWE-89"
      confidence: HIGH

这条规则会在XML mapper文件里每次出现${}时告警,同时排除#{}的用法避免误报。在Java代码里,它会捕获直接用Statement拼接字符串的模式。(延伸阅读:给注塑车间看板上Next.js 15,构建速度从47秒掉到3秒,但一次Server Actions报错让质检停了整整4小时

AI做的不是替换规则,而是把规则输出“翻译”成重构步骤

Semgrep报警之后,我在报警的那一行选中代码,按Ctrl+L调出Continue聊天框,输入一段简短的prompt:“解释这个Semgrep告警,并给出修复该问题的具体代码改动”。模型会结合我选中的代码上下文,解释为什么${}危险,并直接把那一块XML改成#{}的写法,同时提醒如果orderBy是动态列名,需要单独做白名单校验。

这种“规则检测+AI解释”的组合,比单独用任何一边都靠谱。Semgrep帮助发现潜在漏洞,AI辅助解释告警,降低开发者关闭规则的概率。。论文里追求的端到端自动修复,在生产环境往往要么过于激进,要么修复质量不可控;而我这种半自动的流水线,实际落地时反而更容易被团队接受。

把这一套塞进CI/CD后,我发现审查的瓶颈根本不是AI推理速度

GitHub Actions里跑本地模型简直是资源黑洞,我改成只跑Semgrep加diff摘要

在PR上线的最后一步,我把这套审查逻辑塞进了GitHub Actions。如果每次push都启动Ollama加载3B模型,一个job的启动时间会超过2分钟,再加上审查推理,整个流水线耗时直奔5分钟。这在快速迭代的团队里不可接受。

我最终采取的方案是:CI里只跑Semgrep规则,并将告警结果输出为SARIF文件,然后利用一个极轻量的Node脚本把SARIF转换成Markdown格式的逐条告警描述。这个描述会被GitHub Actions自动注入为PR评论。AI的部分,回归到开发者在VS Code本地自行触发,不做强制检查。这样一来,CI流水线的时间只增加了不到15秒,而AI辅助审查变成了一个主动、异步的开发习惯,而不是卡门的质量门禁。

这个分割让我重新理解了论文里那些自动审查系统的设计前提——它们假设算力无成本、延迟不敏感。在真实的CI/CD环境里,审查的瓶颈永远是“开发者等待的时间”和“告警的可信度”,而不是模型一次推理的速度。把重计算留在本地开发,把确定性检查留到CI,是唯一解。

自定义规则真正发挥作用,是在CI里积累了一个项目的“审查记忆”

跑了两周之后,我发现最有价值的资产不是AI模型,而是我不断迭代的那个Semgrep规则库。每遇到一个漏报,我就加一条新规则;每遇到一个误报,我就用pattern-not-inside或metavariable-comparison把它排掉。两个礼拜下来,规则库从8条长到23条,覆盖了SQL注入、不安全的反序列化、路径遍历和几个项目特有的框架风险。AI模型虽然没变,但因为规则喂给它的上下文越来越精准,它给出的修复建议也越来越像我们这个项目专属的资深reviewer了。

这种“规则驱动、AI翻译”的模式,比论文里追求的全自动模型微调务实得多,而且完全在VS Code生态内就能完成闭环。

实验笔记
我最开始用Llama 3.2 3B跑审查时,把上下文窗口设到了16000 token,想让模型看到整个文件。结果发现审查耗时与文件长度成平方关系,200行代码只要3秒,500行就涨到12秒,而且超过600行后,模型开始无中生有地“发现”根本不存在的注入点。后来我限制了每次/review只审查当前打开文件中光标的可视范围(约60行),审查时间稳定在2秒以内,误报率下降了将近一半。Continue的config里,我是这样切的:先用Selection获取代码,再发给模型,而不是传整个文件。这个改动很小,但实用程度直接翻倍。接下来我会试试把CodeReviewer论文里的diff结构用进prompt里,让模型在审查时同时看到改动前和改动后的代码,这样它判断上下文变化时应该会更准——但这需要和Git diff命令联动,我还在写那个VS Code扩展的pre-commit钩子。

本文由 AI 辅助生成,经人工审核后发布。内容由 韩知行 基于实战经验指导完成。

觉得有用?

韩知行

大厂AI研究员,博士毕业后在工业界做了4年。读论文、复现模型、部署上线都干过。学术和工程都懂一些,所以特别理解「论文里99%的SOTA在生产环境不work」这件事。喜欢把前沿研究翻译成工程师能理解的语言。

发表评论