☰
JPlag 支持的语言全景:模块体系、成熟度标签与语言模块加载机制
2026/10/14 16:34:20 网站建设 项目流程
  • 代码质量
  • 教育
  • 后端

【免费下载链接】JPlag

State-of-the-Art Source Code Plagiarism & Collusion Detection. Check for plagiarism in a set of programs.

项目地址:https://gitcode.com/gh_mirrors/jp/JPlag
点击查看免费下载

JPlag 是一款本地运行的源代码抄袭与串通检测工具,其多语言能力通过一组可插拔的语言模块实现。本文基于 JPlag 仓库中的 支持语言文档 展开,结合languages/目录下的 20 余个语言模块源码、Language 接口 与 LanguageLoader 的加载实现,系统梳理:JPlag 当前支持哪些语言及其版本、各模块成熟度标签(mature/beta/alpha/legacy/unknown)的确切含义、语言模块必须遵守的接口契约(文件后缀、最小匹配 token 数、语义信息、归一化等),以及 CLI 子命令如何动态发现并调用语言模块。读完后你可以准确为不同语言的提交集选择合适的检测模块,并理解新增语言模块的接入方式。

一、当前支持的语言清单

根据 README 的 Supported Languages 表格,JPlag 支持的完整语言清单、对应语言版本、CLI 参数名、成熟度标签(state)与所用解析器如下(本表继承自 README,CLI 参数名已按源码中的实际 identifier 校准):

LanguageVersionCLI 参数名stateparser
Java25javamatureJavaC
C11clegacyJavaCC
C++14cppmatureANTLR 4
C#6csharpmatureANTLR 4
Python3.6python3matureANTLR 4
JavaScriptES6javascriptbetaANTLR 4
TypeScript~5typescriptbetaANTLR 4
Go1.17gobetaANTLR 4
Kotlin1.3kotlinmatureANTLR 4
R3.5.0rlangmatureANTLR 4
Rust1.60.0rustmatureANTLR 4
Swift5.4swiftbetaANTLR 4
Scala2.13.8scalamatureScalameta
LLVM IR15llvmirbetaANTLR 4
Scheme?schemelegacyJavaCC
EMF Metamodel2.25.0emfbetaEMF
EMF Model2.25.0emf-modelalphaEMF
SCXML1.0scxmlalphaXML
Text(naive, use with caution)-textlegacyCoreNLP
Multi-Language-multialpha-

两点源码层面的补充说明:

  • 从源码看,Go 模块 GoLanguage 的getIdentifier()返回go,README 的 CLI 帮助列表(Languages:小节)也以go为准;README 表格中golang的写法与当前源码不一致,使用时以go为准。
  • languages/目录中还包含一个额外的 Maven 子模块 emf-metamodel-dynamic,其 DynamicEmfLanguage 提供基于动态 Ecore 模型的 EMF 检测能力(identifier 为emf-dynamic)。从源码结构看,该模块未标注@AutoService,因此不会出现在 CLI 的Languages:子命令列表中,主要面向 API 集成场景。

语言模块的“版本”一列指模块所依据的语法/语言版本,并非承诺对更新版本的全量支持;运行 JPlag 本身需要 Java SE 25(见 README)。

二、语言模块的成熟度标签(state)

支持语言文档 指出,由于各语言模块的年龄与被使用频率不同,其成熟度存在差异,因此每个前端(frontend)都被赋予一个 state 标签。这五类标签的定义是选用模块时的第一参考依据:

  • mature:经过充分验证,并且(大致)与当前语言版本保持同步;
  • beta:相对较新且(大致)保持同步,但测试覆盖不如 mature 充分,欢迎反馈(Feedback welcome);
  • alpha:非常新、尚未完成,请谨慎使用(Use with caution);
  • legacy:来自 JPlag legacy(v3.0.0 之前),可能只支持过时的语言版本,等待更新;
  • unknown:该模块处于什么状态尚不明确。

对照上表可以看到:Java、C++、C#、Python、Kotlin、R、Rust、Scala 为 mature;JavaScript、TypeScript、Go、Swift、LLVM IR、EMF Metamodel 为 beta;EMF Model、SCXML、Multi-Language 为 alpha;C、Scheme、Text 为 legacy。例如 C 模块的 README 还特别提示:该模块“可能”可以解析 C++ 提交,但应使用 cpp 模块处理 C++ 代码——这正是 legacy 模块能力边界的典型说明。

三、语言模块的源码组织

所有语言模块集中在 languages/ 目录,由 languages/pom.xml 聚合为 21 个 Maven 子模块:c、cpp、csharp、emf-metamodel、emf-metamodel-dynamic、emf-model、golang、java、kotlin、python-3、rlang、rust、scala、scheme、scxml、swift、text、typescript、javascript、llvmir、multi-language。

每个模块内部遵循统一结构(详见 Adding New Languages 指南):

组件父类型职责
Language 类de.jplag.Language语言模块的访问入口
pom.xml-Maven 子模块描述符
README.md-模块级文档
TokenType 类de.jplag.TokenType语言专属 token 类型
Lexer/Parser-源码到 AST 的转换
ParserAdapter 类de.jplag.AbstractAntlrParser(ANTLR 场景)装配 Parser 并调用遍历器
Traverser/Listener 类-遍历 AST 并生成 token

值得注意的核心抽象是:JPlag 中的Token 不是词法单元(如标识符、关键字),而是语法实体(如语句、控制结构)的表示。例如class MyClass { private String name; }会被表示为CLASS_DECLARATION、CLASS_BODY_BEGIN、FIELD_DECLARATION、CLASS_BODY_END这样的 token 序列——标识符、字面量等语义信息被有意省略,从而让比较算法聚焦于代码结构。各模块的完整测试文件(如 Complete.go、Complete.R)覆盖了目标语言的主要语法元素,可用于直观了解各模块的 token 化粒度。

四、语言模块的接口契约:Language接口

每个语言模块必须实现 language-api 中的Language接口。该接口定义了 CLI 与核心比较算法依赖的全部契约,关键方法包括:

方法说明
fileExtensions()该语言程序文件的合法扩展名(不区分大小写;空列表表示接受所有扩展名)
getName()描述性名称,如Java
getIdentifier()用于 CLI 选项与动态加载的短标识符,建议符合[a-z_-]+
minimumTokenMatch()该语言下构成一次匹配所需的最少 token 数(默认比较灵敏度基线)
parse(Set<File>, boolean normalize)解析一个提交的全部文件,返回 token 列表
tokensHaveSemantics()返回的 token 是否携带语义信息
supportsNormalization()/requiresCoreNormalization()是否支持 token 序列归一化,以及是否使用 core 模块的归一化
useViewFiles()/viewFileExtension()报告展示是否使用“视图文件”(对输入文件的另一种表示)
expectsSubmissionOrder()/customizeSubmissionOrder()提交顺序是否影响解析结果
supportsMultiLanguage()/hasPriority()能否被 multi 模块复用;多模块支持同一扩展名时是否优先
getOptions()返回语言专属选项对象(供 CLI 子命令展开)
getCommentExtractorSettings()供注释提取器使用的分隔符配置

4.1 各模块的最小匹配 token 数(minimumTokenMatch)

minimumTokenMatch()是每个模块自行声明的“一次匹配至少需要多少 token”。从各语言模块源码可直接读出当前取值:

模块最小匹配 token 数模块最小匹配 token 数
C (c)12Kotlin (kotlin)8
C++ (cpp)12LLVM IR (llvmir)70
C# (csharp)8Python 3 (python3)12
EMF (emf)6R (rlang)8
Go (go)8Rust (rust)8
Java (java)9Scala (scala)8
SCXML (scxml)6Swift (swift)8
Scheme (scheme)13Text (text)5
TypeScript (typescript)12JavaScript (javascript)继承 TypeScript(12)

这些默认值可被 CLI 的-t, --min-tokens参数覆盖以调节灵敏度(取值过小会增加误报,见 README CLI 说明)。其中 LLVM IR 高达 70,反映了 IR 文本 token 密度高、需要更长片段才算有效匹配的设计取向;Text 模块仅 5,对应其“naive、谨慎使用”的定位。

4.2 文件扩展名与模块间关系

各模块声明的扩展名同样决定了 CLI 按什么文件后缀收集提交。几个具有代表性的例子:

  • CLanguage 声明.cpp、.cxx、.c++、.c、.cc、.h、.hpp、.hh、.hxx——因此 C 模块也能吃掉 C++ 文件;
  • CPPLanguage 声明完全相同的扩展名集合,并通过hasPriority()返回true声明“对 C 语言模块拥有优先级”(源码注释:Priority over the C language module),保证 multi 场景下 C++ 文件优先交给更成熟的 cpp 模块;
  • JavaScriptLanguage 直接继承 TypeScriptLanguage,仅改写 identifier(javascript)、名称与扩展名(.js),注释明确说明“JavaScript 作为 TypeScript 的一个变体,将全部责任委托给 TypeScript 语言模块”;
  • NaturalLanguage(text 模块)接受.txt、.asc、.tex、.md、.rtf、.csv、.wiki、.json、.yaml、.yml、.xml等大量文本类后缀。

此外,Java 模块还通过tokensHaveSemantics()与supportsNormalization()返回true,表明它返回带语义的 token 并支持 CLI--normalize选项(README 说明该选项当前支持 Java 与 C++);JavaLanguage 还通过getCommentExtractorSettings()提供了字符串分隔符("""、"、')、行注释(//)、块注释(/* */)与转义符(\)配置,用于注释提取场景。

五、语言模块的加载机制与 CLI 集成

5.1 ServiceLoader 动态发现

语言模块的注册与发现全部基于 Java SPI:每个语言类通过@AutoService(Language.class)注解(编译期生成服务注册文件)声明自己是Language实现,例如 JavaLanguage 类头的@AutoService(Language.class)。

LanguageLoader 是发现逻辑的核心:

for (Language language : ServiceLoader.load(Language.class)) { String languageIdentifier = language.getIdentifier(); if (languages.containsKey(languageIdentifier)) { logger.error("Multiple implementations for a language '{}' are present in the classpath! Skipping ..", languageIdentifier); languages.remove(languageIdentifier); continue; } ... languages.put(languageIdentifier, language); }

要点:

  • 通过ServiceLoader.load(Language.class)扫描 classpath 上所有语言实现,按getIdentifier()建立映射并缓存(getAllAvailableLanguages());
  • 若同一 identifier 出现多个实现,会记录错误日志并跳过该 identifier,避免歧义;
  • getLanguage(String identifier)按短名加载单个模块,未命中时输出 WARN 日志。这意味着 classpath 中放入了某个语言 jar,它就能被识别——CLI 帮助中列出的 20 个Languages:子命令正是这 20 个@AutoService模块的投影(见 cli/pom.xml 对languages/各模块的依赖聚合)。

5.2 CLI:子命令优先于-l选项

README 的 CLI 说明 规定:语言可以用-l <language>参数设置,也可以作为子命令传入(jplag [jplag options] -l <language name> [language options]),且子命令优先于-l。源码中 CliInputHandler 直接以子命令名查询语言模块:

Language language = LanguageLoader.getLanguage(subcommand.commandSpec().name()) .orElseThrow(() -> new CliException(IMPOSSIBLE_EXCEPTION));

子命令形式的主要好处是支持语言专属选项:每个模块可通过getOptions()返回一个 LanguageOptions 子类,其中的选项会自动展开为对应子命令的参数(例如jplag java -h可查看 Java 子命令的全部选项)。MultiLanguage 即是一个自带选项的示例:它允许指定参与多语言解析的具体语言集合,其minimumTokenMatch()取所选语言中的最小值。

5.3 Multi-Language 模块的边界

multi子命令由 MultiLanguage 实现,是一个“多语言门面”:

  • fileExtensions()聚合 classpath 上所有其他语言模块的扩展名;
  • parse()将每个源文件按其扩展名委托给对应的具体语言模块(MultiLanguageParser);
  • 解析时会输出警告:"This module only allows parsing of multiple languages. No comparisons will be made between languages"——即它只解决“混合语言提交集都能被解析”的问题,相似度比较仍只在同语言 token 列表之间进行;
  • 其supportsMultiLanguage()返回false,防止被再次嵌套进 multi 门面。

六、按场景选择语言模块的实践建议

结合上述事实,选型时可按以下规则判断:

  1. C 与 C++ 混用:优先使用cpp(mature、ANTLR 4、对 C 扩展名有优先级);c(legacy)仅在需要旧版 C 行为时考虑,其 README 也明确建议 C++ 代码使用 cpp 模块;
  2. JavaScript/TypeScript 提交集:两者共用 TypeScript 语法实现,javascript模块只是扩展名与标识符不同的变体,可任选其一处理各自文件,多文件混合时使用multi;
  3. EMF 场景:.ecore元模型文件用emf(beta);EMF 实例模型用emf-model(alpha);API 集成且元模型动态变化时可研究emf-metamodel-dynamic子模块;
  4. 纯文本:text模块基于 CoreNLP 的朴素分词,标签为 legacy 且 README 标注“naive, use with caution”,其结果解释需格外谨慎;
  5. 对结果稳健性要求高:优先选择 mature 模块(Java、C++、C#、Python、Kotlin、R、Rust、Scala),beta/alpha 模块的结果建议作为参考并结合人工复核;
  6. 灵敏度调节:在选定模块后,用-t/--min-tokens在该模块minimumTokenMatch默认值附近调节,并用--similarity-threshold过滤低相似度比较(均见 README CLI 参数表)。

七、验证与扩展:TokenPrinter、language-testutils 与新增模块流程

理解各语言模块“实际产生了什么”,最直观的工具是 TokenPrinter:它逐行打印源代码,并在每行下方标注该行产生的 token,例如:

10 public class Example { |CLASS |CLASS{ 11 private int number; |FIELD

各语言模块的单元测试普遍采用 language-testutils 提供的基础设施:测试类继承LanguageModuleTest,通过TestDataCollector声明测试源文件、覆盖率检查与addTokenPositionTests位置断言(测试文件内用>标记源码行、$行标注期望 token 的列号、类型与长度)。以 languages 聚合 POM 为例,language-testutils已作为test-jar依赖注入所有语言模块的测试作用域。

若要新增语言模块,docs/4.-Adding-New-Languages.md 给出了完整流程:创建 Language 类与 TokenType 实现(ANTLR 场景可复用language-antlr-utils中的AbstractAntlrLanguage/AbstractAntlrParser框架)、在 languages/pom.xml 注册子模块、为 cli 与 coverage-report 模块添加依赖、更新 README 与docs文档。由于加载完全依赖 ServiceLoader,只要新模块被加入 classpath 且带@AutoService(Language.class),CLI 子命令与-l选项即可自动识别该语言。

小结

JPlag 的语言支持体系可以概括为三层:其一,languages/下 21 个 Maven 子模块按统一契约(Language接口)封装各语言的解析与 token 化,并各自标注 mature/beta/alpha/legacy/unknown 成熟度标签;其二,LanguageLoader 通过 ServiceLoader 以 identifier 为键动态发现模块,使 CLI 子命令、-l选项与 Java API(JPlagOptions直接持有Language实例)三条使用路径共享同一套语言注册表;其三,multi门面与hasPriority()/supportsMultiLanguage()等机制处理了 C/C++、JavaScript/TypeScript 等扩展名重叠与混合提交集的边界情形。选择检测方案时,先按语言在上表中的 state 标签评估可信度,再用模块默认值(扩展名、minimumTokenMatch)与-t等 CLI 参数校准灵敏度,即可得到可复现的检测配置。

  • 代码质量
  • 教育
  • 后端

【免费下载链接】JPlag

State-of-the-Art Source Code Plagiarism & Collusion Detection. Check for plagiarism in a set of programs.

项目地址:https://gitcode.com/gh_mirrors/jp/JPlag
点击查看免费下载

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询