Dies ist eine Frage im Zusammenhang mit diese faszinierende Frage zum Aufspüren von Division-by-Zero-Exceptions zur Kompilierzeit .
Aus der Antwort von Eric Lippert ist das nicht trivial, um es richtig zu erreichen (was vermutlich der Grund dafür ist, dass es nicht bereits zur Verfügung steht).
Meine Frage ist:
Ist der Schwierigkeitsgrad, diese Art von Prüfungen durchzuführen, unabhängig von der "Ebene" der Sprache, z. höheres Niveau vs. niedrigeres Niveau?
Insbesondere konvertiert der C # -Compiler C # in MSIL. Würden diese Arten von Kontrollen auf der MSIL-Ebene im Rahmen einer Art von Zweitdurchgangsprüfung einfacher oder schwieriger werden?
Oder macht die Sprache selbst überhaupt keinen Unterschied?
Wenn ich die in Erics Antwort aufgelisteten Fallstricke lese, würde ich annehmen, dass die Schecks in jeder Sprache gleich sein müssten? Zum Beispiel können Sie Sprünge in vielen Sprachen haben und müssten daher die Flussüberprüfung implementieren, die Eric beschreibt ...?
Nur um diese Frage spezifisch zu halten, wäre diese Art von Überprüfung in MSIL einfacher oder schwerer als in C #?
Das ist eine sehr interessante und tiefgründige Frage - wenn auch vielleicht nicht gut für diese Seite geeignet.
Die Frage, wenn ich es verstehe, ist die Frage, wie sich die Wahl der zu analysierenden Sprache auswirkt, wenn man bei der Suche nach Defekten statische Analysen durchführt; sollte ein Analysator IL betrachten, oder sollte er sich den Quellcode ansehen? Beachten Sie, dass ich diese Frage aus dem ursprünglichen engen Fokus auf Fehler durch Division durch Null erweitert habe.
Die Antwort ist natürlich: Es kommt darauf an. Beide Techniken werden häufig in der statischen Analyseindustrie verwendet, und es gibt Vor- und Nachteile von jedem. Es hängt davon ab, welche Defekte Sie suchen, welche Techniken Sie verwenden, um falsche Pfade zu beschneiden, falsche Positive zu unterdrücken und Defekte abzuleiten und wie Sie entdeckte Fehler für Entwickler aufdecken wollen.
Das Analysieren von Bytecode hat einige klare Vorteile gegenüber dem Quellcode. Der wichtigste ist: Wenn Sie einen Bytecode-Analysator für Java-Bytecode haben, können Sie Scala ausführen, ohne jemals einen Scala-Analysator zu schreiben. Wenn Sie einen MSIL-Analysator haben, können Sie C # oder VB oder F # durchgehen, ohne Analysatoren für jede Sprache zu schreiben.
Die Analyse von Code auf Bytecode-Ebene hat auch Vorteile. Die Analyse des Kontrollflusses ist sehr einfach, wenn Sie einen Bytecode haben, weil Sie Blöcke von Bytecode sehr schnell in "Basisblöcke" organisieren können; Ein Basisblock ist eine Code-Region, in der keine Anweisung existiert, die in ihre Mitte verzweigt, und jeder normale Ausgang des Blocks befindet sich an seiner Unterseite. (Ausnahmen können natürlich überall vorkommen.) Indem wir Bytecode in Basisblöcke zerlegen, können wir ein Diagramm von Blöcken berechnen, die zueinander verzweigen, und dann jeden Block hinsichtlich seiner Wirkung auf den lokalen und globalen Zustand zusammenfassen. Bytecode ist nützlich, weil es eine Abstraktion über Code ist, der auf einer tieferen Ebene zeigt, was wirklich passiert.
Das ist natürlich auch sein größter Mangel; Bytecode verliert Informationen über die Absichten des Entwicklers . Jeder Fehlerprüfer, der Informationen vom Quellcode benötigt, um den Fehler zu erkennen oder ein falsches Positiv zu verhindern, wird schlechte Ergebnisse liefern, wenn er auf Bytecode läuft. Betrachten Sie zum Beispiel ein C-Programm:
%Vor% Wenn dieser schreckliche Code auf Maschinencode oder Bytecode herabgesetzt würde, würden wir nur eine Reihe von Verzweigungsbefehlen sehen und wir hätten keine Ahnung, dass wir hier einen Defekt melden sollten, an den else
bindet if(foo)
und nicht if(blah)
, wie der Entwickler beabsichtigt.
Die Gefahren des C-Präprozessors sind bekannt. Aber es gibt auch große Schwierigkeiten, die bei der Analyse von komplex verringertem Code auf Bytecode-Ebene auftreten. Betrachten Sie zum Beispiel etwas wie C #:
%Vor% Klar x
kann hier nicht dereferenziert werden. Aber C # wird dies auf einen absolut verrückten Code reduzieren; Ein Teil dieses Codes wird ungefähr so aussehen:
Und so weiter. (Außer dass es viel, viel komplizierter ist als das.) Dies wird dann in noch abstrakteren Bytecode abgesenkt; Stellen Sie sich vor, Sie versuchen, diesen Code auf der Ebene der Switches zu verstehen, die auf gotos und Delegierte in Schliessungen gesenkt werden.
Ein statischer Analysator, der den äquivalenten Bytecode analysiert, wäre vollkommen in seinem Recht zu sagen "einfach x kann null sein, weil wir in einem Zweig des Schalters nach ihm suchen; dies ist der Beweis, dass x auf Nichtigkeit in anderen Zweigen überprüft werden muss, und es ist nicht, deshalb werde ich einen Null-Dereferenzierungsfehler auf den anderen Zweigen geben. "
Aber das wäre falsch positiv. Wir wissen etwas, was der statische Analysator nicht kann, nämlich dass der Nullzustand vor jedem anderen Zustand ausgeführt werden muss, und dass wenn die Coroutine wieder aufgenommen wird x immer auf Null geprüft wurde . Das ist offensichtlich aus dem ursprünglichen Quellcode, aber es wäre sehr schwierig, aus dem Bytecode herauszukommen.
Was machen Sie dann, wenn Sie die Vorteile der Bytecode-Analyse ohne die Nachteile nutzen wollen? Es gibt eine Vielzahl von Techniken; zum Beispiel könnten Sie Ihre eigene Zwischensprache schreiben, die höher war als Bytecode - die High-Level-Konstrukte wie "yield" oder "await" oder "for loop" hat - einen Analysator schreiben, der diese Zwischensprache analysiert, und Schreiben Sie dann Compiler, die jede Zielsprache - C #, Java, was auch immer - in Ihre Zwischensprache übersetzen. Das bedeutet, eine Menge Compiler zu schreiben, aber nur ein Analysator, und vielleicht ist das Schreiben des Analysators der schwierige Teil.
Das war eine sehr kurze Diskussion, ich weiß. Es ist ein komplexes Thema.
Wenn Sie sich für den Entwurf statischer Analysatoren auf Bytecode interessieren, sollten Sie sich das Design von Infer ansehen, einem statischen Open-Source-Analysator für Java und andere Sprachen, der Java-Bytecode in einen noch niedrigeren Bytecode zur Analyse von Heap-Eigenschaften umwandelt ; Lesen Sie zuerst die Trennungslogik für die Ableitung von Heap-Eigenschaften. Ссылка