Um die Liste der Open Source-Programmiersprachen zu ergänzen, die mit dem statischen Code-Analysator PVS-Studio getestet wurden, wurde Perl 5 ausgewählt. In diesem Artikel werden Fehler und Schwierigkeiten beim Anzeigen der Analyseergebnisse festgestellt. Die Anzahl der Makros im Code ist so groß, dass es den Anschein hat, dass der Code nicht in C geschrieben ist, sondern in einem seltsamen Dialekt. Trotz der Schwierigkeiten beim Anzeigen des Codes konnte ich interessante Probleme sammeln, die in diesem Artikel behandelt werden.
Einführung
Perl ist eine interpretierte dynamische Allzweck-Programmiersprache auf hoher Ebene (Perl ist eine Familie von zwei interpretierten dynamischen Allzweck-Programmiersprachen für allgemeine Zwecke). Perl 5 wurde 1994 eingeführt. Nach ein paar Jahrzehnten verursacht C-Code mit zahlreichen Makros bei modernen Programmierern Nervosität.
Der Quellcode für Perl 5 wurde aus dem offiziellen
Repository (
Blead Branch)
entnommen . Zur Überprüfung des Projekts haben wir den statischen Code-Analysator
PVS-Studio verwendet. Die Analyse wurde unter dem Linux-Betriebssystem durchgeführt, der Analysator ist jedoch auch für Windows und MacOS verfügbar.
Das Anzeigen der Ergebnisse der Analyse war keine leichte Aufgabe. Tatsache ist, dass der Analysator die
vorverarbeiteten .i- Dateien überprüft, in denen alle Anweisungen des Präprozessors bereits geöffnet sind, und Warnungen für Dateien mit Quellcode generiert. Dies ist das korrekte Verhalten des Analysators. Sie müssen nichts ändern, aber es werden viele Warnungen für Makros ausgegeben! Und hinter Makros steckt ein unlesbarer Code.
Der ternäre Operator funktioniert nicht so, wie Sie denken
V502 Vielleicht arbeitet der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '-'. toke.c 9494
STATIC char * S_scan_ident(pTHX_ char *s, char *dest, STRLEN destlen, I32 ck_uni) { .... if ((s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1) && VALID_LEN_ONE_IDENT(s, PL_bufend, is_utf8)) { .... } .... }
Beginnen wir die Überprüfung mit einem schönen Fehler. Alle paar Codeüberprüfungen müssen wiederholen, dass der ternäre Operator beim Rechnen fast die niedrigste Priorität hat.
Betrachten Sie das Fragment mit dem Fehler:
s <= PL_bufend - (is_utf8) ? UTF8SKIP(s) : 1
Die Reihenfolge der Operationen, die der Programmierer erwartet:
- ?:
- - -
- <=
Was wirklich passiert:
- - -
- <=
- ?:
Behalten Sie eine Beschriftung mit den Prioritäten der Operationen bei: "
Priorität der Operationen in C / C ++ ".
V502 Vielleicht arbeitet der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '=='. re_exec.c 9193
STATIC I32 S_regrepeat(pTHX_ regexp *prog, char **startposp, const regnode *p, regmatch_info *const reginfo, I32 max _pDEPTH) { .... assert(STR_LEN(p) == reginfo->is_utf8_pat ? UTF8SKIP(STRING(p)) : 1); .... }
Einfacher Code mit einem ähnlichen Fehler. Wenn Sie jedoch die Prioritäten von Operationen nicht kennen, können Sie einen Fehler in einem Ausdruck beliebiger Größe machen.
Ein anderer Ort mit Behauptung:
- V502 Vielleicht arbeitet der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '=='. re_exec.c 9286
V502 Vielleicht arbeitet der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '&&'. pp_hot.c 3036
PP(pp_match) { .... MgBYTEPOS_set(mg, TARG, truebase, RXp_OFFS(prog)[0].end); .... }
Und hier ist eine Warnung für ein Makro ... Um zu verstehen, was dort passiert, hilft auch die Implementierung des Makros nicht weiter, da einige weitere Makros verwendet werden!
Daher hänge ich ein Fragment der vorverarbeiteten Datei für diese Codezeile an:
(((targ)->sv_flags & 0x00000400) && (!((targ)->sv_flags & 0x00200000) || S_sv_only_taint_gmagic(targ)) ? (mg)->mg_len = ((prog->offs)[0].end), (mg)->mg_flags |= 0x40 : ((mg)->mg_len = (((targ)->sv_flags & 0x20000000) && !__builtin_expect(((((PL_curcop)->cop_hints + 0) & 0x00000008) ? (_Bool)1 :(_Bool)0),(0))) ? (ssize_t)Perl_utf8_length( (U8 *)(truebase), (U8 *)(truebase)+((prog->offs)[0].end)) : (ssize_t)((prog->offs)[0].end), (mg)->mg_flags &= ~0x40));
Irgendwo hier bezweifelte der Analysator die korrekte Verwendung des ternären Operators (es gibt 3 davon), aber ich fand nicht die Kraft zu verstehen, was in diesem Code getan wird. Wir haben bereits gesehen, dass die Entwickler solche Fehler machen, daher kann es auch hier sehr wahrscheinlich sein.
Drei weitere Verwendungen dieses Makros:
- V502 Vielleicht arbeitet der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '&&'. pp_ctl.c 324
- V502 Vielleicht arbeitet der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '&&'. regexec.c 7335
- V502 Vielleicht arbeitet der Operator '?:' Anders als erwartet. Der Operator '?:' Hat eine niedrigere Priorität als der Operator '&&'. re_exec.c 7335
Beachten Sie die Kollegen Andrei Karpov. Ich habe 10 Minuten über diesen Code meditiert und bin geneigt zu glauben, dass es keinen Fehler gibt. Aber auf jeden Fall ist es äußerst schmerzhaft, solchen Code zu lesen, und es ist besser, nicht so zu schreiben.
Fehler in den Bedingungen
V523 Die Anweisung 'then' entspricht der Anweisung 'else'. toke.c 12056
static U8 * S_add_utf16_textfilter(pTHX_ U8 *const s, bool reversed) { .... SvCUR_set(PL_linestr, 0); if (FILTER_READ(0, PL_linestr, 0)) { SvUTF8_on(PL_linestr); } else { SvUTF8_on(PL_linestr); } PL_bufend = SvEND(PL_linestr); return (U8*)SvPVX(PL_linestr); }
Ich denke, Sie können auf den Inhalt von Makros verzichten, um sicherzustellen, dass verdächtig duplizierte Codefragmente vorhanden sind.
V564 Das '|' Der Operator wird auf den Wert des Bool-Typs angewendet. Sie haben wahrscheinlich vergessen, Klammern einzufügen, oder wollten das '||' Betreiber. op.c 11494
OP * Perl_ck_rvconst(pTHX_ OP *o) { .... gv = gv_fetchsv(kidsv, o->op_type == OP_RV2CV && o->op_private & OPpMAY_RETURN_CONSTANT ? GV_NOEXPAND : iscv | !(kid->op_private & OPpCONST_ENTERED), iscv
Sehr seltsamer Code. Der Ausdruck "iscv | ! (kid-> op_private & OPpCONST_ENTERED) "wird in keiner Weise verwendet. Hier gibt es eindeutig einen Tippfehler. Zum Beispiel sollten Sie vielleicht hier schreiben:
: iscv = !(kid->op_private & OPpCONST_ENTERED), iscv
V547 Der Ausdruck 'RETVAL == 0' ist immer wahr. Typemap.c 710
XS_EUPXS(XS_XS__Typemap_T_SYSRET_pass); XS_EUPXS(XS_XS__Typemap_T_SYSRET_pass) { dVAR; dXSARGS; if (items != 0) croak_xs_usage(cv, ""); { SysRet RETVAL; #line 370 "Typemap.xs" RETVAL = 0; #line 706 "Typemap.c" { SV * RETVALSV; RETVALSV = sv_newmortal(); if (RETVAL != -1) {
Die Variable
RETVAL wird zweimal hintereinander überprüft. Darüber hinaus ist aus dem Code ersichtlich, dass diese Variable immer Null ist. Vielleicht wollten sie unter einer oder beiden Bedingungen den
RETVALSV- Zeiger überprüfen, aber sie machten einen Tippfehler.
Warnungen über die Größe des Bedieners auslösen
Es gibt verschiedene Arten von Diagnoseregeln im Analysegerät, die mithilfe des Operators
sizeof nach Fehlern suchen. Beim Perl 5-Projekt haben zwei dieser Diagnosen insgesamt etwa tausend Warnungen generiert. In diesem Fall ist nicht der Analysator schuld, sondern die Makros.
V568 Es ist seltsam, dass das Argument des Operators sizeof () der Ausdruck 'len + 1' ist. util.c 1084
char * Perl_savepvn(pTHX_ const char *pv, I32 len) { .... Newx(newaddr,len+1,char); .... }
Der Code enthält viele ähnliche Makros. Ich habe eines als Beispiel gewählt, wir interessieren uns für das Argument "len + 1".
Ein Makro-Präprozessor wird in den folgenden Code erweitert:
(newaddr = ((void)(__builtin_expect(((((( sizeof(size_t) < sizeof(len+1) || sizeof(char) > ((size_t)1 << 8*(sizeof(size_t) - sizeof(len+1)))) ? (size_t)(len+1) : ((size_t)-1)/sizeof(char)) > ((size_t)-1)/sizeof(char))) ? (_Bool)1 : (_Bool)0),(0)) && (S_croak_memory_wrap(),0)), (char*)(Perl_safesysmalloc((size_t)((len+1)*sizeof(char))))));
Für das Konstrukt
sizeof (len +1) wird eine Analysatorwarnung ausgegeben. Tatsache ist, dass in den Argumenten des Operators
sizeof keine Berechnungen durchgeführt werden. Viele Makros werden zu ähnlichem Code erweitert. Dies ist wahrscheinlich ein alter Legacy-Code, in dem niemand etwas anfassen möchte, aber aktuelle Entwickler verwenden weiterhin alte Makros, was auf ihr unterschiedliches Verhalten hinweist.
Dereferenzieren von Nullzeigern
V522 Eine Dereferenzierung des Nullzeigers 'sv' kann stattfinden. pp_ctl.c 577
OP * Perl_pp_formline(void) { .... SV *sv = ((void *)0); .... switch (*fpc++) { .... case 4: arg = *fpc++; f += arg; fieldsize = arg; if (mark < sp) sv = *++mark; else { sv = &(PL_sv_immortals[2]); Perl_ck_warner( (28 ), "...."); } .... break; case 5: { const char *s = item = ((((sv)->sv_flags & (....)) == 0x00000400) ? .... .... } .... }
Dieses Codefragment wird vollständig aus der vorverarbeiteten Datei übernommen, da das Vorhandensein des Problems aufgrund von Makros nicht in der Quelldatei überprüft werden kann.
Der
SV- Zeiger wird bei der Deklaration auf Null initialisiert. Der Analysator stellte fest, dass dieser Zeiger bei Übergabe von
5 in der
switch-Anweisung dereferenziert wird, was zuvor noch nie initialisiert wurde. In der Verzweigung ist eine Änderung des
sv- Zeigers mit dem Wert
4 vorhanden , aber am Ende dieses Codeblocks befindet sich die
break- Anweisung. Höchstwahrscheinlich ist an dieser Stelle zusätzlicher Code erforderlich.
V595 Der 'k'-Zeiger wurde verwendet, bevor er gegen nullptr verifiziert wurde. Überprüfen Sie die Zeilen: 15919, 15920. op.c 15919
void Perl_rpeep(pTHX_ OP *o) { .... OP *k = o->op_next; U8 want = (k->op_flags & OPf_WANT);
In diesem Codefragment hat der Analysator einen Zeiger
k gefunden , der eine Zeile vor seiner Gültigkeitsprüfung dereferenziert wird. Dies kann entweder ein Fehler oder ein zusätzlicher Code sein.
Diagnostics
V595 findet in jedem Projekt viele Warnungen, Perl 5 ist keine Ausnahme. Es ist unmöglich, all dies in den Artikel zu integrieren, daher beschränken wir uns auf ein Beispiel, und die Entwickler überprüfen das Projekt auf Wunsch selbst.
Verschiedenes
V779 Nicht erreichbarer Code erkannt. Möglicherweise liegt ein Fehler vor. universal.c 457
XS(XS_utf8_valid); XS(XS_utf8_valid) { dXSARGS; if (items != 1) croak_xs_usage(cv, "sv"); else { SV * const sv = ST(0); STRLEN len; const char * const s = SvPV_const(sv,len); if (!SvUTF8(sv) || is_utf8_string((const U8*)s,len)) XSRETURN_YES; else XSRETURN_NO; } XSRETURN_EMPTY; }
In der Zeile mit
XSRETURN_EMPTY hat der Analysator einen nicht erreichbaren Code festgestellt. Diese Funktion
enthält zwei
return-Anweisungen und
croak_xs_usage - ein Makro, das zu einer noreturn-Funktion erweitert wird:
void Perl_croak_xs_usage(const CV *const cv, const char *const params) __attribute__((noreturn));
An ähnlichen Stellen im Perl 5-Code wird das Makro
NOT_REACHED verwendet, um einen nicht erreichbaren Zweig anzuzeigen.
V784 Die Größe der Bitmaske ist kleiner als die Größe des ersten Operanden. Dies führt zum Verlust höherer Bits. inffast.c 296
void ZLIB_INTERNAL inflate_fast(z_streamp strm, unsigned start) { .... unsigned long hold; unsigned bits; .... hold &= (1U << bits) - 1; .... }
Der Analysator hat beim Arbeiten mit Bitmasken einen verdächtigen Vorgang festgestellt. Als Bitmaske wird eine Variable mit niedrigerer Auflösung als die Variable
hold verwendet . Dies führt zum Verlust hoher Bits. Entwickler sollten diesen Code beachten.
Fazit

Das Finden von Fehlern durch Makros war sehr schwierig. Das Anzeigen des Berichts hat viel Zeit und Mühe gekostet. Trotzdem enthält der Artikel sehr interessante Fälle, die echten Fehlern ähneln. Der Analysatorbericht ist ziemlich umfangreich, dort gibt es definitiv viel interessanteres. Aber ich kann es nicht weiter betrachten :). Ich rate dem Entwickler, das Projekt selbst zu überprüfen und die Fehler zu beseitigen, die er erkennen kann.
PS Wir möchten dieses interessante Projekt auf jeden Fall unterstützen und sind bereit, Entwicklern für mehrere Monate eine Lizenz zur Verfügung zu stellen.

Wenn Sie diesen Artikel einem englischsprachigen Publikum zugänglich machen möchten, verwenden Sie bitte den Link zur Übersetzung: Svyatoslav Razmyslov.
Perl 5: So verbergen Sie Fehler in Makros