Hlavní navigace

Kontrola potenciálních chyb ve zdrojových kódech Go nástroji gosec a go-critic

16. 12. 2021
Doba čtení: 29 minut

Sdílet

 Autor: Go lang
V osmdesáté první části seriálu o programovacím jazyce Go se seznámíme s dvojicí nástrojů určených pro statickou analýzu kódu a pro hledání potenciálních problémů. Tyto nástroje se jmenují gosec a go-critic.

Obsah

1. Kontrola potenciálních chyb ve zdrojových kódech nástroji gosec a go-critic

2. Použití nástroje gosec

3. První demonstrační příklad s několika problematickými rysy

4. Výsledky analýzy zdrojového kódu prvního demonstračního příkladu nástrojem gosec

5. Označení bloků či jednotlivých příkazů, u kterých se mají vybrané problémy ignorovat

6. Druhý demonstrační příklad s několika problematickými rysy

7. Výsledky analýzy zdrojového kódu druhého demonstračního příkladu nástrojem gosec

8. Třetí demonstrační příklad s několika problematickými rysy

9. Výsledky analýzy zdrojového kódu třetího demonstračního příkladu nástrojem gosec

10. Použití nástroje go-critic

11. Čtvrtý demonstrační příklad s několika problematickými rysy

12. Výsledky analýzy zdrojového kódu čtvrtého demonstračního příkladu nástrojem go-critic

13. Pátý demonstrační příklad s několika problematickými rysy s výsledkem jeho analýzy

14. Šestý demonstrační příklad s několika problematickými rysy s výsledkem jeho analýzy

15. Příklad obsahující problematické části detekované oběma nástroji

16. Kód, který nebude vykonán optimálně

17. Kontrola zdrojových kódů knihoven jazyka Go nástrojem go-critic

18. Statistika na závěr

19. Repositář s demonstračními příklady

20. Odkazy na Internetu

1. Kontrola potenciálních chyb ve zdrojových kódech nástroji gosec a go-critic

Samotný programovací jazyk Go je navržen velmi konzervativně, což bylo ostatně patrné i z článku o (ne)používaní generických datových typů, funkcí a metod (což se pravděpodobně změní v Go 2, ostatně si můžete nové vlastnosti vyzkoušet již v Go 1.18 Beta). To pochopitelně některým vývojářům nemusí vyhovovat, na čemž ale ve skutečnosti nemusí být vůbec nic špatného – ideální univerzálně přijímaný programovací jazyk totiž neexistoval, neexistuje a pravděpodobně ani nikdy existovat nebude, protože některé vlastnosti jazyků jsou protichůdné. Ovšem samotný programovací jazyk je jen jednou (i když pochopitelně velmi důležitou) součástí celého ekosystému, který kromě překladače (někdy interpretru) obsahuje i vývojová prostředí a ladicí nástroje, ale i další pomocné nástroje a utility. Mezi tyto nástroje patří i utility určené pro kontrolu kvality zdrojových kódů, odhalování různých chyb nerozpoznaných překladačem, potenciálních chyb, špatně strukturovaného kódu, nedodržování zavedených idiomů atd. Jedním z těchto nástrojů je go-critic, který si dnes popíšeme; další nástroje, i když úžeji zaměřené, již byly popsány v šedesáté části seriálu o Go.

Samostatnou kapitolu tvoří nástroje sloužící k odhalení potenciálních bezpečnostních problémů. I těchto nástrojů existuje relativně velké množství a jedním z nejdůležitějších projektů (navíc stále aktivně vyvíjeným) z této skupiny – nástrojem gosec – se budeme zabývat dnes.

Poznámka: oba dnes popisované nástroje, tedy jak gosec, tak i go-critic, pracují nad AST, nikoli přímo nad zdrojovými kódy Go. Využívá se přitom standardní knihovna jazyka Go, která potřebné nástroje obsahuje. Těmito nástroji se budeme zabývat v samostatném článku.

2. Použití nástroje gosec

V první polovině dnešního článku se budeme zabývat možnostmi, které nám nabízí nástroj nazvaný příznačně gosec. Tento nástroj dokáže najít ve zdrojových kódech potenciální bezpečnostní problémy. Například se to týká konstrukce cest k souborům na základě „podivně“ získaných údajů (třeba přes REST API), skládání SQL dotazů, přímé použití tokenů v programovém kódu (i v testech), popř. použití algoritmů, které již dnes nejsou považovány za bezpečné. Typickým příkladem takového algoritmu je MD5.

Instalace nástroje gosec je přímočará:

$ go install github.com/securego/gosec/v2/cmd/gosec@latest

Pro lepší představu o možnostech nástroje gosec jsou pod tímto odstavcem vypsána všechna pravidla aplikovaná na zdrojové kódy. Tato pravidla jsou určena pro hledání potenciálních bezpečnostních chyb:

Pravidlo Popis pravidla
G101 Look for hard coded credentials
G102 Bind to all interfaces
G103 Audit the use of unsafe block
G104 Audit errors not checked
G106 Audit the use of ssh.InsecureIgnoreHostKey
G107 Url provided to HTTP request as taint input
G108 Profiling endpoint automatically exposed on /debug/pprof
G109 Potential Integer overflow made by strconv.Atoi result conversion to int16/32
G110 Potential DoS vulnerability via decompression bomb
G201 SQL query construction using format string
G202 SQL query construction using string concatenation
G203 Use of unescaped data in HTML templates
G204 Audit use of command execution
G301 Poor file permissions used when creating a directory
G302 Poor file permissions used with chmod
G303 Creating tempfile using a predictable path
G304 File path provided as taint input
G305 File traversal when extracting zip/tar archive
G306 Poor file permissions used when writing to a new file
G307 Deferring a method which returns an error
G401 Detect the usage of DES, RC4, MD5 or SHA1
G402 Look for bad TLS connection settings
G403 Ensure minimum RSA key length of 2048 bits
G404 Insecure random number source (rand)
G501 Import blocklist: crypto/md5
G502 Import blocklist: crypto/des
G503 Import blocklist: crypto/rc4
G504 Import blocklist: net/http/cgi
G505 Import blocklist: crypto/sha1
G601 Implicit memory aliasing of items from a range statement

Některé typické problémy, které lze nalézt ve zdrojových kódech reálných projektů, budou ukázány v navazujících kapitolách.

3. První demonstrační příklad s několika problematickými rysy

Pro zjištění některých vlastností nástroje gosec i chyb, resp. spíše řečeno potenciálních chyb, které dokáže detekovat, použijeme následující demonstrační příklad, který byl získán ze skutečného projektu (a do značné míry byl zkrácen). Funkce readPipelineLogFile má sloužit pro načtení logovacích informací, přičemž každý řádek v logu obsahuje datovou strukturu PipelineLogEntry uloženou ve formátu JSON:

package main
 
import (
        "bufio"
        "encoding/json"
        "log"
        "os"
)
 
// PipelineLogEntry represents one log entry (record) read from log file.
type PipelineLogEntry struct {
        Level    string `json:"levelname"`
        Time     string `json:"asctime"`
        Name     string `json:"name"`
        Filename string `json:"filename"`
        Message  string `json:"message"`
}
 
func readPipelineLogFile(filename string) ([]PipelineLogEntry, error) {
        entries := []PipelineLogEntry{}
 
        file, err := os.Open(filename)
        if err != nil {
                return entries, err
        }
 
        defer file.Close()
 
        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
                entry := PipelineLogEntry{}
                err = json.Unmarshal([]byte(scanner.Text()), &entry)
                if err != nil {
                        log.Println(err)
                } else {
                        entries = append(entries, entry)
                }
        }
 
        if err := scanner.Err(); err != nil {
                return entries, err
        }
 
        return entries, nil
}
 
func main() {
        readPipelineLogFile("foobar")
}
Poznámka: povšimněte si, že se v popisované funkci důsledně kontrolují všechny chyby, které mohou nastat, vstupní soubor s logy se zavírá atd. – mohlo by se tedy zdát, že je vše v naprostém pořádku.

Zdrojový kód tohoto příkladu získáte na adrese https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es1.go.

4. Výsledky analýzy zdrojového kódu prvního demonstračního příkladu nástrojem gosec

Spuštění analýzy nástrojem gosec je snadná – pouze se tomuto nástroji předá název balíčku, popř.  „./…“ (bez uvozovek) pro kontrolu všech balíčků umístěných v aktuálním adresáři či podadresářích:

$ gosec ./...

V případě, že máte nainstalovánu poslední verzi nástroje gosec, měly by výsledky analýzy zdrojového kódu z předchozí kapitoly vypadat takto:

[gosec] 2021/12/13 13:02:14 Including rules: default
[gosec] 2021/12/13 13:02:14 Excluding rules: default
[gosec] 2021/12/13 13:02:14 Import directory: /home/ptisnovs/temp/z
[gosec] 2021/12/13 13:02:14 Checking package: main
[gosec] 2021/12/13 13:02:14 Checking file: /home/ptisnovs/temp/z/gosec_issues_1.go
 
Results:
 
 
[/home/ptisnovs/temp/z/gosec_issues_1.go:22] - G304 (CWE-22): Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
    21:
  > 22:         file, err := os.Open(filename)
    23:         if err != nil {
 
 
 
[/home/ptisnovs/temp/z/gosec_issues_1.go:27] - G307 (CWE-703): Deferring unsafe method "Close" on type "*os.File" (Confidence: HIGH, Severity: MEDIUM)
    26:
  > 27:         defer file.Close()
    28:
 
 
 
[/home/ptisnovs/temp/z/gosec_issues_1.go:48] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)
    47: func main() {
  > 48:         readPipelineLogFile("foobar")
    49: }
 
 
 
Summary:
  Gosec  : dev
  Files  : 1
  Lines  : 49
  Nosec  : 0
  Issues : 3

Alternativně je možné si vyžádat výstup ve formátu JSON, který je snáze strojově zpracovatelný:

$ gosec -fmt=json -out=report.json ./...

V tomto případě bude výsledek vypadat následovně:

{
        "Golang errors": {},
        "Issues": [
                {
                        "severity": "MEDIUM",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "22",
                                "url": "https://cwe.mitre.org/data/definitions/22.html"
                        },
                        "rule_id": "G304",
                        "details": "Potential file inclusion via variable",
                        "file": "/home/ptisnovs/temp/z/gosec_issues_1.go",
                        "code": "21: \n22: \tfile, err := os.Open(filename)\n23: \tif err != nil {\n",
                        "line": "22",
                        "column": "15",
                        "nosec": false,
                        "suppressions": null
                },
                {
                        "severity": "MEDIUM",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "703",
                                "url": "https://cwe.mitre.org/data/definitions/703.html"
                        },
                        "rule_id": "G307",
                        "details": "Deferring unsafe method \"Close\" on type \"*os.File\"",
                        "file": "/home/ptisnovs/temp/z/gosec_issues_1.go",
                        "code": "26: \n27: \tdefer file.Close()\n28: \n",
                        "line": "27",
                        "column": "2",
                        "nosec": false,
                        "suppressions": null
                },
                {
                        "severity": "LOW",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "703",
                                "url": "https://cwe.mitre.org/data/definitions/703.html"
                        },
                        "rule_id": "G104",
                        "details": "Errors unhandled.",
                        "file": "/home/ptisnovs/temp/z/gosec_issues_1.go",
                        "code": "47: func main() {\n48: \treadPipelineLogFile(\"foobar\")\n49: }\n",
                        "line": "48",
                        "column": "2",
                        "nosec": false,
                        "suppressions": null
                }
        ],
        "Stats": {
                "files": 1,
                "lines": 49,
                "nosec": 0,
                "found": 3
        },
        "GosecVersion": "dev"
}

Podívejme se nyní na jednotlivé problémy, které byly detekovány:

[/home/ptisnovs/temp/z/gosec_issues_1.go:22] - G304 (CWE-22): Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
    21:
  > 22:         file, err := os.Open(filename)
    23:         if err != nil {

Toto je obecný problém se střední závažností, který souvisí s tím, že funkci lze zavolat s libovolným řetězcem, který je použit jako název souboru. Pokud by byl řetězec získán způsobem, jenž je dostupný potenciálnímu útočníkovi (například z REST API), mohlo by to vést k závažnějším komplikacím.

[/home/ptisnovs/temp/z/gosec_issues_1.go:27] - G307 (CWE-703): Deferring unsafe method "Close" on type "*os.File" (Confidence: HIGH, Severity: MEDIUM)
    26:
  > 27:         defer file.Close()
    28:

Tento problém má opět střední závažnost a souvisí s tím, že se soubor uzavírá až v bloku defer, což je v některých případech příliš pozdě. Tento problém je velmi pěkně popsán v článku Don't defer Close() on writable files. Tomuto zajímavému problému se budeme věnovat v samostatném textu.

[/home/ptisnovs/temp/z/gosec_issues_1.go:48] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)
    47: func main() {
  > 48:         readPipelineLogFile("foobar")
    49: }

Třetí potenciální problém je detekovatelný i dalšími nástroji – nekontrolujeme návratovou chybovou hodnotu. Jedná se o podobný antipattern, jakým je použití prázdného bloku catch nebo except v jazycích podporujících práci s výjimkami.

5. Označení bloků či jednotlivých příkazů, u kterých se mají vybrané problémy ignorovat

V některých situacích ovšem skutečně potřebujeme (například) vytvořit jméno souboru z nekonstantních řetězců (řetězcových literálů); podobně jako se někdy (!) skládají či formátují SQL dotazy. V takových případech by bylo vhodné mít možnost označit příslušné příkazy nebo bloky, aby je nástroj gosec ignoroval. To je skutečně možné. K tomuto účelu se používají komentáře obsahující slovo „gosec“, za kterým následuje jméno pravidla, které chceme zakázat. Tento komentář může být přidán k příkazu, bloku, před volání funkce atd. Podívejme se na příklad použití – viz podtržené části zdrojového kódu:

package main
 
import (
        "bufio"
        "encoding/json"
        "log"
        "os"
)
 
// PipelineLogEntry represents one log entry (record) read from log file.
type PipelineLogEntry struct {
        Level    string `json:"levelname"`
        Time     string `json:"asctime"`
        Name     string `json:"name"`
        Filename string `json:"filename"`
        Message  string `json:"message"`
}
 
func readPipelineLogFile(filename string) ([]PipelineLogEntry, error) {
        entries := []PipelineLogEntry{}
 
        file, err := os.Open(filename) // #nosec G304
        if err != nil {
                return entries, err
        }
 
        // #nosec G307
        defer file.Close()
 
        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
                entry := PipelineLogEntry{}
                err = json.Unmarshal([]byte(scanner.Text()), &entry)
                if err != nil {
                        log.Println(err)
                } else {
                        entries = append(entries, entry)
                }
        }
 
        if err := scanner.Err(); err != nil {
                return entries, err
        }
 
        return entries, nil
}
 
func main() {
        // #nosec G104
        readPipelineLogFile("foobar")
}

Zdrojový kód tohoto příkladu získáte na adrese https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es1_nosec.go.

Nyní by již kontrola neměla odhalit žádné problémy:

$ gosec ./...
 
[gosec] 2021/12/14 12:37:03 Including rules: default
[gosec] 2021/12/14 12:37:03 Excluding rules: default
[gosec] 2021/12/14 12:37:03 Import directory: /home/ptisnovs/temp/z
[gosec] 2021/12/14 12:37:03 Checking package: main
[gosec] 2021/12/14 12:37:03 Checking file: /home/ptisnovs/temp/z/gosec_issues_1_nosec.go
Results:
 
 
Summary:
  Gosec  : dev
  Files  : 1
  Lines  : 51
  Nosec  : 3
  Issues : 0

Zákaz aplikace pravidel komentářem „// gosec“ lze ovšem zakázat (pokud například nedůvěřujete určitému projektu):

$ gosec -nosec ./...
[gosec] 2021/12/14 12:37:31 Including rules: default
[gosec] 2021/12/14 12:37:31 Excluding rules: default
[gosec] 2021/12/14 12:37:31 Import directory: /home/ptisnovs/temp/z
[gosec] 2021/12/14 12:37:31 Checking package: main
[gosec] 2021/12/14 12:37:31 Checking file: /home/ptisnovs/temp/z/gosec_issues_1_nosec.go
Results:
 
 
[/home/ptisnovs/temp/z/gosec_issues_1_nosec.go:22] - G304 (CWE-22): Potential file inclusion via variable (Confidence: HIGH, Severity: MEDIUM)
    21:
  > 22:         file, err := os.Open(filename) // #nosec G304
    23:         if err != nil {
 
 
 
[/home/ptisnovs/temp/z/gosec_issues_1_nosec.go:28] - G307 (CWE-703): Deferring unsafe method "Close" on type "*os.File" (Confidence: HIGH, Severity: MEDIUM)
    27:         // #nosec G307
  > 28:         defer file.Close()
    29:
 
 
 
[/home/ptisnovs/temp/z/gosec_issues_1_nosec.go:50] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)
    49:         // #nosec G104
  > 50:         readPipelineLogFile("foobar")
    51: }
 
 
 
Summary:
  Gosec  : dev
  Files  : 1
  Lines  : 51
  Nosec  : 0
  Issues : 3

6. Druhý demonstrační příklad s několika problematickými rysy

Ve druhé kapitole jsme si řekli, že nástroj gosec dokáže zjistit i použití algoritmů, které již nejsou považovány za bezpečné. Konkrétně se jedná o následující pravidla:

Pravidlo Popis pravidla
G401 Detect the usage of DES, RC4, MD5 or SHA1
G403 Ensure minimum RSA key length of 2048 bits
G404 Insecure random number source (rand)
G501 Import blocklist: crypto/md5
G502 Import blocklist: crypto/des
G503 Import blocklist: crypto/rc4
G504 Import blocklist: net/http/cgi
G505 Import blocklist: crypto/sha1

Vlastnosti gosec v této oblasti si ověříme následujícím, tentokrát velmi krátkým kódem:

package main
 
import (
        "crypto/md5"
        "fmt"
        "io"
)
 
func main() {
        hash := md5.New()
 
        io.WriteString(hash, "Příliš žluťoučký kůň")
        fmt.Printf("%x", hash.Sum(nil))
}

Zdrojový kód tohoto příkladu získáte na adrese https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es2.go.

7. Výsledky analýzy zdrojového kódu druhého demonstračního příkladu nástrojem gosec

Opět se podívejme na to, jaké výsledky získáme analýzou zdrojového kódu představeného v předchozí kapitole. Spustíme nástroj gosec s parametrem „./…“:

$ gosec ./...

V případě, že je nainstalována poslední verze nástroje gosec, vypíše se trojice potenciálních problémů:

Results:
 
 
[/home/ptisnovs/temp/z/gosec_issues_2.go:10] - G401 (CWE-326): Use of weak cryptographic primitive (Confidence: HIGH, Severity: MEDIUM)
    9: func main() {
  > 10:         hash := md5.New()
    11:
 
 
 
[/home/ptisnovs/temp/z/gosec_issues_2.go:4] - G501 (CWE-327): Blocklisted import crypto/md5: weak cryptographic primitive (Confidence: HIGH, Severity: MEDIUM)
    3: import (
  > 4:  "crypto/md5"
    5:  "fmt"
 
 
 
[/home/ptisnovs/temp/z/gosec_issues_2.go:12] - G104 (CWE-703): Errors unhandled. (Confidence: HIGH, Severity: LOW)
    11:
  > 12:         io.WriteString(hash, "Příliš žluťoučký kůň")
    13:         fmt.Printf("%x", hash.Sum(nil))
 
 
 
Summary:
  Gosec  : dev
  Files  : 1
  Lines  : 14
  Nosec  : 0
  Issues : 3

Všechny tři potenciální problémy jsou snadno pochopitelné – zjistilo se, že se importuje a dokonce i používá algoritmus MD5 a navíc, že není provedena kontrola, zda nedošlo k nějaké chybě při volání funkce io.WriteString (což popravdě řečeno ignoruje relativně velká část programů).

Alternativně pochopitelně opět můžeme žádat výstup ve formátu strojově zpracovatelného formátu JSON, a to nepatrně upraveným zavoláním:

$ gosec -fmt=json -out=report.json ./...

S výsledkem:

{
        "Golang errors": {},
        "Issues": [
                {
                        "severity": "MEDIUM",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "326",
                                "url": "https://cwe.mitre.org/data/definitions/326.html"
                        },
                        "rule_id": "G401",
                        "details": "Use of weak cryptographic primitive",
                        "file": "/home/ptisnovs/temp/z/gosec_issues_2.go",
                        "code": "9: func main() {\n10: \thash := md5.New()\n11: \n",
                        "line": "10",
                        "column": "10",
                        "nosec": false,
                        "suppressions": null
                },
                {
                        "severity": "MEDIUM",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "327",
                                "url": "https://cwe.mitre.org/data/definitions/327.html"
                        },
                        "rule_id": "G501",
                        "details": "Blocklisted import crypto/md5: weak cryptographic primitive",
                        "file": "/home/ptisnovs/temp/z/gosec_issues_2.go",
                        "code": "3: import (\n4: \t\"crypto/md5\"\n5: \t\"fmt\"\n",
                        "line": "4",
                        "column": "2",
                        "nosec": false,
                        "suppressions": null
                },
                {
                        "severity": "LOW",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "703",
                                "url": "https://cwe.mitre.org/data/definitions/703.html"
                        },
                        "rule_id": "G104",
                        "details": "Errors unhandled.",
                        "file": "/home/ptisnovs/temp/z/gosec_issues_2.go",
                        "code": "11: \n12: \tio.WriteString(hash, \"Příliš žluťoučký kůň\")\n13: \tfmt.Printf(\"%x\", hash.Sum(nil))\n",
                        "line": "12",
                        "column": "2",
                        "nosec": false,
                        "suppressions": null
                }
        ],
        "Stats": {
                "files": 1,
                "lines": 14,
                "nosec": 0,
                "found": 3
        },
        "GosecVersion": "dev"
}

Jen pro zajímavost si ukažme, jak se jednotlivá pravidla zakážou – nyní pro celý program, popř. pro příkaz import:

// #nosec G401
// #nosec G104
package main
 
import (
        "crypto/md5" // #nosec G501
        "fmt"
        "io"
)
 
func main() {
        hash := md5.New()
 
        io.WriteString(hash, "Příliš žluťoučký kůň")
        fmt.Printf("%x", hash.Sum(nil))
}

Další kontrola již proběhne bez detekce chyb:

[gosec] 2021/12/14 12:38:39 Including rules: default
[gosec] 2021/12/14 12:38:39 Excluding rules: default
[gosec] 2021/12/14 12:38:39 Import directory: /home/ptisnovs/temp/z
[gosec] 2021/12/14 12:38:39 Checking package: main
[gosec] 2021/12/14 12:38:39 Checking file: /home/ptisnovs/temp/z/gosec_issues_2_nosec.go
Results:
 
 
Summary:
  Gosec  : dev
  Files  : 1
  Lines  : 16
  Nosec  : 2
  Issues : 0

8. Třetí demonstrační příklad s několika problematickými rysy

Třetí demonstrační příklad, který si dnes ukážeme a který budeme analyzovat, není stoprocentně založen na reálných zdrojových kódech, ovšem ukazuje, jak nástroj gosec dokáže odhalit potenciální problémy, které vznikají při skládání SQL dotazů. Předchozí verze nástroje gosec varovaly před jakýmkoli skládáním SQL z nekonstantních řetězců, nová verze je ovšem již chytřejší a některé operace již ignoruje. Nicméně se podívejme na příklad s několika SQL dotazy:

package main
 
import (
        "database/sql"
        "fmt"
        "os"
)
 
func foo(arg string) {
        db, err := sql.Open("sqlite3", ":memory:")
        if err != nil {
                panic(err)
        }
        rows, err := db.Query("SELECT * FROM foo WHERE name = " + arg)
 
        if err != nil {
                panic(err)
        }
        defer rows.Close()
}
 
func bar(arg string) {
        db, err := sql.Open("sqlite3", ":memory:")
        if err != nil {
                panic(err)
        }
 
        query := fmt.Sprintf("select * from foo where name = '%s'", arg)
 
        rows, err := db.Query(query)
        if err != nil {
                panic(err)
        }
        defer rows.Close()
}
 
func main() {
        foo("foo")
        bar("bar")
}

Zdrojový kód tohoto příkladu získáte na adrese https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es3.go.

Poznámka: pokud si příklad chcete přeložit a spustit (k čemuž ovšem není důvod), bude nutné ještě naimportovat ovladač (driver) zajišťující připojení k databázi a komunikaci s databází.

9. Výsledky analýzy zdrojového kódu třetího demonstračního příkladu nástrojem gosec

Zjištění potenciálních problémů ve zdrojovém kódu opět zajistí tento příkaz:

$ gosec ./...

S výsledky:

Results:
 
Golang errors in file: [/home/ptisnovs/temp/z/gocritic_gosec_issues.go]:
 
  > [line 6 : column 2] - "os" imported but not used
 
 
 
[/home/ptisnovs/temp/z/gocritic_gosec_issues.go:28] - G201 (CWE-89): SQL string formatting (Confidence: HIGH, Severity: MEDIUM)
    27:
  > 28:         query := fmt.Sprintf("select * from foo where name = '%s'", arg)
    29:
 
 
 
[/home/ptisnovs/temp/z/gocritic_gosec_issues.go:14] - G202 (CWE-89): SQL string concatenation (Confidence: HIGH, Severity: MEDIUM)
    13:         }
  > 14:         rows, err := db.Query("SELECT * FROM foo WHERE name = " + arg)
    15:
 
 
 
Summary:
  Gosec  : dev
  Files  : 1
  Lines  : 40
  Nosec  : 0
  Issues : 2

Nejzajímavější jsou chyby získané pravidly G201 a G202. První pravidlo zjistilo, že se SQL dotaz získává formátováním řetězce, což může, ale taktéž nemusí být problematické. Druhé pravidlo detekovalo použití argumentu, který je do funkce předán a tedy obecně není možné zaručit, odkud se jeho obsah získá.

Pro úplnost si ještě ukažme výstup z nástroje gosec ve formátu JSON:

{
        "Golang errors": {
                "/home/ptisnovs/temp/z/gocritic_gosec_issues.go": [
                        {
                                "line": 6,
                                "column": 2,
                                "error": "\"os\" imported but not used"
                        }
                ]
        },
        "Issues": [
                {
                        "severity": "MEDIUM",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "89",
                                "url": "https://cwe.mitre.org/data/definitions/89.html"
                        },
                        "rule_id": "G201",
                        "details": "SQL string formatting",
                        "file": "/home/ptisnovs/temp/z/gocritic_gosec_issues.go",
                        "code": "27: \n28: \tquery := fmt.Sprintf(\"select * from foo where name = '%s'\", arg)\n29: \n",
                        "line": "28",
                        "column": "11",
                        "nosec": false,
                        "suppressions": null
                },
                {
                        "severity": "MEDIUM",
                        "confidence": "HIGH",
                        "cwe": {
                                "id": "89",
                                "url": "https://cwe.mitre.org/data/definitions/89.html"
                        },
                        "rule_id": "G202",
                        "details": "SQL string concatenation",
                        "file": "/home/ptisnovs/temp/z/gocritic_gosec_issues.go",
                        "code": "13: \t}\n14: \trows, err := db.Query(\"SELECT * FROM foo WHERE name = \" + arg)\n15: \n",
                        "line": "14",
                        "column": "24",
                        "nosec": false,
                        "suppressions": null
                }
        ],
        "Stats": {
                "files": 1,
                "lines": 40,
                "nosec": 0,
                "found": 2
        },
        "GosecVersion": "dev"
}

10. Použití nástroje go-critic

„There is never too much static code analysis. Try it out.“

Nástroj gosec, jehož základní vlastnosti jsme si ukázali v předchozích kapitolách, je striktně určen pro hledání potenciálních bezpečnostních problémů ve zdrojových kódech. Druhý dnes představený nástroj se jmenuje go-critic a jeho zaměření je mnohem větší, protože slouží jak pro hledání reálných i potenciálních chyb, tak i těch částí kódu, které nemusí být vykonány efektivně. Taktéž ovšem slouží pro detekci kódu, který není napsán idiomaticky. Všechna pravidla, resp. všechny (potenciální) problémy, které tento nástroj dokáže detekovat, jsou vypsány na stránce Checks overview.

Oba dva zmíněné nástroje ovšem mají společný základ, protože provádí statickou analýzu kódu na základě zkonstruovaného AST.

Instalace nástroje go-critic je triviální:

$ GO111MODULE=on go get -v -u github.com/go-critic/go-critic/cmd/gocritic

11. Čtvrtý demonstrační příklad s několika problematickými rysy

Ve čtvrtém demonstračním příkladu, který vypadá zdánlivě zcela v pořádku, je naschvál ponecháno několik problematických rysů, které budou nástrojem go-critic odhaleny:

package main
 
import "fmt"
import "strings"
 
func printMessages(Format string, message1 string, message2 string) {
        //fmt.Printf("%s %s\n", message1, message2)
 
        if len(message1) != 0 && len(message2) != 0 {
                fmt.Printf(Format, strings.Replace(message1, " ", "", -1), message2)
        }
}
 
func main() {
        const fmt = "%s %s\n"
 
        for i := 0; 10 > i; i = i + 1 {
                printMessages(fmt, "Hello ", "world")
        }
}

Zdrojový kód tohoto demonstračního příkladu získáte na adrese https://github.com/tisnik/wcco­de/blob/master/gocritic_is­sues1.go.

12. Výsledky analýzy zdrojového kódu čtvrtého demonstračního příkladu nástrojem go-critic

Nástroj gocritic se spouští s odlišnými parametry. Především je nutné předat parametr/příkaz check a následně lze s využitím parametrů -enable a -disable určit, která pravidla se mají použít a která popř. ne. Můžeme dokonce povolit všechna pravidla s využitím přepínače -enableAll:

$ gocritic check -enableAll ./...

Výsledky pro demonstrační příklad z předchozí kapitoly budou vypadat takto:

./gocritic_issues_1.go:17:22: assignOp: replace `i = i + 1` with `i++`
./gocritic_issues_1.go:6:20: captLocal: `Format' should not be capitalized
./gocritic_issues_1.go:7:2: commentFormatting: put a space between `//` and comment text
./gocritic_issues_1.go:7:2: commentedOutCode: may want to remove commented-out code
./gocritic_issues_1.go:9:5: emptyStringTest: replace `len(message1) != 0` with `message1 != ""`
./gocritic_issues_1.go:9:27: emptyStringTest: replace `len(message2) != 0` with `message2 != ""`
./gocritic_issues_1.go:15:8: importShadow: shadow of imported package 'fmt'
./gocritic_issues_1.go:6:1: paramTypeCombine: func(Format string, message1 string, message2 string) could be replaced with func(Format, message1, message2 string)
./gocritic_issues_1.go:10:22: wrapperFunc: use strings.ReplaceAll method in `strings.Replace(message1, " ", "", -1)`
./gocritic_issues_1.go:17:14: yodaStyleExpr: consider to change order in expression to i <= 10

Většina problémů, resp. určitých nedostatků nalezených v kódu (a to velmi krátkém!) je snadno pochopitelná:

  1. Náhrada složitého výrazu i = i + 1 za idiomatičtější i++
  2. Parametr je vždy lokální a tudíž by měl začínat velkým písmenem
  3. Za znakem uvozujícím komentář se píše mezera
  4. Detekce zakomentovaného kódu – ten nemá co v repositáři pohledávat :-)
  5. Zajímavá a užitečná je detekce neidiomatického testu na prázdný řetězec – emptyStringTest
  6. Dále je konstanta, proměnná či parametr pojmenován stejně jako importovaný balíček – což se mi popravdě stává prakticky neustále
  7. Detekce, že parametry se stejným typem mohou mít uveden typ společně (a to je zrovna u hlaviček funkcí užitečné)
  8. Další velmi užitečná je detekce použití zbytečně univerzálních funkcí namísto funkce speciální – strings.Replace/ReplaceAll
  9. A konečně použití podmínky 10 > i namísto přece jen čitelnější varianty i < = 10

13. Pátý demonstrační příklad s několika problematickými rysy s výsledkem jeho analýzy

Další demonstrační příklad obsahuje velmi zajímavou chybu (která se tam navíc může dostat kdykoli později). Prozatím nebudu prozrazovat jakou – nejprve se na zdrojový kód příkladu podívejte; výsledky analýzy budou uvedeny až pod zdrojovým kódem:

package main
 
import (
        "bufio"
        "encoding/json"
        "log"
        "os"
)
 
// PipelineLogEntry represents one log entry (record) read from log file.
type PipelineLogEntry struct {
        Level    string `json:"levelname"`
        Time     string `json:"asctime"`
        Name     string `json:"name"`
        Filename string `json:"filename"`
        Message  string `json:"message"`
}
 
func readPipelineLogFile(filename string) ([]PipelineLogEntry, error) {
        entries := []PipelineLogEntry{}
 
        file, err := os.Open(filename)
        if err != nil {
                return entries, err
        }
 
        defer func() {
                err := file.Close()
 
                if err != nil {
                        log.Println(err)
                }
        }()
 
        scanner := bufio.NewScanner(file)
        for scanner.Scan() {
                entry := PipelineLogEntry{}
                err = json.Unmarshal([]byte(scanner.Text()), &entry)
                if err != nil {
                        log.Fatal(err)
                } else {
                        entries = append(entries, entry)
                }
        }
 
        if err := scanner.Err(); err != nil {
                return entries, err
        }
 
        return entries, nil
}
 
func main() {
        readPipelineLogFile("foobar")
}

Zdrojový kód tohoto demonstračního příkladu získáte na adrese https://github.com/tisnik/wcco­de/blob/master/gocritic_is­sues2.go.

Pokusme se nyní zjistit potenciální problémy v kódu tohoto příkladu:

$ gocritic check -enableAll ./...

Výsledkem je detekce chyby, která nemusí být na první pohled viditelná – pokud se totiž zavolá funkce log.Fatal, neprovede se již blok (resp. anonymní funkce) definovaná v defer:

./gocritic_issues_2.go:40:4: exitAfterDefer: log.Fatal will exit, and `defer func(){...}(...)` will not run

14. Šestý demonstrační příklad s několika problematickými rysy s výsledkem jeho analýzy

I další demonstrační příklad vypadá zdánlivě neškodně, ovšem na následujících několika řádcích nalezneme neuvěřitelných devět problematických rysů. Nejdříve se opět podívejme na zdrojový kód tohoto příkladu:

package main
 
import "fmt"
 
func new(len int, cap int) ([]int, int) {
        vals := make([]int, len)
        for i := 0; cap > i; i = i + 1 {
                vals = append(vals, i)
                vals = append(vals, i*2)
        }
        return vals, len + cap
}
 
func main() {
        vals, _ := new(0, 010)
        fmt.Println(vals)
        for i := 0; i < len(vals); i++ {
                vals[i] = 0
        }

}

Výsledek běhu nástroje go-critic:

./gocritic_issues_3.go:8:3: appendCombine: can combine chain of 2 appends into one
./gocritic_issues_3.go:7:23: assignOp: replace `i = i + 1` with `i++`
./gocritic_issues_3.go:5:10: builtinShadow: shadowing of predeclared identifier: len
./gocritic_issues_3.go:5:19: builtinShadow: shadowing of predeclared identifier: cap
./gocritic_issues_3.go:5:6: builtinShadowDecl: shadowing of predeclared identifier: new
./gocritic_issues_3.go:15:13: octalLiteral: suspicious octal args in `new(0, 010)`
./gocritic_issues_3.go:5:1: paramTypeCombine: func(len int, cap int) ([]int, int) could be replaced with func(len, cap int) ([]int, int)
./gocritic_issues_3.go:17:2: sliceClear: rewrite as for-range so compiler can recognize this pattern
./gocritic_issues_3.go:5:1: unnamedResult: consider giving a name to these results

Některé z potenciálních problémů už jsme mohli vidět v předchozích kapitolách:

  1. Náhrada složitého výrazu i = i + 1 za idiomatičtější i++
  2. Dále je konstanta, proměnná či parametr pojmenován stejně jako importovaný balíček – což se mi popravdě stává prakticky neustále
  3. Detekce, že parametry se stejným typem mohou mít uveden typ společně (a to je zrovna u hlaviček funkcí užitečné)

To jsou však jen triviality. Zajímavé jsou další problematická místa kódu:

  1. Použití osmičkových hodnot začínajících pouze na nulu a nikoli dvojicí znaků 0o (což je mnohem lepší, protože programátor dává explicitně najevo svoje úmysly). Mimochodem – tyto problémy lze nalézt i přímo ve zdrojových kódech standardní knihovny jazyka Go.
  2. Pokus o vymazání řezu (nebo pole) počítanou smyčkou for, zatímco překladač dokáže rozeznat a optimalizovat smyčku typu for-each

Zdrojový kód tohoto demonstračního příkladu získáte na adrese https://github.com/tisnik/wcco­de/blob/master/gocritic_is­sues3.go.

15. Příklad obsahující problematické části detekované oběma nástroji

Další příklad již známe, protože jsme se s ním setkali v části věnované nástroji gosec. Vyzkoušejme si tedy, jaké potenciální problémy (a zda vůbec jaké) v tomto zdrojovém kódu nalezne go-critic:

package main
 
import (
        "database/sql"
        "fmt"
        "os"
)
 
func foo(arg string) {
        db, err := sql.Open("sqlite3", ":memory:")
        if err != nil {
                panic(err)
        }
        rows, err := db.Query("SELECT * FROM foo WHERE name = " + arg)
 
        if err != nil {
                panic(err)
        }
        defer rows.Close()
}
 
func bar(arg string) {
        db, err := sql.Open("sqlite3", ":memory:")
        if err != nil {
                panic(err)
        }
 
        query := fmt.Sprintf("select * from foo where name = '%s'", arg)
 
        rows, err := db.Query(query)
        if err != nil {
                panic(err)
        }
        defer rows.Close()
}
 
func main() {
        foo("foo")
        bar("bar")
}

Výsledek možná není překvapující – používá se defer přímo na konci funkce, což je zbytečné (i když touto konstrukcí může programátor naznačovat, kdy se má příkaz vykonat):

./gocritic_gosec_issues.go:19:2: unnecessaryDefer: defer rows.Close() is placed just before return
./gocritic_gosec_issues.go:34:2: unnecessaryDefer: defer rows.Close() is placed just before return
Poznámka: kupodivu se zrovna tento (velmi malý) problém objevuje i v produkčním kódu, což je pravděpodobně výsledek postupného přepisu funkcí, kdy se defer postupně posunuje směrem ke konci funkce.

16. Kód, který nebude vykonán optimálně

Užitečné mohou být i informace o tom, že nějaká část programového kódu nebude vykonána optimálně. Můžeme si to ostatně velmi snadno otestovat. Předpokládejme, že v programu pracujeme s touto jednoduchou datovou strukturou:

// KafkaMessageLogEntry represents one log entry (record) read from log file.
type KafkaMessageLogEntry struct {
        Level        string `json:"level"`
        Time         string `json:"time"`
        Message      string `json:"message"`
        Type         string `json:"type"`
        Error        string `json:"error"`
        Topic        string `json:"topic"`
        Offset       int    `json:"offset"`
        Group        string `json:"group"`
        Organization int    `json:"organization"`
        Cluster      string `json:"cluster"`
}

Funkce printReadEntry bude akceptovat tuto datovou strukturu jako parametr:

func printReadEntry(entry KafkaMessageLogEntry) {
        fmt.Printf("%s %s %s %d %d %s\n", entry.Time, entry.Group, entry.Topic, entry.Offset, entry.Organization, entry.Cluster)
}

To ovšem nemusí být optimální, protože nástroj go-critic odvodil, že při každém volání této funkce se bude muset přesunout 144 bajtů na zásobník, takže by mohlo být lepší pouze předat ukazatel na strukturu:

./temp/analyser.go:167:21: hugeParam: entry is heavy (144 bytes); consider passing it by pointer
Poznámka: z hlediska sémantiky je pochopitelně lepší NEpoužívat ukazatel.

V další funkci je detekováno, že se v každé iteraci oněch 144 bajtů zkopíruje do lokální proměnné entry, což opět není optimální:

func filterConsumedMessages(entries []KafkaMessageLogEntry) []KafkaMessageLogEntry {
        consumed := []KafkaMessageLogEntry{}
 
        for _, entry := range entries {
                if entry.Message == "Consumed" && entry.Group != "" {
                        consumed = append(consumed, entry)
                }
        }
        return consumed
}

Výsledek detekce:

./temp/analyser.go:124:2: rangeValCopy: each iteration copies 144 bytes (consider pointers or indexing)

Řešení by mohlo spočívat v tom, že by se vytvořila proměnná typu ukazatel na strukturu – v tomto ohledu bohužel jazyk Go neposkytuje lepší prostředky jak určit, že se sice má iterovat přes hodnoty v poli/řezu, ale stačí nám získat pouze ukazatel na prvek.

17. Kontrola zdrojových kódů knihoven jazyka Go nástrojem go-critic

Pro zajímavost se můžeme pokusit o provedení kontroly zdrojových kódů dodávaných přímo s programovacím jazykem Go. Po instalaci Go se totiž v podadresáři src nachází mj. i zdrojové kódy ke standardním knihovnám, a to včetně jednotkových testů. Analýza s využitím nástrojů gosec a go-critic sice bude nějakou dobu trvat, ale uvidíme, že i přes velký rozsah těchto kódů (necelé dva miliony řádků) je nalezeno jen minimum potenciálních problémů – a to navíc mnohdy „pouze“ v testech, v nichž se někdy musí ohýbat pravidla jak psát efektivní, idiomatický a korektní kód.

18. Statistika na závěr

Zdrojové kódy uložené v adresáři src (standardní, dnes již poněkud zastaralá instalace Go 1.17.1) mají 1903007 řádků a nástroj go-critic v nich nalezl pouze 2861 potenciálních problémů, většinou ovšem jen netypicky zapsaných výrazů. Celou statistiku je možné z nalezených problémů vygenerovat tímto jednoduchým skriptem:

ICTZ23

from collections import Counter
 
counter = Counter()
 
with open("results.txt") as fin:
    for i, line in enumerate(fin):
        type = line.split(" ")[1][:-1]
        counter[type] += 1
 
for cnt, type in counter.most_common(30):
    print(cnt, type)

Následuje tabulka s třiceti nejčastějšími typy problémů. Povšimněte si, že se skutečně většinou jedná o „otočené“ operandy, parametry, jejichž typy lze zapsat jen jednou, popř. o detekci toho, že interní identifikátor se jmenuje stejně jako importovaný balíček:

Test/problém Počet případů
yodaStyleExpr 358
paramTypeCombine 237
importShadow 208
unnamedResult 207
commentedOutCode 166
builtinShadow 166
ifElseChain 145
typeUnparen 128
emptyStringTest 127
singleCaseSwitch 93
octalLiteral 85
captLocal 83
assignOp 69
hugeParam 64
commentFormatting 64
exitAfterDefer 59
preferStringWriter 58
redundantSprint 36
unslice 34
initClause 33
elseif 32
sloppyReassign 31
filepathJoin 26
httpNoBody 23
preferWriteByte 22
unlambda 21
dupImport 18
ptrToRefParam 17
appendCombine 16
nestingReduce 16
Poznámka: zajímavý je výskyt problémů typu exitAfterDefer, který se mimochodem hojně vyskytoval i v našich zdrojových kódech :-)

19. Repositář s demonstračními příklady

Zdrojové kódy všech minule i dnes použitých demonstračních příkladů byly uloženy do staronového Git repositáře, který je dostupný na adrese https://github.com/tisnik/wccode (stále na GitHubu :-). V případě, že nebudete chtít klonovat celý repositář (ten je ovšem – alespoň prozatím – velmi malý, dnes má přibližně stovku kilobajtů), můžete namísto toho použít odkazy na jednotlivé demonstrační příklady, které naleznete v následující tabulce:

# Příklad/soubor Stručný popis Cesta
1 01_missing_package.go nekorektní zdrojový kód, v němž chybí deklarace balíčku https://github.com/tisnik/wcco­de/blob/master/01_missing_pac­kage.go
2 02_parenthesis.go chybně umístěná otevírací bloková závorka https://github.com/tisnik/wcco­de/blob/master/02_parenthe­sis.go
3 03_bad_syntax.go chybně umístěné otevírací i uzavírací blokové závorky https://github.com/tisnik/wcco­de/blob/master/03_bad_syn­tax.go
4 04_before_transform.go zdrojový kód před transformací nástrojem gofmt https://github.com/tisnik/wcco­de/blob/master/04_before_tran­sform.go
5 05_after_transform.go zdrojový kód po transformaci nástrojem gofmt https://github.com/tisnik/wcco­de/blob/master/05_after_tran­sform.go
       
6 06_integer_signed_types_checks.go kontrola celočíselných konstant překladačem https://github.com/tisnik/wcco­de/blob/master/06_integer_sig­ned_types_checks.go
7 07_improper_conversion.go kontrola prováděná při typových konverzích (celočíselné datové typy) https://github.com/tisnik/wcco­de/blob/master/07_improper_con­version.go
8 08_fp_types_checks.go kontrola prováděná při typových konverzích (typy s plovoucí řádovou čárkou) https://github.com/tisnik/wcco­de/blob/master/08_fp_types_chec­ks.go
       
9 09_nil_map.go pokus o zápis do takzvané nulové mapy (nil map) https://github.com/tisnik/wcco­de/blob/master/09_nil_map­.go
10 10_nil_pointer.go pokus o přístup do struktury přes nulový ukazatel (nil pointer) https://github.com/tisnik/wcco­de/blob/master/10_nil_poin­ter.go
       
11 11_unreachable_code.go zdrojový kód, jehož části nejsou dosažitelné https://github.com/tisnik/wcco­de/blob/master/11_unreacha­ble_code.go
12 12_shift.go použití bitového posunu o 70 bitů v 64bitové proměnné https://github.com/tisnik/wcco­de/blob/master/12_shift.go
13 13_printf_checks.go kontrola parametrů funkce fmt.Printf https://github.com/tisnik/wcco­de/blob/master/13_printf_chec­ks.go
14 14_sprintf_checks.go kontrola parametrů funkce fmt.Sprintf i její návratové hodnoty https://github.com/tisnik/wcco­de/blob/master/14_sprintf_chec­ks.go
15 15_read_byte_methods.go kontrola signatury metody ze známého rozhraní https://github.com/tisnik/wcco­de/blob/master/15_read_by­te_methods.go
       
16 16_simple_server.go jednoduchý HTTP server, ne všechny chybové kódy jsou ošetřeny https://github.com/tisnik/wcco­de/blob/master/16_simple_ser­ver.go
17 17_png_output.go zápis do PNG, opět ne všechny chybové kódy jsou ošetřeny https://github.com/tisnik/wcco­de/blob/master/17_png_output­.go
       
18 18_cyclomatic_complexity.go kód pro měření cyklomatické složitosti https://github.com/tisnik/wcco­de/blob/master/18_cycloma­tic_complexity.go
19 19_cyclomatic_complexity.go kód pro měření cyklomatické složitosti https://github.com/tisnik/wcco­de/blob/master/19_cycloma­tic_complexity.go
       
20 gosec_issues1.go zdrojový kód s několika problémy detekovatelnými nástrojem gosec https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es1.go
21 gosec_issues2.go zdrojový kód s několika problémy detekovatelnými nástrojem gosec https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es2.go
22 gosec_issues1_nosec.go použití poznámky #nosec pro různé bloky kódu https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es1_nosec.go
23 gosec_issues2_nosec.go použití poznámky #nosec pro různé bloky kódu https://github.com/tisnik/wcco­de/blob/master/gosec_issu­es2_nosec.go
24 gocritic_issues1.go zdrojový kód s několika problémy detekovatelnými nástrojem go-critic https://github.com/tisnik/wcco­de/blob/master/gocritic_is­sues1.go
25 gocritic_issues2.go zdrojový kód s několika problémy detekovatelnými nástrojem go-critic https://github.com/tisnik/wcco­de/blob/master/gocritic_is­sues2.go
26 gocritic_issues3.go zdrojový kód s několika problémy detekovatelnými nástrojem go-critic https://github.com/tisnik/wcco­de/blob/master/gocritic_is­sues3.go
27 gocritic_gosec_issues.go zdrojový kód s několika problémy detekovatelnými nástroji gosecgo-critic https://github.com/tisnik/wcco­de/blob/master/gocritic_go­sec_issues.go
       
28 stat.py statistika potenciálních problémů nalezených ve zdrojových kódech knihoven pro jazyk Go https://github.com/tisnik/wcco­de/blob/master/stat.py
Poznámka: tyto příklady nebyly zařazeny do repositáře používaného pro příklady z tohoto seriálu, a to mj. i z toho důvodu, aby nějakým omylem neposloužily ke studijním účelům :-)

20. Odkazy na Internetu

  1. Popis příkazu gofmt
    https://pkg.go.dev/cmd/gofmt
  2. Popis příkazu govet
    https://pkg.go.dev/cmd/vet
  3. Repositář nástroje errcheck
    https://github.com/kisielk/errcheck
  4. Repositář nástroje goconst
    https://github.com/jgautheron/goconst
  5. Repositář nástroje gocyclo
    https://github.com/fzipp/gocyclo
  6. Repositář nástroje ineffassign
    https://github.com/gordon­klaus/ineffassign
  7. Repositář nástroje gosec
    https://github.com/securego/gosec
  8. Repositář nástroje go-critic
    https://github.com/go-critic/go-critic
  9. Seznam testů prováděných nástrojem go-critic
    https://go-critic.com/overview
  10. Welcome go-critic
    https://itnext.io/welcome-go-critic-a26b6e30f1c6
  11. Don't defer Close() on writable files
    https://www.joeshaw.org/dont-defer-close-on-writable-files/
  12. 5 Gotchas of Defer in Go — Part I
    https://blog.learngoprogram­ming.com/gotchas-of-defer-in-go-1–8d070894cb01
  13. Golang Guide: A List of Top Golang Frameworks, IDEs & Tools
    https://blog.intelligentbe­e.com/2017/08/14/golang-guide-list-top-golang-frameworks-ides-tools/
  14. What is the point of slice type in Go?
    https://stackoverflow.com/qu­estions/2098874/what-is-the-point-of-slice-type-in-go
  15. The curious case of Golang array and slices
    https://medium.com/@hackintoshrao/the-curious-case-of-golang-array-and-slices-2565491d4335
  16. Introduction to Slices in Golang
    https://www.callicoder.com/golang-slices/
  17. Golang: Understanding ‚null‘ and nil
    https://newfivefour.com/golang-null-nil.html
  18. What does nil mean in golang?
    https://stackoverflow.com/qu­estions/35983118/what-does-nil-mean-in-golang
  19. nils In Go
    https://go101.org/article/nil.html
  20. Go slices are not dynamic arrays
    https://appliedgo.net/slices/
  21. The zero value of a slice is not nil
    https://stackoverflow.com/qu­estions/30806931/the-zero-value-of-a-slice-is-not-nil
  22. Go-tcha: When nil != nil
    https://dev.to/pauljlucas/go-tcha-when-nil–nil-hic
  23. Nils in Go
    https://www.doxsey.net/blog/nils-in-go

Autor článku

Pavel Tišnovský vystudoval VUT FIT a v současné době pracuje ve společnosti Red Hat, kde vyvíjí nástroje pro OpenShift.io.