Glengamoi (Forum) · AspHeute · .NET Heute (RSS-Suche) · AspxFiles (Wiki) · .NET Blogs

Plagiat oder nicht?

Geschrieben von: Christoph Wille
Kategorie: C#

This printed page brought to you by AlphaSierraPapa

Ob das Aussehen einer Website, die Funktionalität eines Programmes, überall steht Sourcecode dahinter - von HTML bis C#. Stellen Sie sich vor, es kommt Ihnen zufällig ein Sourcecode unter, der Sie ganz stark an Ihren eigenen aus einem Projekt erinnert... so ist es uns, dem SharpDevelop Team, passiert. Die Frage die sich nun stellt, wie weist man nach daß geklaut wurde?

Ein Weg ist, mühsam jedes Sourcecodefile händisch zu vergleichen, das verbietet sich allerdings ab einer gewissen Zahl an Sourcecodezeilen - die Stellen des Plagiarismus wollen eingegrenzt werden, bevor man sich die Mühe des händischen Vergleichens macht. Ein sehr robuster Weg um Plagiarismus aufzudecken ist die Verwendung des Longest Common Substring Algorithmus, der für die Erkennung von Gleichheiten in DNA Sequenzen verwendet wird. Dieser Algorithmus ist nicht ganz ohne Seitenhieb auf unser Problem: wir lernen, was im Laufe der Evolution an Genen erhalten geblieben ist.

Das LCS Tool

Im Download für diesen Artikel mit dabei ist das LCS Tool als Kommandozeilenversion. Als Wort der Warnung: die Pfade für die zu vergleichenden Dateien sind hardcodiert, aber da der Sourcecode mitgeliefert wird, ist die Anpassung eine Kleinigkeit.

Die gesamte LCS Funktionalität ist in einer passend LCS genannten Klasse verpackt:

namespace Algorithms
{
  public class LCS
  {
    private string ReadFile(string fileName);
    private int Compute(ref string src, ref string dst, int srcOffset, int dstOffset);
    public void Compare(string srcFile, string dstFile);
    public void CompareSet(string[] src, string[] dst);
  }
}

Bevor wir uns Sourcecode für die Klasse selbst ansehen, wie verwendet man die Klasse? Hier ein Beispiel:

string[] sourceFiles = new string[] { @"AssemblyInfo1.cs" };
string[] destinationFiles = new string[] { @"AssemblyInfo2.cs" };

LCS lcs = new LCS();
lcs.CompareSet(sourceFiles, destinationFiles);

Die Methode CompareSet kann mehrere Dateien vergleichen, die per Array übergeben werden. Intern ruft sie die Methode Compare für jedes Set von Dateien (Source und potentielles Plagiat) auf - diese liest dann die Dateien aus (ReadFile löscht zusätzlich die Whitespaces) und errechnet den LCS Wert (Compute): den längsten gefundenen Substring, und daraus die Wahrscheinlichkeit, daß es sich tatsächlich um ein Plagiat handelt:

    public void Compare(string srcFile, string dstFile)
    {
      string src = ReadFile(srcFile);
      string dst = ReadFile(dstFile);
    
      table = new int[src.Length, dst.Length];
      for (int i =0; i < src.Length; ++i) 
      {
        for (int j =0; j < dst.Length; ++j) 
        {
          table[i, j] = -1;
        }
      }
      Console.WriteLine("source: {0}, destination: {1}", Path.GetFileName(srcFile), Path.GetFileName(dstFile));
      Console.WriteLine("source length: {0}, destination length: {1}", src.Length, dst.Length);
      int lcs = Compute(ref src, ref dst, 0, 0);
      Console.WriteLine("LCS match: {0}/{1}%", lcs, (lcs * 100) / dst.Length);
    }

In unserem Drill-Down in die Berechnung des LCS fehlt uns jetzt nur noch die Compute Methode:

    private int Compute(ref string src, ref string dst, int srcOffset, int dstOffset)
    {
      if (srcOffset >= src.Length || dstOffset >= dst.Length) 
      {
        return 0;
      }
    
      if (table[srcOffset, dstOffset] == -1) 
      {
        if (Char.ToUpper(src[srcOffset]) == Char.ToUpper(dst[dstOffset])) 
        {
          table[srcOffset, dstOffset] = 1 + Compute(ref src, ref dst, srcOffset + 1, dstOffset + 1);
        } 
        else 
        {
          table[srcOffset, dstOffset] =  Math.Max(Compute(ref src, ref dst, srcOffset, dstOffset + 1), 
            Compute(ref src, ref dst, srcOffset + 1, dstOffset));
        }
      }
    
      return table[srcOffset, dstOffset];
    }

Es wird die private Variable table verwendet, die zuvor in der Compare Methode mit -1en gefüllt wurde. Was wird hier berechnet? Der längste zusammenhängende Substring. Und das passiert indem man die beiden Dateien (mittlerweile sind es Strings) Zeichen für Zeichen durchgeht, und mit Überspringen von Abweichungen den längsten gemeinsamen String herausrechnet (mathematische Details in Longest Common Substring).

Damit hat man dann eine Wahrscheinlichkeit an der Hand, mit der man eine sehr gute Aussage treffen kann, ob ein Sourcecode von einem anderen "inspiriert" wurde (die berühmte Blaupause).

Anwendung

Um zu demonstrieren wie das Programm arbeitet, habe ich eine von VS.NET generierte AssemblyInfo.cs Datei genommen, und diese Datei zweimal kopiert. Eine Datei (AssemblyInfo1.cs) wurde nicht verändert, und in der zweiten (AssemblyInfo2.cs) nur das AssemblyCompany Attribut mit einem Wert befüllt:

C:\lcs\bin\Debug>lcs
source: AssemblyInfo1.cs, destination: AssemblyInfo2.cs
source length: 1908, destination length: 1923
LCS match: 1908/99%

Tja, ich würde sagen da hat wer mein AssemblyInfo1.cs genommen und nur einen String eingefügt. Plagiat.

Als nächstes habe ich aus AssemblyInfo2.cs alle Kommentare gelöscht, und das Tool nochmals gestartet:

C:\lcs\bin\Debug>lcs
source: AssemblyInfo1.cs, destination: AssemblyInfo2.cs
source length: 1908, destination length: 457
LCS match: 442/96%

So ein Pech aber auch, eine einfache Kommentarkosmetik wird immer noch durch das Tool entlarvt.

Ich erspare mir jetzt weitere Demonstrationen, Sie sehen ja schon in welche Richtung das läuft - man hat mit LCS eine sehr gute Möglichkeit, Plagiatskandidaten zu erforschen. Aus unserer Erfahrung sollte man sich alles ab 60% Wahrscheinlichkeit anschauen, allerdings Vorsicht bei Dateien die kaum mehr als eine Zeile Code pro Methode beinhalten: hier läuft man Gefahr, Implementierungen von Interfaces die einfach nicht anders bewerkstelligt werden können als Plagiate mißzuinterpretieren. Gleiches gilt für durch Wizards autogenerierte Klassenstubs und dergleichen.

Hat man Plagiatskandidaten gefunden, muß man den Code aber dennoch durchsehen - und da findet man schon die eine oder andere Eigenheit des Programmierers, die der Kopierer "miteingeschleppt" hat. Bei uns waren das Bugs spezifisch zum Framework das bei uns rund um den geklauten Code verwendet wurde, etliche Eigenheiten des Programmierers und andere Kleinigkeiten.

Schlußbemerkung

Hat man den Code analysiert kommt dann der eigentlich nervenaufreibende Teil: denjenigen zu kontaktieren, der den Code geklaut hat, es aber sicher nicht sofort zugeben wird. Hat man aber eine solide Analyse (und LCS mit seinen harten Wahrscheinlichkeiten tut mehr "weh" als die Fakten die man durch händische Nachanalyse gefunden hat), dann bringt man den Inspirierten auch dazu, den Code zu ändern.

This printed page brought to you by AlphaSierraPapa

Download des Codes

Klicken Sie hier, um den Download zu starten.
http://www.aspheute.com/code/20030910.zip

Links zu anderen Sites

A matter of inspiration
http://www.icsharpcode.net/pub/relations/amatterofinspiration.aspx
Longest Common Substring Algorithmus
http://www.cs.sunysb.edu/~algorith/files/longest-common-substring.shtml
SharpDevelop
http://www.icsharpcode.net/opensource/sd/

 

©2000-2006 AspHeute.com
Alle Rechte vorbehalten. Der Inhalt dieser Seiten ist urheberrechtlich geschützt.
Eine Übernahme von Texten (auch nur auszugsweise) oder Graphiken bedarf unserer schriftlichen Zustimmung.