
In diesem Artikel werde ich über die neue statische Analysebibliothek (und das Hilfsprogramm) von go-ruleguard
sprechen, die gogrep
für die Verwendung in Linters anpasst.
Besonderheit: Sie beschreiben die Regeln der statischen Analyse auf einem speziellen Go-like DSL, das zu Beginn von ruleguard
zu einer Reihe von Diagnosen wird. Dies ist möglicherweise eines der am einfachsten konfigurierbaren Tools für die Implementierung benutzerdefinierter Inspektionen für Go.
Als Bonus werden wir über go/analysis
und seine Vorgänger sprechen.
Erweiterbarkeit der statischen Analyse
Es gibt viele Linters für Go, von denen einige erweitert werden können. Um den Linter zu erweitern, müssen Sie normalerweise Go-Code mit der speziellen Linter-API schreiben.
Es gibt zwei Möglichkeiten: Go Plugins und Monolith. Der Monolith impliziert, dass alle Schecks (einschließlich Ihrer persönlichen) in der Kompilierungsphase verfügbar sind.
Für die Erweiterung von revive
müssen neue Überprüfungen in den Kernel aufgenommen werden. go-critic
kann darüber hinaus Plug-ins, mit denen Sie Erweiterungen unabhängig vom Hauptcode sammeln können. Beide Ansätze implizieren, dass Sie die Manipulationen go/ast
und go/types
unter Verwendung der linter-API in Go implementieren. Selbst einfache Prüfungen erfordern viel Code .
go/analysis
zielt darauf ab, das Bild dadurch zu vereinfachen, dass der "Rahmen" des Linter nahezu identisch wird, löst jedoch nicht das Problem der Komplexität der technischen Implementierung der Diagnose selbst.
Exkurs zu `loader` und` go / packages`
Wenn Sie einen Analyzer für Go schreiben, besteht Ihr letztes Ziel darin, mit AST und Typen zu interagieren. Bevor Sie dies tun können, muss der Quellcode jedoch auf die richtige Weise "geladen" werden. Zur Vereinfachung umfasst das Konzept des Ladens das Parsen , die Typprüfung und das Importieren von Abhängigkeiten .
Der erste Schritt zur Vereinfachung dieser Pipeline war das go/loader
Paket, mit dem Sie über ein paar Aufrufe alles "herunterladen" können, was Sie benötigen. Alles war fast in Ordnung, und dann wurde er zugunsten von go/packages
veraltet. go/packages
hat eine leicht verbesserte API und funktioniert theoretisch gut mit Modulen.
Nun ist es am besten, keine der oben genannten Methoden direkt zum Schreiben von Analysatoren zu verwenden, da go/analysis
go/packages
etwas gab, das keine der vorherigen Lösungen hatte - eine Struktur für Ihr Programm. Jetzt können wir das vorgegebene go/analysis
Paradigma verwenden und Analysegeräte effizienter wiederverwenden. Dieses Paradigma ist umstritten. go/analysis
eignet sich beispielsweise gut für die Analyse auf der Ebene eines Pakets und seiner Abhängigkeiten. Eine globale Analyse ohne raffinierte technische Tricks ist jedoch nicht einfach.
go/analysis
vereinfacht auch das Testen von Analysegeräten .
Was ist Herrscher?

go-ruleguard
ist ein statisches Analysedienstprogramm, das standardmäßig keine einzelne Prüfung enthält.
Die Regeln für die ruleguard
zu Beginn aus einer speziellen gorules
Datei gorules
, die deklarativ die gorules
beschreibt, für die Warnungen ausgegeben werden sollen. Diese Datei kann von Benutzern des ruleguard
frei bearbeitet werden.
Es ist nicht notwendig, gorules
Steuerprogramm gorules
zu gorules
, um neue Schecks zu verbinden, daher können die Regeln von gorules
als dynamisch bezeichnet werden .
Das ruleguard
Kontrolle der ruleguard
sieht folgendermaßen aus:
package main import ( "github.com/quasilyte/go-ruleguard/analyzer" "golang.org/x/tools/go/analysis/singlechecker" ) func main() { singlechecker.Main(analyzer.Analyzer) }
Gleichzeitig wird der analyzer
über das Paket ruleguard
implementiert, das Sie verwenden müssen, wenn Sie ihn als Bibliothek verwenden möchten.
Herrscher VS wiederbeleben
Nehmen wir ein einfaches, aber realistisches Beispiel: Nehmen wir an, wir möchten runtime.GC()
-Aufrufe in unseren Programmen vermeiden. In revive gibt es dafür bereits eine eigene Diagnose, sie heißt "call-to-gc"
.
Call-to-GC-Implementierung (70 Zeilen in Elven)
package rule import ( "go/ast" "github.com/mgechev/revive/lint" )
Vergleichen Sie nun, wie das in go-ruleguard
:
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func callToGC(m fluent.Matcher) { m.Match(`runtime.GC()`).Report(`explicit call to the garbage collector`) }
Nichts mehr, nur was wirklich wichtig ist - runtime.GC
und die Nachricht, die ausgegeben werden muss, falls die Regel ausgelöst wird.
Sie fragen sich vielleicht: Ist das alles? Ich habe speziell mit einem so einfachen Beispiel begonnen, um zu zeigen, wie viel Code für eine sehr triviale Diagnose im Fall des herkömmlichen Ansatzes erforderlich sein könnte. Ich verspreche, es wird spannendere Beispiele geben.
Schnellstart
go-critic
verfügt über eine rangeExprCopy
Diagnose, die potenziell unerwartete Array-Kopien im Code findet.
Dieser Code wird über eine Kopie des Arrays iteriert:
var xs [2048]byte for _, x := range xs {
Die Lösung für dieses Problem besteht darin, ein Zeichen hinzuzufügen:
var xs [2048]byte - for _, x := range xs { // Copies 2048 bytes + for _, x := range &xs { // No copy // Loop body. }
Höchstwahrscheinlich benötigen Sie dieses Kopieren nicht und die Leistung der korrigierten Version ist immer besser. Sie können warten, bis der Go-Compiler besser wird, oder Sie können solche Stellen im Code erkennen und sie heute mit demselben go-critic
korrigieren.
Diese Diagnose kann in der gorules
Sprache ( rules.go
Datei) rules.go
werden:
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func _(m fluent.Matcher) { m.Match(`for $_, $_ := range $x { $*_ }`, `for $_, $_ = range $x { $*_ }`). Where(m["x"].Addressable && m["x"].Type.Size >= 128). Report(`$x copy can be avoided with &$x`). At(m["x"]). Suggest(`&$x`) }
Die Regel findet alle for-range
Schleifen, in denen beide iterierbaren Variablen verwendet werden (dies ist der Fall, der zum Kopieren führt). Der iterierbare Ausdruck $x
muss addressable
und größer als der ausgewählte Schwellenwert in Bytes sein.
Report()
definiert die Nachricht, die an den Benutzer ausgegeben werden soll, und Suggest()
beschreibt eine quickfix
Vorlage, die in Ihrem Editor über gopls (LSP) sowie interaktiv verwendet werden kann, wenn ruleguard
mit dem Argument ruleguard
aufgerufen wird (wir werden darauf zurückkommen). At()
hängt die Warnung und den quickfix
an einen bestimmten Teil der Vorlage an. Wir brauchen dies, um $x
durch &$x
zu ersetzen, anstatt die gesamte Schleife neu zu schreiben.
Sowohl Report()
als auch Suggest()
akzeptieren eine Zeichenfolge, in die die von der Vorlage aus Match()
erfassten Ausdrücke interpoliert werden können. Die vordefinierte Variable $$
bedeutet "alle erfassten Fragmente" (als $0
in regulären Ausdrücken).
Erstellen Sie die Datei rangecopy.go
:
package example
Jetzt können wir ruleguard
:
$ ruleguard -rules rules.go -fix rangecopy.go rangecopy.go:12:20: builtins copy can be avoided with &builtins
Wenn wir uns danach rangecopy.go
, werden wir eine feste Version sehen, da ruleguard
mit dem Parameter ruleguard
aufgerufen wurde.
Die einfachsten Regeln können gorules
werden, ohne eine gorules
Datei zu erstellen:
$ ruleguard -c 1 -e 'm.Match(`return -1`)' rangecopy.go rangecopy.go:17:2: return -1 16 } 17 return -1 18 }
Dank der Verwendung von go/analysis/singlechecker
haben wir die Option -c
, mit der wir die angegebenen Kontextzeilen zusammen mit der Warnung selbst anzeigen können. Die Steuerung dieses Parameters ist ein wenig eingängig: Der Standardwert ist -c=-1
, was bedeutet, dass "kein Kontext" -c=0
, und -c=0
gibt eine Kontextzeile aus (die von der Diagnose angegebene).
Hier sind einige weitere interessante gorules
:
- Geben Sie Vorlagen ein , mit denen Sie die erwarteten Typen angeben können. Beispielsweise beschreibt die Ausdruckszuordnung
map[$t]$t
alle Zuordnungen, für die der Werttyp mit dem Typ des Schlüssels übereinstimmt, und *[$len]$elem
erfasst alle Zeiger auf Arrays. - Innerhalb einer einzelnen Funktion kann es mehrere Regeln geben,
und die Funktionen selbst sollten Regelgruppen genannt werden . - Die Regeln in der Gruppe werden nacheinander in der Reihenfolge angewendet, in der sie definiert sind. Die erste Regel, die ausgelöst wird, bricht den Vergleich mit den übrigen Regeln ab. Dies ist weniger für die Optimierung als vielmehr für die Spezialisierung von Regeln für bestimmte Fälle wichtig. Ein Beispiel, in dem dies nützlich ist, ist die Regel,
$x=$x+$y
in $x+=$y
umzuschreiben. In dem Fall mit $y=1
möchten Sie $x++
und nicht $x+=1
anbieten.
Weitere Informationen zum verwendeten DSL finden Sie in docs/gorules.md
.
Weitere Beispiele
package gorules import "github.com/quasilyte/go-ruleguard/dsl/fluent" func exampleGroup(m fluent.Matcher) {
Wenn für die Regel kein Report()
-Aufruf vorhanden ist, wird die von Suggest()
ausgegebene Nachricht verwendet. Dies ermöglicht in einigen Fällen die Vermeidung von Doppelspurigkeiten.
Typfilter und Unterausdrücke können verschiedene Eigenschaften prüfen. Beispielsweise sind die Eigenschaften Pure
und Const
nützlich:
Var.Pure
bedeutet, dass der Ausdruck keine Nebenwirkungen hat.Var.Const
bedeutet, dass der Ausdruck in einem konstanten Kontext verwendet werden kann (z. B. die Dimension eines Arrays).
Für package-qualified
Namen unter Where()
-Bedingungen müssen Sie die Import()
-Methode verwenden. Der Einfachheit halber wurden alle Standardpakete für Sie importiert, sodass im obigen Beispiel keine zusätzlichen Importe erforderlich sind.
go/analysis
Quickfix-Aktionen
Support für quickfix
von go/analysis
für uns quickfix
.
Im go/analysis
Modell generiert der Analysator Diagnosen und Fakten . Die Diagnose wird an die Benutzer gesendet, und die Fakten sind für die Verwendung durch andere Analysegeräte vorgesehen.
Die Diagnose kann eine Reihe von vorgeschlagenen Korrekturen enthalten , von denen jede beschreibt, wie die Quellcodes im angegebenen Bereich geändert werden, um das von der Diagnose festgestellte Problem zu beheben.
Die offizielle Beschreibung finden Sie unter go/analysis/doc/suggested_fixes.md
.
Fazit

Versuchen Sie es mit ruleguard
für Ihre Projekte. Wenn Sie einen Fehler finden oder nach einer neuen Funktion fragen möchten, öffnen Sie ein Problem .
Wenn Sie immer noch Schwierigkeiten haben, eine Anwendung für die ruleguard
finden, finden Sie hier einige Beispiele:
- Implementieren Sie Ihre eigene Diagnose für Go.
- Aktualisieren oder refaktorisieren Sie den Code automatisch mit
-fix
. - Erfassung von
-json
mit -json
Verarbeitung des -json
.
Entwicklungspläne für ruleguard
in naher Zukunft:
Nützliche Links und Ressourcen