@dmckee
Nun, das passt nicht in einen Kommentar, aber hier ist die Sache:
Zuerst schreiben Sie einen korrekten statischen Analysator. "Korrekt" bedeutet in diesem Zusammenhang, dass er nicht schweigen wird, wenn es irgendetwas Fragwürdiges im analysierten Code gibt, so dass Sie in dieser Phase fröhlich undefiniertes und unspezifiziertes Verhalten vermischen. Beide sind in kritischem Code schlecht und inakzeptabel, und Sie warnen zu Recht vor beiden.
Aber Sie wollen nur einmal vor einem möglichen Fehler warnen, und Sie wissen auch, dass Ihr Analysator in Benchmarks in Bezug auf "Präzision" und "Recall" im Vergleich zu anderen, möglicherweise nicht korrekten, Analysatoren beurteilt wird, also dürfen Sie nicht zweimal vor demselben Problem warnen... Sei es ein echter oder ein falscher Alarm (Sie wissen nicht, welcher. Sie wissen nie, welcher, sonst wäre es zu einfach).
Sie wollen also eine einzige Warnung ausgeben für
*p = x;
y = *p;
Denn sobald p
bei der ersten Anweisung ein gültiger Zeiger ist, kann davon ausgegangen werden, dass er auch bei der zweiten Anweisung ein gültiger Zeiger ist. Wenn Sie dies nicht ableiten, verringert sich Ihre Punktzahl bei der Präzisionsmetrik.
Sie bringen Ihrem Analysator also bei, dass er davon ausgeht, dass p
ein gültiger Zeiger ist, sobald Sie im obigen Code das erste Mal davor gewarnt haben, so dass Sie beim zweiten Mal nicht mehr davor warnen müssen. Allgemeiner ausgedrückt: Sie lernen, Werte (und Ausführungspfade) zu ignorieren, die etwas entsprechen, vor dem Sie bereits gewarnt haben.
Dann stellen Sie fest, dass nicht viele Leute kritischen Code schreiben, also machen Sie andere, leichtere Analysen für den Rest von ihnen, basierend auf den Ergebnissen der ersten, korrekten Analyse. Sagen wir, ein C-Programm-Slicer.
Und du sagst es "ihnen": Sie müssen sich nicht um all die (möglicherweise oft falschen) Alarme kümmern, die bei der ersten Analyse ausgelöst werden. Das zerschnittene Programm verhält sich genauso wie das ursprüngliche Programm, solange keiner von ihnen ausgelöst wird. Der Slicer erzeugt Programme, die für das Slicing-Kriterium für "definierte" Ausführungspfade äquivalent sind.
Und die Benutzer ignorieren fröhlich die Alarme und benutzen die Schneidemaschine.
Und dann merkt man, dass es sich vielleicht um ein Missverständnis handelt. Zum Beispiel, die meisten Implementierungen von memmove
(Sie wissen schon, derjenige, der überlappende Blöcke behandelt) rufen tatsächlich ein nicht spezifiziertes Verhalten auf, wenn sie mit Zeigern aufgerufen werden, die nicht auf denselben Block zeigen (indem sie Adressen vergleichen, die nicht auf denselben Block zeigen). Und Ihr Analysator ignoriert beide Ausführungspfade, weil beide nicht spezifiziert sind, aber in Wirklichkeit sind beide Ausführungspfade gleichwertig und alles ist gut.
Es sollte also keine Missverständnisse über die Bedeutung von Alarmen geben, und wenn man beabsichtigt, sie zu ignorieren, sollten nur unmissverständliche und undefinierte Verhaltensweisen ausgeschlossen werden.
Und so kommt es, dass man ein starkes Interesse daran hat, zwischen nicht spezifiziertem Verhalten und nicht definiertem Verhalten zu unterscheiden. Niemand kann Ihnen vorwerfen, dass Sie letzteres ignorieren. Aber Programmierer werden ersteres schreiben, ohne überhaupt darüber nachzudenken, und wenn Sie sagen, dass Ihr Slicer "falsches Verhalten" des Programms ausschließt, werden sie sich nicht betroffen fühlen.
Und dies ist das Ende einer Geschichte, die definitiv nicht in einen Kommentar gepasst hat. Ich entschuldige mich bei allen, die bis hierher gelesen haben.