摘要:JPlag是一个Web服务,可以在给定的集合中找到类似的程序对的程序。它在实践中被成功地用于检测学生Java程序提交中的剽窃行为。能支持的语言除了java之外,还有C、C++和Scheme。我们描述Jpalg架构和jplag的比较算法,这个算法是基于名为“Greedy String Tiling”的已知算法。那么,这篇论文的贡献主要有三个方面:首先,对JPlag在几个不同的集合上的表现进行评估的Java程序,证明抄袭很难通过JPlag的检查。再一次测试中,在我们的各种基准程序集中,77个剽窃者中有90%以上被可靠检测到,其他人中的大多数都会引起怀疑。运行时间仅为几百秒,用于提交每个100个程序的100个程序。其次,参数研究表明,该方法在配置参数方面相当稳健。第三,我们研究了用于伪装抄袭的种类,频率和成功的种类。
关键词:剽窃,相似性,搜索,令牌,字符串拼贴
类别:GT算法,GT性能,F.2.2。 模式匹配,H.3.3,H.5.2。,
I.5.4。 文本处理,K.3.m.,K.5.1
检测类似的程序
1.所有要比较的程序都被解析(或根据输入进行扫描)
语言)并转换为令牌字符串。
2.将这些标记字符串成对比较以确定相似性
每一对。
将程序转换为令牌字符串的前端过程是JPlag中唯一依赖于语言的过程。 目前存在三种前端实现:Java和Scheme的实现都是实现完整的解析器。 C ++(或C)的第三个前端只包含一个扫描器。
作为一项规则,应该选择令牌,以便表征程序结构的本质(剽窃者难以改变),而不是表面方面。 JPlag忽略空白,注释和标识符的名称。 此外,JPlag在可能的情况下将语义信息放入令牌中,以减少纯粹偶然发生的虚假子串匹配。 例如,在Java中,我们使用Begin Method标记而不是仅仅一个Open Brace标记。 基于解析器的前端在这方面更胜一筹。 有关Java示例,请参见表1。 请参阅第4节以了解标记化方法的基本原理。
我们在第3.4节中的评估表明,JPlag对令牌集的更改非常有效
Greedy-String-Tiling(String A, String B) {
tiles = {};
do {
maxmatch = M;
matches = {};
Forall unmarked tokens Aa in A {
Forall unmarked tokens Bb in B {
j = 0;
while (Aa+j == Bb+j && unmarked(Aa+j) && unmarked(Bb+j))
j + +;
if (j == maxmatch)
matches = matches ⊕ match(a, b, j);
else if (j > maxmatch) {
matches = {match(a, b, j)};
maxmatch = j;
}
}
}
Forall match(a, b, maxmatch) ∈ matches {
For j = 0 ...(maxmatch − 1) {
mark(Aa+j);
mark(Bb+j );
}
tiles = tiles ∪ match(a, b, maxmatch);
}
} while (maxmatch > M);
return tiles;
}
表2:“贪婪字符串拼贴”算法[Wise,1993]。 第12行中的⊕运算符在匹配集合中添加匹配项,当且仅当它与集合中已有的匹配项之一不重叠时。 三重匹配(a,b,l)表示A和B的相同子串之间的关联,分别从位置Aa和Bb开始,长度为1。
用于比较两个标记字符串的算法本质上是“贪婪字符串拼贴”[Wise,1993]。 比较两个字符串A和B时,目标是找到一组具有以下属性的连续子字符串:每个子字符串都出现在A和B中,尽可能长并且不涵盖已由其他某个其他字符覆盖的标记子。 为了避免虚假匹配,强制执行最小匹配长度M.
最小匹配长度M被强制执行。“贪婪字符串拼贴”是一种启发式算法,因为保证找到的一组子字符串的最大值会使搜索过于昂贵。 这里是粗略的草图(参见表2的伪代码)。 该算法重复以下两个步骤:
步骤1(第5-18行):搜索两个字符串以查找最大的连续匹配。 从概念上讲,这是通过3个嵌套循环完成的:第一个循环遍历A中的所有标记,第二个将当前标记与B中的每个标记进行比较。如果它们相同,则最内部循环搜索Prechelt L.,Malpohl G.,Philippsen M .: Finding Plagiarisms ... 1021比赛结束。 这些嵌套循环收集所有最长的公共子串集合
步骤2(第19-25行):标记步骤1中发现的所有最大长度的非重叠匹配。这意味着它们的所有标记都被标记,因此可能不会用于后续迭代步骤1中的进一步匹配。 在Wise的术语中,通过标记所有的标记,匹配成为一个贴图。
重复这两个步骤直到找不到进一步的匹配。 由于在每一步中标记了更多的标记,所以算法总是终止。 它返回我们需要计算相似性度量的瓦片列表。 这项措施应该反映原始程序中由比赛覆盖的部分代币。 我们将其定义为sim(A,B)= 2·coverage(tiles)/(| A | + | B |)其中coverage(tiles)=? 匹配(a,b,长度)∈tiles长度。
这种启发式算法的运行时复杂度仍然相当高。 在最坏的情况下,所有三个嵌套循环都会执行到最大限度。 尽管在后面的迭代中匹配长度递减,但如果在每次迭代中只标记一个最短的可设想的匹配,则这可能导致与Θ((| A | + | B |)3)一样大的步骤, 最后涵盖了字符串[Wise,1993; Prechelt等,2000]。 在最好的情况下,根本不存在来自A的单个令牌,并且搜索需要Θ((| A | + | B |)2)个步骤。
2.2.3 Wise和JPlag的运行时间优化
尽管最坏情况下的复杂度不能降低,但通过应用Karp-Rabin模式匹配算法[Karp and Rabin,1987]的想法,实际情况下的平均复杂度可以提高到几乎Θ(| A | + | B |。
通过使用哈希函数来处理更长的字符串(“文本”T)。 为此,所有长度为| P |的子串的哈希值 在T中计算。 这可以通过使用能够根据h(Tt-1Tt ... Tt + | P | -1)的值计算h(TtTt + 1..Tt + | P | -1)的值的散列函数h以线性时间完成。 -2),Tt-1和Tt + | P | -1。 然后将所有散列值与P的值进行比较。如果两个值相同,则进行字符对比以验证T中P的出现已被找到。 这种算法在实践中的复杂性几乎是线性的。
我们修改Wise的贪婪字符串拼贴以下列方式应用Karp-Rabin匹配的基本思想:
1.对于时间Θ(| A | + | B |)中的所有长度为s的子串计算散列值。 JPlag使用s = M,Wise的算法如下所述调整s。
2.然后将来自A的每个散列值与来自B的每个散列值进行比较。如果两个值相同,则通过用令牌比较子串标记来验证匹配。 然后该算法试图将匹配尽可能地扩展到散列函数覆盖的范围之外。
3.使用散列表来定位来自B的具有与来自A的给定子字符串相同的散列值的子字符串。
这个算法的最坏情况下的复杂度仍然是Θ((| A | + | B |)3),因为所有的子字符串都可能必须通过令牌比较令牌,但实际上复杂度远低于Θ((| A | + | B |)2)通常被观察到。
JPlag预先执行所有散列计算(两个字符串的散列值加上字符串B的散列表),即在平铺过程之前仅进行一次散列计算。 当找到匹配并且标记了一个贴图时,静态哈希表中的相应条目不再适用于后续迭代。 由于JPlag离开表中的受影响条目,所以验证子步骤(上面的编号2)变得稍微复杂一些。
相比之下,Wise会在每次迭代中重新计算所有散列值和散列表,以便找到的任何匹配都是有效的。
在这两种方法中,我们发现静态预计算加上稍微更昂贵的有效性检查对于JPlag的默认参数和典型的标记字符串来说更快。
3 JPlag系统的评估
在本节中,我们将研究JPlag的歧视性能及其对程序结构的敏感性,剽窃频率以及JPlag算法的自由参数。 我们提出一个基于多套真实学生课程加上一些明确制作的剽窃的实证研究。
3.1设置我们的研究
本节介绍了一组程序,用于量化结果的标准以及评估中考虑的自由参数集。
3.1.1使用的原始程序集:简单,坚硬,干净,大
我们在研究中使用了四种不同的程序作为基准。 其中三门是来自第二学期信息学课程的编程练习,第四门是从一门研究生高级程序设计课程,向有经验的学生介绍Java和AWT。 有关概述,请参阅表3,现在忽略最右边的两列
“简单”:最大化流量。 用于计算通过具有容量加权边的有向图的最大流量的算法。 该程序基于一个不包含在此处调查的源中的可重用GraphSearch类。 该程序集在程序长度和结构上显示出相当大的变化性。 平均长度为236个非空的非注释行代码(LOC)。 它包括2个剽窃他人的程序(即4个程序构成2个抄袭对)。 我们通过对所有程序进行仔细的手动比较来确定这个程序集和其他程序集内的抄袭对,即我们应用了人类审查人员可以做的最好的检查。 由于这些程序存在相当大的结构变化,我们认为这个程序为JPlag设置了一个相对简单的任务,因此该程序的名称为Simple。
“硬”:乘法排列。 将两个置换表示为置换矩阵,由整数数组实现,指示每行的1的位置。 这是一个非常短的程序(平均长度43 LOC),具有相当固定的结构。 我们可能期望即使是独立编写的程序看起来也非常相似。 因此,这个程序集对于JPlag来说是一个非常严格的测试,因此将被命名为Hard。 在节目集中有12个节目形成6个抄袭对。
e命名为Hard。 在节目集中有12个节目形成6个抄袭对。 “清洁”:k-均值。 一维k均值聚类程序,使用绝对距离作为距离函数,并在数据范围内以等距方式进行初始化。 平均节目长度是118 LOC。 这个程序集根本不包含任何剽窃行为,因此将其命名为Clean。
“大”:跳箱。 一个简单的图形游戏,玩家必须将鼠标移动到屏幕上跳转的方块。 这套节目平均时间最长,也是节目设计中最大的节目; 平均节目长度是263 LOC。 在节目集中有4个抄袭对。 它还包含另外两个具有很多相似性的对,但我们不认为它们是实际的剽窃。 其中之一,这两个程序员显然在早期阶段一起工作,但后来独立完成了他们的程序。 另一方面,这两个程序共享一个共同的分数作为基础,并从早期的AWT编程练习中获得。
3.1.2人工程序集:Hard.P,Large.P等
为了更仔细地调查JPlag的行为,我们的程序集中的实际剽窃数量不足。 因此,我们通过在网站上公开发布“寻求剽窃”并通过电子邮件收集意见书来收集进一步的剽窃。 回答我们的电话的学生和其他程序员从我们的网页下载了一个源程序,对其进行了修改并将其发回。 他们被告知要像一个剽窃并试图欺骗剽窃检测计划的学生行事,特别是不要花费过多的时间。 中间80%的剽窃者最终使用了7到40分钟的自我报告时间。
对于发布的12个原始程序(从硬件程序集6和大型程序集6中随机选择)中的每一个,我们因此收集了14个剽窃版本,导致多达105个额外的抄袭对。基于这些额外的剽窃,我们为JPlag的评估形成了额外的程序集(请参阅表3):
Hard.all基于Hard和其他一系列剽窃行为的结合
收集它。
Hard.P基于Hard.all,但仅包含所有这些程序
也存在抄袭(原创或收集)。在这个程序集中,一个很大的
所有节目对中的一小部分是抄袭对。
Large.all和Large.P是基于Large和
为此收集了更多的剽窃品。
请注意,为了简洁,Hard.all和Large.all将在3.2和3.3节中分别讨论,但包含在3.4和3.5节中,其中我们还使用了Hard.all中的两个较高抄袭内容的较小子集和另外两个来自Large.all
3.1.3评估标准,定义
JPlag可以被视为一个具有固定查询的信息检索系统:给定一组程序对,检索所有那些是剽窃的对,但没有其他的。 对于这样的操作模式,我们只需要通过应用截止阈值将每对产生的相似度值转化为是/否决策:具有高于阈值的相似度的所有对将被认为是抄袭对。
为了表征JPlag输出的正确性,我们可以使用通用信息检索质量度量“精度”(P)和“回忆”(R)。 这些措施的定义如下。 假设我们有一组n个节目,其中p = n·(n-1)/ 2对,这些对中的g是抄袭对,即一个节目从另一个抄袭或者两个抄袭都是(直接或间接)抄袭 一些共同的祖先也是程序集的一部分。
现在假设JPlag返回f个标记为剽窃对的程序对。 如果这些对中的t是真正的抄袭对,而其他的f - t不是,那么我们将精确度和回忆定义为P:= 100·t / f和R:= 100·t / g,即精度是 实际抄袭对和召回标记对的百分比是实际标记的所有抄袭对的百分比。
此外,我们将100·g / p定义为剽窃内容,即所有抄袭对的分数(见表3的“%”栏)。
3.1.4其他参数在研究中有所不同
如第2.2节所述,JPlag算法中有两个自由参数:最小匹配长度M和使用的令牌集。 这两种情况在我们的研究中也有所不同。
默认标记集“normal”包含描述主要程序结构(变量声明,类/方法的开始/结束等)和控制流(返回,中断,继续,抛出,if,while,等等的开始/结束)的标记。 ),加上2个令牌(分配)和方法调用(应用)。 它忽略了表达式(操作符,操作数)内的所有结构以及方法的标识等。除了默认的标记集,我们还使用了一个相当小的标记集(称为“struc”),其中只包含与控制流和程序块结构相关的标记 但不包括变量声明)。 此外,我们使用了包含所有可能令牌的最大令牌集(称为“满”)。
我们使用了最小匹配长度3,4,5,7,9,11,14,17,20,25,30和40. 9是默认值。
3.2基本结果
为简要总结JPlag的性能,我们将首先报告使用标准参数(最小匹配长度9,正常标记集)和50%截止阈值时我们每个数据集的精度和召回率的值。 摘要显示在表3最右边的两列中。
正如我们所看到的,JPlag的性能对于简单,清洁和大型来说是完美的; 对于我们四个真实世界的数据集中有三个我们不但无一例外地获得所有的抄袭对,而且输出也完全没有非抄袭对
对于困难的数据集Hard,我们错过了6个抄袭对中的2个(R = 0.67),并错误地检索了7个非抄袭对以及4个正确的抄袭对。 不完全召回当然可以通过降低截止门限来改善,但这也会导致更多不正确的输出。 这个折衷将在3.3节中分析。
具有高剽窃密度的人造数据集的结果也非常好:JPlag检测到两种病例(Large.P和Large.all)中所有剽窃对的92%,另外两种(Hard.P 和Hard.all),除一个案例(Hard.all)外,结果完全没有虚假输出。
鉴于我们在第4节中介绍的程序设计者尝试的各种伪装尝试,这些结果相当令人印象深刻。
3.3相似度值的分布; 精确/回忆权衡
理想情况下,JPlag会报告任何非抄袭对的0%和任何抄袭对的100%的相似度。 但实际上,这种区别几乎没有那么清楚。 因此,为了判断先前显示的结果的稳健性,我们现在回顾JPlag为抄袭对产生的相似性值的分布,并将其与非抄袭对的分布进行比较。 如果两个分布重叠,则一个或多个节目对将被错误地判断
简单:“最大化流量”程序。 我们的第一个例子是程序集Simple。 结果显示在图2中。该程序集导致总共378个程序对,其中只有2个是实际的抄袭对。 图的上半部分显示了376个非抄袭对的相似度值分布,即2个抄袭对的底部。
JPlag在相当广泛的截止门槛范围内将剽窃与其他节目完美区分开来。 图3的左侧部分显示了当我们逐渐增加截止阈值时回想如何改变:只有在相当高的截止阈值时,我们才会错过任何剽窃行为。 精确度和召回率之间的折中结果显示在图的右侧。 我们总是至少有完美的精确度或完美的召回率,对于适当选择的截止阈值,我们甚至可以同时得到两者,即曲线到达曲线的右上角(100/100,理想点)。
总结起来,JPlag的行为对于这个程序集是完美的。
硬:“多重排列”计划。 请记住,该算法的简单性表明了一个有点规范的程序结构。 因此我们可以期望这个程序对JPlag来说是一个非常困难的测试。
硬:“多重排列”计划。请记住,该算法的简单性表明了一个有点规范的程序结构。因此我们可以期望这个程序对JPlag来说是一个非常困难的测试。图4确实表明Hard程序集的行为不太理想:相似性值分布有相当多的重叠。让我们首先考虑一下,注意在绝对数量中,剽窃对的分布几乎可以忽略不计,因为剽窃的内容只有0.3%。尽管存在困难,但非剽窃的相似度值的分布几乎与Simple相同。另一方面,除了一个例外,Hard中的6个抄袭对仅表现出中等相似度,即使对于人类观察者也是如此。看看源程序,我们得到了这样的印象,即学生们大部分都在一起工作,但无论如何都可能独立完成他们的程序。但是,鉴于节目规模较小,无法确定。所以人们可以说,这些根本不是剽窃。但是为了得到一种最坏的情况分析,让我们假设这6对确实都是真正的剽窃。然后,精度/回忆权衡是远远不理想的(参见图5底部),但中等截断阈值仍然导致合理的妥协,如前所述回收67例。
3.4令牌集合和最小匹配长度的影响
到目前为止所有的数据都使用了JPlag的默认参数:“正常”标记集和标准最小匹配长度为9.但是,我们也感兴趣的是JPlag如何抵抗这些参数的变化以及这些变化如何与 程序集进行分析。 因此,下面介绍性能测量,我们将研究JPlag的性能如何针对不同的最小匹配长度,截止阈值,令牌集和程序集进行更改。
我们通过精确度和召回率的加权和来衡量JPlag的总抄袭鉴别性能。 我们选择3的回忆(相对于精确度)的相对权重,因为惩罚假阴性(非检测到的抄袭)远远大于误报,这仅仅为人类用户的最终判断提供了更多的工作。 因此,绩效指标变为P + 3R。 确切的值3并不重要,重量为2或4的结果将是相似的。
我们将在这里省略分析的大量细节,仅提供结果; 更多的细节可以在[Prechelt et al。,2000]中找到。 当分析使用“正常”标记集合和截止阈值50时最小匹配长度M与P + 3R量度之间的相关性时,我们做出以下观察:
1. M的最佳值可能取决于所选择的截止阈值。 这并不奇怪,因为较小的M会导致通常较高的相似性值。
2.因此,增加M的总体表现趋势可能是向上,向下或山形。因此,任何固定的M都必须被认为是妥协。
4.低的最小匹配长度往往会产生虚假匹配,从而降低精度。
5.高的最小匹配长度倾向于错过更多的抄袭区域,从而减少回忆,特别是对于抄袭内容更高的人工程序集。
6.总体而言,除非所选择的价值远离最优价值,否则性能损失很小。
我们得出这样的结论:JPlag对M的适度非最佳选择是强有力的。
使用小型“struc”标记集的相同分析发现,由于标记字符串较短,较大的M值不太可取。否则,结果与默认令牌集的结果非常相似。
最后,对于最大可能的令牌集,称为“完整”,我们发现由于较长的令牌字符串,更大的M可能更容易被容忍,但适度的值仍然倾向于优越。否则,结果再次与缺省和缩减的标记集相似。
我们得出这样的结论:JPlag对于不同的令牌集合的选择是高度可靠的。这很有用,因为它表明JPlag对于许多其他编程语言也可以类似地工作,如果它们允许类似结构的标记集合。
此外,我们得出结论:默认的标记集合和默认的最小匹配长度9是JPlag针对各种程序集的合理和可靠的参数选择。
3.5截止标准的影响
通常,我们会看JPlag找到的最相似的一对节目,并分别为每一对节目决定它是否是抄袭对。 人们将以这种方式朝着较低的相似度值前进,直到人们确信发现了所有的剽窃行为。
然而,在某些情况下,基于相似性阈值的全自动决策是优选的:具有该相似性或更高相似性的对将被视为剽窃,而具有较低相似性的对将被视为独立。 我们称这样一个标准为截止标准。 截止标准接收相似值的向量s作为输入,并且如上所述计算截止阈值T.
为了在全自动模式下评估JPlag,我们使用了许多不同的截止标准。 它们中的大多数适应于正在调查的程序集的相似性分布。 从上面的讨论中我们已经知道在30到60范围内的截止阈值通常会产生最好的结果。 但是,目前还不清楚是否存在几乎总是最佳的固定阈值。 考虑当前相似度分布的适应性标准可能会更成功。 这些是我们探索的截止标准:
脱粒。 截断标准的threshT系列使用最简单的方法:它根本不查看输入矢量,而是应用固定的截止门限来做出决定。 我们已经使用了从30%到95%的各种阈值T,从而导致阈值thresh30到thresh95。
mplus。 mplusD家族的截止标准有点适应于系统中较高或较低的s相似度值。 它返回向量中相似值的中值(50%分位数,q50)加上距中值到100的距离的D百分比:T = q50(s)+ D / 100 *(100-q50(s))。 与固定阈值相比,这些标准可以适应不同的“基本相似性”; 他们认为中位数相似度代表了一个典型的非抄袭对,因为远远不到所有配对的一半都是抄袭对。 我们使用了mplus25,mplus50和mplus75。
qplus。 qplusD家族相当于mplusD家族,除了偏移量的起点是第三个四分位数:T = q75(s)+ D / 100 *(100-q75(s))。 这个想法是q75可能代表一个更大的意外相似的情况,所以即使是小的D值也不应该导致误报。 我们使用了qplus25,qplus50和qplus75。
K均值。 kmeans截止标准使用一维k-均值聚类将矢量分成两类。 具有较高相似性值的类将被视为抄袭对。
avginf。 avginf家族的截断标准考虑具有大致相同的相似度值的对的信息内容。这里的想法是,剽窃行为应该是罕见的,因此表明剽窃行为的相似性值的范围必须具有很高的信息含量(在信息理论意义上)。因此,我们选择阈值T作为具有此相似度或更高相似度的对的平均信息内容Cv≥T至少比整体平均值C高百分之P的最小阈值。为此,avginf标准将相似度值组合成重叠宽度为5%的类:给定所有对的相似度值的向量s,令Sv为s的值为v ... v + 5的相似度集合。然后,每个这样的对的信息内容为Cv:= - log2(| Sv | / | s |)和空类被定义为没有信息内容,即,如果Sv = C,则Cv:= 0。基于这些值Cv,可以确定阈值。我们使用了avginf050,avginf100,avginf200和avginf400。
图9比较了截止标准的性能分布,基于性能测量P + 3R。 对于原始程序集,最好的标准是thresh50,thresh60,qplus50和mplus50。 他们对于P + 3R的中位数都是400(即至少有一半的结果是完美的),平均值约为330,第一个四分位数约为280.对于人工程序集,没有一个标准能够达到。 最好的是thresh40,qplus25和mplus25。 这是一个好消息:出于实际目的(剽窃数量通常很小),像阈值这样简单的规则似乎会产生最好的结果。
3.6 JPlag和MOSS的比较
我们还通过MOSS [Aiken,1998]运行我们的基准程序集,对结果进行后处理,使用与JPlag相同的相似性度量,然后计算各种截止阈值的精度和召回率。 结果如表4所示。
表中的左边部分假设了理想化的截止标准,在每种情况下都选择最佳阈值。 请注意,MOSS通常会返回比JPlag相当低的相似值。 正如我们所看到的,就简单,清洁和大型(或其变体)而言,MOSS的性能与JPlag基本相同。 唯一的区别是MOSS必须比JPlag更强烈地依赖于截断阈值的可变性。
然而,对于Hard程序集的变体,JPlag显然是优越的:对于Hard.P和Hard.all,JPlag以更好的精度实现相同的召回。 请注意,对于Hard.all,当t = 30时,JPlag达到P = 46,R = 88,但是这具有较低的P + 3R。 而且,对于原始程序集Hard,MOSS无法找到三分之一以上的抄袭对,即使使用最低截止阈值10时也是如此。
如果我们为每个系统选择一个固定的阈值,就会出现类似的情况,如表格的右半部分所示:我们将JPlag的相似度降低了50%,MOSS降低了30%。 这些阈值是合理的,往往会平衡精确度和回忆。 结果表明,JPlag的精确度或召回率比MOSS差,但是其中很多情况下,MOSS明显比JPlag更差 - 特别是对于hard程序集。 如前所述,没有关于MOSS算法的书面描述。 因此,我们无法解释结果.
如果我们为每个系统选择一个固定的阈值,就会出现类似的情况,如表格的右半部分所示:我们将JPlag的相似度降低了50%,MOSS降低了30%。 这些阈值是合理的,往往会平衡精确度和回忆。 结果表明,JPlag的精确度或召回率比MOSS差,但是其中很多情况下,MOSS明显比JPlag更差 - 特别是对于硬件程序集。 如前所述,没有关于MOSS算法的书面描述。 因此,我们无法解释结果
3.7运行时效率
JPlag的运行时间随着程序集中程序数量的增加而呈二次曲线增长,并且与程序的大小略微超线性。
但是,由此产生的运行时间通常很小。 我们最大的节目集Large.all包含99个节目,平均约250个LOC。 JPlag用于阅读和解析这些程序以及执行所有配对比较的总运行时间是Pentium III上的挂钟时间大约6秒
4成功和不成功的剽窃攻击
本节分析伪装技术和我们在用于评估的计划中看到的攻击类型。 任何单一的攻击事件最多都会导致我们称之为JPlag的局部混乱。 考虑应用伪装技术所需的最短代码段。 如果JPlag没有确定原始代码段和攻击结果之间的任何相似性,我们说JPlag在本地是混乱的。 局部混淆关键取决于所使用的令牌集和最小匹配长度。
例如,如果将单行代码(或单个令牌)插入到最小匹配长度的代码段中,可能会导致本地混淆。 插入后,JPlag通常不会再找到具有足够的令牌来匹配的相应代码段。 再举一个例子,如果最小匹配长度的代码段被分成两部分然后交换,JPlag也被欺骗了。
除非程序非常短,否则在剽窃逃避检测之前需要进行一些局部混淆(取决于截止标准)。 完美的攻击将需要在原始代码的每个片段中以最小匹配长度来实现局部混淆。 JPlag将会成功,除非剽窃者既富有创造性,足以发现足够的伪装技术,可以在整个给定的程序中应用(大多数技术只适用于某些情况),然后渴望足够频繁地应用它们。
4.1无效攻击
本节讨论的攻击根本不起作用,因为它们不会对JPlag生成并考虑的令牌列表进行任何修改。 几乎每个剽窃者都至少使用过这些徒劳的伪装技术之一。
- 通过改变换行符,空格和TAB来修改代码格式[48 times3]
- 插入,修改或删除评论[30次]
- 程序输出或其格式的修改[33次,2次成功]在2个程序中,修改后的输出格式导致额外的方法调用。
- 更改变量,方法或类的名称[44次]
- 分割或合并变量声明列表[6次]
- 修改器,如私人,最终等[6次]
- 常数值的修改[3次]
- 有些学生根本没有试图伪装他们的作弊,并提交了相同的副本。 如果提交次数过大,无法手动比较所有数据并且没有自动系统,则可能会发生这种“攻击”。[4次]
正如我们从这个列表中看到的,JPlag对格式化,评论,文字和名称的完全无知是其成功的关键。 为默认令牌集选择的粗粒度(例如,忽略修饰符)也是有用的。
4.2对粒度敏感的攻击
粗略地说,Java类由方法和变量的声明组成。 声明的排序是无关紧要的。 因此,一个有前途的攻击行是重新排序实现中的声明。 但是,如果重新排序的代码段长于最小匹配长度,则JPlag信号将阻止移动,而不是在本地混淆。 因此,这种攻击的成功与否关键取决于其应用的粒度。
剽窃者经常使用这种类型的重新排序[55次]。 但是,他们中只有大约15%实际上混淆了JPlag。
- 在变量声明块内重新排序[25次,6次成功]
- 变量和方法声明的全局重新排序[30次,3次成功]
4.3局部混淆攻击
以下类型的攻击通常在本地成功,至少使用默认的令牌集。 在134次这些袭击中,只有12次不成功。 然而,只有极少数的剽窃者在一个特定的程序中实现了如此之多的局部混淆以逃避被发现。
-
修改控制结构[35次,全部成功]
•用while循环代替for循环,反之亦然[8次] •通过用主迭代变量[3次]表达它们来消除辅助指标变量 •用无限循环代替常规循环[1次] •用一系列if语句替换switch-statement [6次] •为switch语句的每个case添加冗余break-statements [1次]
-
临时变量和子表达式[28次,全部成功]
•将子表达式转换为新的辅助变量[16次] •反之亦然[7次] •通过赋值列表替换数组初始值设定项[2次] •从声明中移除初始化[2次] •用默认值[1次]显式初始化
-
内联和重构[20次,16次成功]
•内联小方法[5次] •将现有方法的部分重构为新方法[11次]
-
修改范围[9次,全部成功]
•将合理性测试或试验块的开始/结束移向方法的开始和/或结束[3次] •将临时变量移动到周围的块中[3次] •在内部块中添加临时变量的冗余声明[1次] •如果只有一个实例存在,则用实例变量替换类变量[2次]
- 重新排序基本块内的独立语句[8次,6次成功]
- 利用数学身份[5次,2次成功]
- 自愿引入程序缺陷[5次,3次成功] 3名剽窃者删除代码或添加其他语句。 两次不成功的尝试涉及修改后的常量。
-
修改数据结构[6次,5次成功]
•用char数组替换字符串[1次] •用两个单独的int [1次]替换int [2] •用数组替换几个相同类型的变量[3次] •提升整数[1次,不成功]
- 冗余[15次,14次成功]
•添加或删除未使用的代码[7次]
•使用完全限定的软件包名称[2次]
•插入虚拟方法的调用[1次]
•导入额外的软件包和类[1次]
•将呼叫插入到Thread.yield()[1次]
•在void方法结束时插入return [1次]
•复制右侧无副作用的赋值语句[1次]
•访问实例变量时添加或删除此事件[1次,不成功]作为对策措施,JPlag的默认令牌集将忽略此设置。
- 冗余[15次,14次成功]
-
代码结构重新设计[3次,全部成功]
•将状态改变方法转换为单独的新类[2次] •创建并返回一个新对象,而不是改变现有状态[1次]
在这个列表中,最后三种类型的攻击是非常聪明的,即使对于读者来说也很难检测:修改数据结构,添加或删除冗余,或重新设计代码结构。
5 Summary and conclusions
我们使用4套真实Java程序和另外几套包含额外剽窃的Java程序集对JPlag进行的实证评估可总结如下:
- 对于清楚剽窃的节目,即完全采用节目并进行修改以隐藏起源的节目,JPlag的结果几乎完美 - 即使节目长度少于100行也是如此。
- 即使只有部分剽窃节目,就像我们的硬盘节目集合一样,JPlag将指出相似之处,并且通常可以很好地区分它们与意外相似之处。
- 剽窃者从真实程序集中选择的伪装(如果有的话)对JPlag完全没用。
- 即使是知情剽窃者选择的攻击在所有案例中的成功率都不到10%。 这些人知道他们必须欺骗一个程序,并没有其他目标(除了只用一点时间来完成)。
- 对JPlag检测的成功攻击需要很多工作,或者会产生一个对任何人员检查员来说都很荒谬的程序结构。
- JPlag非常适合对其两个自由参数,令牌集和最小匹配长度的非最优选择。
- 给定JPlag计算的相似度值,一个固定的截止阈值就足以作为一种判别标准,它将剽窃的程序对与非剽窃的程序对分离,并且具有接近最佳的回忆率,但仍具有良好的精确度。
我们并不确切知道这些结果转移到其他情况的程度。 它们可能适用于C和C ++的较小程度,因为这些语言目前尚未解析,但仅由JPlag扫描。 对于不同结构的(或更大的)程序,如果这些攻击使某些攻击更加有效,或者对于不同的攻击者(如果那些攻击者仍然使用不同的攻击)
但是,总体而言,如果JPlag的有效性远远低于证明的效果,我们会感到惊讶。 看起来基于标记的字符串相似性方法对寻找剽窃非常有效,至少如果令牌字符串忽略了足够的细节。 对于用Java,C,C ++和Scheme编写的程序,JPlag是一种易于使用的实现方法.
原则上,JPlag也可以用于其他目的。 如果一家软件公司怀疑竞争对手窃取了部分源代码,那么在法院命令或双方协议后JPlag可以比较两个有问题的大型程序系统并指出类似的区域。 JPlag已经成功应用于这种情况下(其各方希望保持匿名).
通过指出不同的区域,而不是那些相似的区域,JPlag可以用作程序差异引擎(如Unix diff)。与基于字符的差异相比,JPlag差异忽略了很多细节,因此产生了更小的(尽管 也不太精确)的差异,这在某些情况下可能有用。
文献名称: Lutz Prechelt , Guido Malpohl , Michael Philippsen. Finding Plagiarisms among a Set of Programs with JPlag[J]
前端VUe代码的github地址
后端地址SSM