Unterhaltsame Archäologie. Oder PVS-Studio prüft Microsoft Word 1.1a

    Einhornschädel
    Kürzlich hat Microsoft allen Programmierern ein Geschenk gemacht, die sich mit etwas Interessantem befassen möchten. Microsoft hat den Quellcode für MS-DOS 1.1, 2.0 und Word für Windows 1.1a geöffnet. Das MS-DOS-Betriebssystem ist in Assembler geschrieben, und der Analyzer ist darauf nicht anwendbar. Aber Word ist in C geschrieben. Der Quellcode für Word 1.1a ist fast 25 Jahre alt, aber wir haben es irgendwie geschafft, ihn zu überprüfen. Natürlich hat dieser Test keinen praktischen Wert. Nur zum Spaß.

    Wo kann man von der Quelle profitieren?


    Vielleicht interessieren sich viele nicht nur für diesen Artikel, sondern auch dafür, dass Sie die Quellcodes für MS-DOS 1.1, 2.0 und Word für Windows 1.1a herunterladen können. Für diejenigen, die sich selbst für den Quellcode interessieren, sende ich ihn an die Originalquelle.

    Pressemitteilung: Computer History Museum macht historischen MS-DOS- und Word für Windows-Quellcode der Öffentlichkeit zugänglich .

    Überprüfen Sie Word 1.1a


    Abbildung 1. Word für Windows 1.1a.

    Abbildung 1. Word für Windows 1.1a.

    Word für Windows 1.1a wurde 1990 gestartet. Am 25. März 2014 wurde der Code für dieses Produkt der Öffentlichkeit zugänglich gemacht. Word war und ist das Flaggschiff von Microsoft. Ich und viele andere sind daran interessiert, das Innere des Softwareprodukts zu untersuchen, das so viel zum wirtschaftlichen Erfolg von Microsoft beigetragen hat.

    Ich habe beschlossen, den Word 1.1a-Code mit unserem PVS-Studio- Tool zu überprüfen. Dies ist ein statischer Analysator für C / C ++ - Code. Das ist natürlich nicht so einfach. Der Analyzer ist für die Arbeit mit Projekten ausgelegt, die mindestens in Visual Studio 2005 entwickelt wurden. Und jetzt habe ich den Quellcode in C, der mehr als 20 Jahre alt ist. Es kann gesagt werden, dass dies prähistorische Zeiten sind. Zumindest damals gab es keinen Standard für die C-Sprache. Jeder Compiler war für sich. Glücklicherweise enthielt der Quellcode von Word 1.1a keine ungewöhnlichen Momente und die Verwendung einer großen Anzahl von Nicht-Standard-Compiler-Erweiterungen.

    Für die Analyse werden vorverarbeitete Dateien (* .i) benötigt . Mit vorverarbeiteten Dateien können Sie das PVS-Studio Standalone- Tool verwenden. Mit ihm können Sie Analysen durchführen und Diagnosemeldungen untersuchen. Natürlich ist der Analyzer nicht für die Analyse von 16-Bit-Programmen ausgelegt. Diese Analyseergebnisse werden jedoch ausreichen, um die Neugier zu befriedigen. Analysieren Sie das Projekt vor 24 Jahren sorgfältig, es gibt keinen praktischen Sinn.

    Der wichtigste Haken war also, wie man die vorverarbeiteten Dateien erhält. Ich bat meinen Kollegen, in diese Richtung zu zaubern. Er ging die Entscheidung sehr kreativ an. Er führte eine Vorverarbeitung mit GCC 4.8.1 durch. Es ist unwahrscheinlich, dass irgendjemand anders den Quellcode für Word 1.1 verspottet hätte. Verwenden Sie GCC - das mussten Sie sich einfallen lassen. Fantazer.

    Das interessanteste war ziemlich erfolgreich. Es wurde ein kleines Dienstprogramm geschrieben, das die Vorverarbeitung mit GCC 4.8.1 für jede Datei aus dem Verzeichnis startete, in dem sie sich befand. Bei der Anzeige von Fehlern im Zusammenhang mit der Einbeziehung von Header-Dateien wurden den Startparametern -I-Schlüssel mit dem Pfad zu den erforderlichen Dateien hinzugefügt. Einige nicht gefundene Header-Dateien wurden leer erstellt. Alle anderen # include-Erweiterungsprobleme standen im Zusammenhang mit der Einbeziehung von Ressourcen, sodass sie auskommentiert wurden. Während der Vorverarbeitung wurde das WIN-Makro definiert, weil Im Code gibt es eine Verzweigung für WIN und MAC.

    Dann betraten PVS-Studio Standalone und Ihr bescheidener Diener das Geschäft. Ich habe verdächtige Codefragmente ausgeschrieben und bin bereit, sie Ihnen zu zeigen. Aber zuerst noch etwas zum Projekt.

    Verschiedenes in Word 1.1a-Code



    Die komplexesten Funktionen


    Die größte zyklomatische Komplexität der folgenden Funktionen:
    1. CursUpDown - 219;
    2. FIdle - 192;
    3. CmdDrCurs1 - 142.

    #ifdef WIN23


    Als ich den Quellcode durchsah und "#ifdef WIN23" traf, lächelte ich. Und schrieb sogar diesen Ort. Ich dachte, es wäre ein Tippfehler und sollte #ifdef WIN32 geschrieben werden.

    Als ich WIN23 ein zweites Mal sah, bezweifelte ich es. Und dann wurde ihm plötzlich klar, dass ich mir vor 24 Jahren den Quellcode angesehen hatte. WIN23 bedeutet Version von Windows 2.3.

    Harte Zeiten


    Im Code bin ich auf diese interessante Zeile gestoßen.
    Assert((1 > 0) == 1);

    Es scheint unglaublich, dass diese Bedingung möglicherweise nicht erfüllt ist. Wenn es jedoch einen solchen Scheck gibt, gab es Gelegenheit, ihr zu schreiben. In jenen Tagen gab es keinen Standard für Sprache. Nach meinem Verständnis war es eine gute Form zu überprüfen, wie die Arbeit des Compilers die Erwartungen der Programmierer erfüllt.

    Wenn wir K & R als Standard betrachten, ist natürlich theoretisch die Bedingung ((1> 0) == 1) immer erfüllt. Aber K & R war nur ein De-facto-Standard und nichts weiter. Dies ist eine Überprüfung der Eignung des Compilers.

    Validierungsergebnisse


    Lassen Sie uns nun über die verdächtigen Stellen sprechen, die ich im Code gefunden habe. Ich denke, aus diesem Grund lesen Sie diesen Artikel. Fangen wir an.

    Endlose Schleife


    void GetNameElk(elk, stOut)
    ELK elk;
    unsigned char *stOut;
    {
      unsigned char *stElk = &rgchElkNames[mpelkichName[elk]];
      unsigned cch = stElk[0] + 1;
      while (--cch >= 0)
        *stOut++ = *stElk++;
    }

    PVS-Studio Warnung : V547 Ausdruck '- cch> = 0' ist immer wahr. Der Wert für den vorzeichenlosen Typ ist immer> = 0. mergeelx.c 1188

    Der Zyklus „while (--cch> = 0)“ wird niemals angehalten. Die Variable 'cch' ist vom Typ ohne Vorzeichen. Um wie viel diese Variable nicht reduziert wird, bleibt sie immer> = 0.

    Überlauf des Arrays durch Tippfehler


    uns rgwSpare0 [5];
    DumpHeader()
    {
      ....
      printUns ("rgwSpare0[0]   = ", Fib.rgwSpare0[5], 0, 0, fTrue);
      printUns ("rgwSpare0[1]   = ", Fib.rgwSpare0[1], 1, 1, fTrue);
      printUns ("rgwSpare0[2]   = ", Fib.rgwSpare0[2], 0, 0, fTrue);
      printUns ("rgwSpare0[3]   = ", Fib.rgwSpare0[3], 1, 1, fTrue);
      printUns ("rgwSpare0[4]   = ", Fib.rgwSpare0[4], 2, 2, fTrue);
      ....
    }

    PVS-Studio Warnung: V557 Array Overrun ist möglich. Der '5'-Index zeigt über die Array-Grenze hinaus. dnatfile.c 444

    Irgendwie stand in der ersten Zeile: Fib.rgwSpare0 [5]. Das ist nicht richtig. Es gibt nur 5 Elemente im Array, was bedeutet, dass der maximale Index 4 sein sollte. Der Wert '5' ist das Ergebnis eines Tippfehlers. Höchstwahrscheinlich sollte in der ersten Zeile der Nullindex verwendet werden:
    printUns ("rgwSpare0[0]   = ", Fib.rgwSpare0[0], 0, 0, fTrue);

    Nicht initialisierte Variable


    FPrintSummaryInfo(doc, cpFirst, cpLim)
    int doc;
    CP cpFirst, cpLim;
    {
      int fRet = fFalse;
      int pgnFirst = vpgnFirst;
      int pgnLast = vpgnLast;
      int sectFirst = vsectFirst;
      int sectLast = sectLast;
      ....
    }

    PVS-Studio Warnung: V573 Die nicht initialisierte Variable 'sectLast' wurde verwendet. Die Variable wurde verwendet, um sich selbst zu initialisieren. print2.c 599 Die

    Variable 'sectLast' ist sich selbst zugeordnet:
    int sectLast = sectLast;

    Es scheint, dass die Variable 'vsectLast' zur Initialisierung hätte verwendet werden sollen:
    int sectLast = vsectLast;

    Ein weiterer identischer Fehler wurde gefunden. Anscheinend die Konsequenz von Copy-Paste:

    V573 Uninitialized Variable 'sectLast' wurde verwendet. Die Variable wurde verwendet, um sich selbst zu initialisieren. print2.c 719

    Undefiniertes Verhalten


    CmdBitmap()
    {
      static int  iBitmap = 0;
      ....
      iBitmap = ++iBitmap % MAXBITMAP;
    }

    Warnung PVS-Studio: V567 Undefiniertes Verhalten. Die 'iBitmap'-Variable wird geändert, während sie zweimal zwischen Sequenzpunkten verwendet wird. ddedit.c 107

    Ich weiß nicht, wie dieser Code vor 20 Jahren behandelt wurde. Aber jetzt wird es als Rowdytum angesehen, da es zu undefiniertem Verhalten führt.

    Ähnlich:
    • V567 Undefiniertes Verhalten. Die Variable 'iIcon' wird geändert, während sie zweimal zwischen Sequenzpunkten verwendet wird. ddedit.c 132
    • V567 Undefiniertes Verhalten. Die 'iCursor'-Variable wird geändert, während sie zweimal zwischen Sequenzpunkten verwendet wird. ddedit.c 150

    Erfolgloser Aufruf der Funktion printf ()


    ReadAndDumpLargeSttb(cb,err)
      int     cb;
      int     err;
    {
      ....
      printf("\n - %d strings were read, "
             "%d were expected (decimal numbers) -\n");
      ....
    }

    Warnung PVS-Studio: V576 Falsches Format. Beim Aufruf der Funktion 'printf' wird eine andere Anzahl von tatsächlichen Argumenten erwartet. Erwartet: 3. Vorhanden: 1. dini.c 498

    Die Funktion printf () ist eine Funktion mit einer variablen Anzahl von Argumenten . Argumente können an sie weitergegeben werden oder nicht. Hier vergaßen sie die Argumente, wodurch Müll gedruckt wird.

    Nicht initialisierte Zeiger


    In einem der Hilfsprogramme, die im Quellcode von Word enthalten sind, finden Sie etwas völlig Unverständliches.
    main(argc, argv)
    int argc;
    char * argv [];
    {
      FILE * pfl;
      ....
      for (argi = 1; argi < argc; ++argi)
      {
        if (FWild(argv[argi]))
        {
          FEnumWild(argv[argi], FEWild, 0);
        }
        else
        {
          FEWild(argv[argi], 0);
        }
        fclose(pfl);
      }
      ....
    }

    PVS-Studio Warnung: V614 Nicht initialisierter Zeiger 'pfl' verwendet. Prüfen Sie das erste tatsächliche Argument der Funktion 'fclose'. eldes.c 87 Die

    Variable 'pfl' wird nicht vor der Schleife und in der Schleife selbst initialisiert. Aber oft wird die Funktion fclose (pfl) aufgerufen. All dies könnte jedoch gut funktionieren. Die Funktion gibt den Fehlerstatus zurück und das Programm setzt seine Arbeit fort.

    Und hier ist ein weiteres gefährliches Merkmal. Höchstwahrscheinlich führt sein Aufruf zu einer abnormalen Beendigung des Programms.
    FPathSpawn( rgsz )
    char *rgsz[];
    { /* puts the correct path at the beginning of rgsz[0]
         and calls FSpawnRgsz */
      char *rgsz0;
      strcpy(rgsz0, szToolsDir);
      strcat(rgsz0, "\\");
      strcat(rgsz0, rgsz[0]);
      return FSpawnRgsz(rgsz0, rgsz);
    }

    PVS-Studio Warnung: V614 Nicht initialisierter Zeiger 'rgsz0' verwendet. Prüfen Sie das erste tatsächliche Argument der Funktion 'strcpy'. makeopus.c 961 Der

    Zeiger 'rgsz0' ist nicht initialisiert. Dies hindert Sie nicht daran, eine Zeichenfolge in diese zu kopieren.

    Ein Tippfehler im Zustand


    ....
    #define wkHdr    0x4000
    #define wkFtn    0x2000
    #define wkAtn    0x0008
    ....
    #define wkSDoc    (wkAtn+wkFtn+wkHdr)
    CMD CmdGoto (pcmb)
    CMB * pcmb;
    {
      ....
      int wk = PwwdWw(wwCur)->wk;
        if (wk | wkSDoc)
          NewCurWw((*hmwdCur)->wwUpper, fTrue);
      ....
    }

    PVS-Studio Warnung: V617 Prüfen Sie den Zustand. Das Argument '(0x0008 + 0x2000 + 0x4000)' des '|' Die bitweise Operation enthält einen Wert ungleich Null. dlgmisc.c 409 Die

    Bedingung (wk | wkSDoc) ist immer wahr. In der Tat wollten sie hier höchstwahrscheinlich schreiben:
    if (wk & wkSDoc)

    In der Regel vertauscht der Betreiber | und &.

    Und am Ende ein langes, aber einfaches Beispiel


    int TmcCharacterLooks(pcmb)
    CMB * pcmb;
    {
      ....
      if (qps < 0)
      {
        pcab->wCharQpsSpacing = -qps;
        pcab->iCharIS = 2;
      }
      else  if (qps > 0)
      {
        pcab->iCharIS = 1;
      }
      else
      {
        pcab->iCharIS = 0;
      }
      ....
      if (hps < 0)
      {
        pcab->wCharHpsPos = -hps;
        pcab->iCharPos = 2;
      }
      else  if (hps > 0)
      {
        pcab->iCharPos = 1;
      }
      else
      {
        pcab->iCharPos = 1;
      }
      ....
    }

    PVS-Studio Warnung: V523 Die Anweisung 'then' entspricht der Anweisung 'else'. dlglook1.c 873

    Wenn sie mit der Variablen 'qps' arbeiten, schreiben sie die folgenden Werte in 'pcab-> iCharIS': 2, 1, 0.

    Ebenso arbeiten sie mit der Variablen 'hps'. Gleichzeitig werden verdächtige Zahlen in die Variable 'pcab-> iCharPos' gestellt: 2, 1, 1.

    Dies ist höchstwahrscheinlich ein Tippfehler. Am Ende sollten Sie wahrscheinlich Null verwenden.

    Fazit


    Fand sehr wenige fremde Orte. Dafür gibt es zwei Gründe. Erstens schien mir der Code qualitativ hochwertig und sehr verständlich geschrieben zu sein. Zweitens war die Analyse noch minderwertig. Es besteht keine praktische Notwendigkeit, dem Analysator die Merkmale des alten C beizubringen.

    Ich hoffe, ich habe Ihnen ein paar Minuten interessante Lektüre gegeben. Vielen Dank für Ihre Aufmerksamkeit. Und probieren Sie den PVS-Studio-Analyzer in Ihrem Code aus.

    Dieser Artikel ist in englischer Sprache.


    Wenn Sie diesen Artikel mit einem englischsprachigen Publikum teilen möchten, verwenden Sie bitte den Link zur Übersetzung: Andrey Karpov. Archäologie für Unterhaltung oder Überprüfen von Microsoft Word 1.1a mit PVS-Studio .

    Haben Sie den Artikel gelesen und eine Frage?
    Oft werden unseren Artikeln die gleichen Fragen gestellt. Hier haben wir Antworten auf diese Fragen gesammelt: Antworten auf Fragen von Lesern von Artikeln über PVS-Studio und CppCat, Version 2014 . Bitte beachten Sie die Liste.

    Jetzt auch beliebt: