golangci / golangci-lint

Fast linters runner for Go
https://golangci-lint.run
GNU General Public License v3.0
15.27k stars 1.36k forks source link

Windows: Diff fails with cgo: "day out of range" #4867

Closed mook-as closed 1 month ago

mook-as commented 2 months ago

Welcome

Description of the problem

On Windows, running goimports on a file using cgo (i.e. import "C") produces an error because the time in the diff is zero ("1900-01-00 00:00:00 +0000").

Note that the same code works fine on macOS (using Apple diff) and Linux (diff (GNU diffutils) 3.6, WSL on the same machine).

Code that does not use cgo appear to work fine on Windows as well.

Version of golangci-lint

golangci-lint.exe --version ```console > golangci-lint.exe --version golangci-lint has version 1.59.1 built with go1.22.3 from 1a55854a on 2024-06-09T18:08:33Z ```
diff.exe --version ``` diff (GNU diffutils) 3.6 Copyright (C) 2017 Free Software Foundation, Inc. License GPLv3+: GNU GPL version 3 or later . This is free software: you are free to change and redistribute it. There is NO WARRANTY, to the extent permitted by law. Written by Paul Eggert, Mike Haertel, David Hayes, Richard Stallman, and Len Tower. ```
diff.exe --help ``` Usage: C:\Users\Bob\scoop\apps\diffutils\current\diff.exe [OPTION]... FILES Compare FILES line by line. Mandatory arguments to long options are mandatory for short options too. --normal output a normal diff (the default) -q, --brief report only when files differ -s, --report-identical-files report when two files are the same -c, -C NUM, --context[=NUM] output NUM (default 3) lines of copied context -u, -U NUM, --unified[=NUM] output NUM (default 3) lines of unified context -e, --ed output an ed script -n, --rcs output an RCS format diff -y, --side-by-side output in two columns -W, --width=NUM output at most NUM (default 130) print columns --left-column output only the left column of common lines --suppress-common-lines do not output common lines -p, --show-c-function show which C function each change is in -F, --show-function-line=RE show the most recent line matching RE --label LABEL use LABEL instead of file name and timestamp (can be repeated) -t, --expand-tabs expand tabs to spaces in output -T, --initial-tab make tabs line up by prepending a tab --tabsize=NUM tab stops every NUM (default 8) print columns --suppress-blank-empty suppress space or tab before empty output lines -l, --paginate pass output through 'pr' to paginate it -r, --recursive recursively compare any subdirectories found --no-dereference don't follow symbolic links -N, --new-file treat absent files as empty --unidirectional-new-file treat absent first files as empty --ignore-file-name-case ignore case when comparing file names --no-ignore-file-name-case consider case when comparing file names -x, --exclude=PAT exclude files that match PAT -X, --exclude-from=FILE exclude files that match any pattern in FILE -S, --starting-file=FILE start with FILE when comparing directories --from-file=FILE1 compare FILE1 to all operands; FILE1 can be a directory --to-file=FILE2 compare all operands to FILE2; FILE2 can be a directory -i, --ignore-case ignore case differences in file contents -E, --ignore-tab-expansion ignore changes due to tab expansion -Z, --ignore-trailing-space ignore white space at line end -b, --ignore-space-change ignore changes in the amount of white space -w, --ignore-all-space ignore all white space -B, --ignore-blank-lines ignore changes where lines are all blank -I, --ignore-matching-lines=RE ignore changes where all lines match RE -a, --text treat all files as text --strip-trailing-cr strip trailing carriage return on input --binary read and write data in binary mode -D, --ifdef=NAME output merged file with '#ifdef NAME' diffs --GTYPE-group-format=GFMT format GTYPE input groups with GFMT --line-format=LFMT format all input lines with LFMT --LTYPE-line-format=LFMT format LTYPE input lines with LFMT These format options provide fine-grained control over the output of diff, generalizing -D/--ifdef. LTYPE is 'old', 'new', or 'unchanged'. GTYPE is LTYPE or 'changed'. GFMT (only) may contain: %< lines from FILE1 %> lines from FILE2 %= lines common to FILE1 and FILE2 %[-][WIDTH][.[PREC]]{doxX}LETTER printf-style spec for LETTER LETTERs are as follows for new group, lower case for old group: F first line number L last line number N number of lines = L-F+1 E F-1 M L+1 %(A=B?T:E) if A equals B then T else E LFMT (only) may contain: %L contents of line %l contents of line, excluding any trailing newline %[-][WIDTH][.[PREC]]{doxX}n printf-style spec for input line number Both GFMT and LFMT may contain: %% % %c'C' the single character C %c'\OOO' the character with octal code OOO C the character C (other characters represent themselves) -d, --minimal try hard to find a smaller set of changes --horizon-lines=NUM keep NUM lines of the common prefix and suffix --speed-large-files assume large files and many scattered small changes --color[=WHEN] colorize the output; WHEN can be 'never', 'always', or 'auto' (the default) --palette=PALETTE the colors to use when --color is active; PALETTE is a colon-separated list of terminfo capabilities --help display this help and exit -v, --version output version information and exit FILES are 'FILE1 FILE2' or 'DIR1 DIR2' or 'DIR FILE' or 'FILE DIR'. If --from-file or --to-file is given, there are no restrictions on FILE(s). If a FILE is '-', read standard input. Exit status is 0 if inputs are the same, 1 if different, 2 if trouble. Report bugs to: bug-diffutils@gnu.org GNU diffutils home page: General help using GNU software: ```

Configuration

golangci-lint.exe --verbose run --no-config --disable-all --enable=goimports .

Go environment

```console > go version go version go1.22.3 windows/amd64 > go env set GO111MODULE= set GOARCH=amd64 set GOBIN= set GOCACHE=C:\Users\Bob\AppData\Local\go-build set GOENV=C:\Users\Bob\AppData\Roaming\go\env set GOEXE=.exe set GOEXPERIMENT= set GOFLAGS= set GOHOSTARCH=amd64 set GOHOSTOS=windows set GOINSECURE= set GOMODCACHE=C:\Users\Bob\go\pkg\mod set GONOPROXY= set GONOSUMDB= set GOOS=windows set GOPATH=C:\Users\Bob\go set GOPRIVATE= set GOPROXY=https://proxy.golang.org,direct set GOROOT=C:\Users\Bob\scoop\apps\go\current set GOSUMDB=sum.golang.org set GOTMPDIR= set GOTOOLCHAIN=auto set GOTOOLDIR=C:\Users\Bob\scoop\apps\go\current\pkg\tool\windows_amd64 set GOVCS= set GOVERSION=go1.22.3 set GCCGO=gccgo set GOAMD64=v1 set AR=ar set CC=gcc set CXX=g++ set CGO_ENABLED=1 set GOMOD=C:\junk\go.mod set GOWORK= set CGO_CFLAGS=-O2 -g set CGO_CPPFLAGS= set CGO_CXXFLAGS=-O2 -g set CGO_FFLAGS=-O2 -g set CGO_LDFLAGS=-O2 -g set PKG_CONFIG=pkg-config set GOGCCFLAGS=-m64 -mthreads -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=C:\Users\Bob\AppData\Local\Temp\go-build2143475260=/tmp/go-build -gno-record-gcc-switches ```

Verbose output of running

```console > $env:GL_DEBUG="loader,runner,goimports" > golangci-lint.exe cache clean > golangci-lint.exe --verbose run --no-config --disable-all --enable=goimports . level=info msg="golangci-lint has version 1.59.1 built with go1.22.3 from 1a55854a on 2024-06-09T18:08:33Z" level=info msg="[lintersdb] Active 1 linters: [goimports]" level=debug msg="[loader] Built loader args are [.]" level=debug msg="[loader] 90.6508ms for GOROOT=C:\\Users\\Bob\\scoop\\apps\\go\\current GOPATH= GO111MODULE=off GOPROXY= PWD=D:\\junk go list -e -f {{context.ReleaseTags}} -- unsafe" level=debug msg="[loader] 849.2045ms for GOROOT=C:\\Users\\Bob\\scoop\\apps\\go\\current GOPATH= GO111MODULE= GOPROXY= PWD=D:\\junk go list -e -json=Name,ImportPath,Error,Dir,GoFiles,IgnoredGoFiles,IgnoredOtherFiles,CFiles,CgoFiles,CXXFiles,MFiles,HFi les,FFiles,SFiles,SwigFiles,SwigCXXFiles,SysoFiles,TestGoFiles,XTestGoFiles,CompiledGoFiles,Export -compiled=true -test=true -export=false -deps=false -find=false -pgo=off -- ." level=debug msg="[loader] loaded 1 pkgs" level=debug msg="[loader] Loaded pkg #0: ID=github.com/mook-as/junk GoFiles=[D:\\junk\\main.go] CompiledGoFiles=[C:\\Users\\Bob\\AppData\\Local\\go-build\\d3\\d3471a4721bbfc7ce5e462826250c96bf7de28043ff87fd6607146c235fd0b75-d C:\\Users\\Bob\\AppData\ \Local\\go-build\\79\\79f5e1f0832f2d1793668a71558814fe207fb3e58863f0cfa3f51343a1af0534-d C:\\Users\\Bob\\AppData\\Local\\go-build\\0e\\0eece15e335c3eb4a6806dc4c9e12a42f39a530fe57f932858c29ed4b78a256f-d] Syntax=[]" level=info msg="[loader] Go packages loading at mode 7 (compiled_files|files|name) took 1.0404772s" level=debug msg="[loader] package with tests: map[string]bool{}" level=info msg="[runner/filename_unadjuster] Pre-built 0 adjustments in 568.3µs" level=info msg="[linters_context/goanalysis] analyzers took 76.2997ms with top 10 stages: goimports: 76.2394ms, typecheck: 60.3µs" level=warning msg="[runner] Can't run linter goanalysis_metalinter: goimports: can't extract issues from gofmt diff output \"--- C:/Users/Bob/AppData/Local/go-build/d3/d3471a4721bbfc7ce5e462826250c96bf7de28043ff87fd6607146c235fd0b75-d.orig\\t1900-01-00 00:00:00 +0000\\r\\n+++ C:/Users/Mook/AppData/Local/go-build/d3/d3471a4721bbfc7ce5e462826250c96bf7de28043ff87fd6607146c235fd0b75-d\\t1900-01-00 00:00:00 +0000\\r\\n@@ -4,22 +4,28 @@\\r\\n \\r\\n package main\\r\\n \\r\\n-import \\\"unsafe\\\"\\r\\n+import (\\r\\n+\\t\\\"syscall\\\"\\r\\n+\\t\\\"unsafe \\\"\\r\\n \\r\\n-import \\\"syscall\\\"\\r\\n-\\r\\n-import _cgopackage \\\"runtime/cgo\\\"\\r\\n+\\t_cgopackage \\\"runtime/cgo\\\"\\r\\n+)\\r\\n \\r\\n type _ _cgopackage.Incomplete\\r\\n+\\r\\n var _ syscall.Errno\\r\\n+\\r\\n func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Pointe r { return ptr }\\r\\n \\r\\n //go:linkname _Cgo_always_false runtime.cgoAlwaysFalse\\r\\n var _Cgo_always_false bool\\r\\n+\\r\\n //go:linkname _Cgo_use runtime.cgoUse\\r\\n func _Cgo_use(interface{})\\r\\n+\\r\\n //go:linkname _Cgo_no_callback runtime.cgoNoCallback\\r\\n func _Cgo_no_callback(bool)\\r\\n+\\r\\n type _Ctype_ulong uint32\\r\\n \\r\\n type _Ctype_void [0]byte\\r\\n@@ -39,8 +45,8 @@\\r\\n //go:linkname _cgoexp_af0891677fe5_Foo _cgoexp_af0891677fe5_Foo\\r\\n //go:cgo_export_static _cgoexp_af0891677fe5_Foo\\r\\n func _cgoexp_af0891677fe5 _Foo(a *struct {\\r\\n-\\t\\tp0 _Ctype_ulong\\r\\n-\\t\\tr0 _Ctype_ulong\\r\\n-\\t}) {\\r\\n+\\tp0 _Ctype_ulong\\r\\n+\\tr0 _Ctype_ulong\\r\\n+}) {\\r\\n \\ta.r0 = Foo(a.p0)\\r\\n }\\r\\n\": can't parse patch: parsing time \"1900-01-00 00:00:00 +0000\": day out of range" level=info msg="[runner] processing took 0s with stages: exclude: 0s, max_per_file_from_linter: 0s, fixer: 0s, sort_results: 0s, cgo: 0s, filename_unadjuster: 0s, invalid_issue: 0s, path_prettifier: 0s, skip_files: 0s, identifier_marker: 0s, skip_dirs: 0s, max_same_issues: 0s, max_from_linter: 0s, source_code: 0s, path_prefixer: 0s, autogenerated_exclude: 0s, exclude-rules: 0s, nolint: 0s, uniq_by_line: 0s, diff: 0s, path_shortener: 0s, severity-rules: 0s" level=info msg="[runner] linters took 165.4926ms with stages: goanalysis_metalinter: 164.0954ms" level=error msg="Running error: can't run linter goanalysis_metalinter\ngoimports: can't extract issues from gofmt diff output \"--- C:/Users/Bob/AppData/Local/go-build/d3/d3471a4721bbfc7ce5e462826250c96bf7de28043ff87fd6607146c235fd0b75-d.orig\\t1900-01-00 00:00:00 +0000\\r\\n +++ C:/Users/Bob/AppData/Local/go-build/d3/d3471a4721bbfc7ce5e462826250c96bf7de28043ff87fd6607146c235fd0b75-d\\t1900-01-00 00:00:00 +0000\\r\\n@@ -4,22 +4,28 @@\\r\\n \\r\\n package main\\r\\n \\r\\n-import \\\"unsafe\\\"\\r\\n+import (\\r\\n+\\t\\\"syscall\\\"\\r\\n+\\t\\\"un safe\\\"\\r\\n \\r\\n-import \\\"syscall\\\"\\r\\n-\\r\\n-import _cgopackage \\\"runtime/cgo\\\"\\r\\n+\\t_cgopackage \\\"runtime/cgo\\\"\\r\\n+)\\r\\n \\r\\n type _ _cgopackage.Incomplete\\r\\n+\\r\\n var _ syscall.Errno\\r\\n+\\r\\n func _Cgo_ptr(ptr unsafe.Pointer) unsafe.Po inter { return ptr }\\r\\n \\r\\n //go:linkname _Cgo_always_false runtime.cgoAlwaysFalse\\r\\n var _Cgo_always_false bool\\r\\n+\\r\\n //go:linkname _Cgo_use runtime.cgoUse\\r\\n func _Cgo_use(interface{})\\r\\n+\\r\\n //go:linkname _Cgo_no_callback runtime.cgoNoCallback\\r\\n func _Cgo_no_callback(bool)\\r\\n+\\r\\n type _Ctype_ulong uint32\\r\\n \\r\\n type _Ctype_void [0]byte\\r\\n@@ -39,8 +45,8 @@\\r\\n //go:linkname _cgoexp_af0891677fe5_Foo _cgoexp_af0891677fe5_Foo\\r\\n //go:cgo_export_static _cgoexp_af0891677fe5_Foo\\r\\n func _cgoexp_af089167 7fe5_Foo(a *struct {\\r\\n-\\t\\tp0 _Ctype_ulong\\r\\n-\\t\\tr0 _Ctype_ulong\\r\\n-\\t}) {\\r\\n+\\tp0 _Ctype_ulong\\r\\n+\\tr0 _Ctype_ulong\\r\\n+}) {\\r\\n \\ta.r0 = Foo(a.p0)\\r\\n }\\r\\n\": can't parse patch: parsing time \"1900-01-00 00:00:00 +0000\": day out of range" level=info msg="Memory: 15 samples, avg is 27.2MB, max is 27.3MB" level=info msg="Execution took 1.3401862s" ```

A minimal reproducible example or link to a public repository

https://github.com/mook-as/junk/tree/ad9838066794145336d89b4f8e549216022d0883 It's a very tiny program that uses cgo.

Validation

Supporter

boring-cyborg[bot] commented 2 months ago

Hey, thank you for opening your first Issue ! 🙂 If you would like to contribute we have a guide for contributors.

ldez commented 2 months ago

Hello,

you checked:

  • [X] Yes, I've tried with the standalone linter if available (e.g., gocritic, go vet, etc.).

can you give the output of goimports?

mook-as commented 2 months ago

Oh, sorry about that; I missed the output.

> goimports.exe -v -d .                            
2024/07/09 17:36:30.755410 fixImports(filename="main.go"), abs="D:\\junk\\main.go", srcDir="D:\\junk" ...

(That was the end of the output. It returned success.)

FWIW:

> go version -m $(Get-Command goimports.exe).Source
C:\Users\Bob\go\bin\goimports.exe: go1.22.3
        path    golang.org/x/tools/cmd/goimports
        mod     golang.org/x/tools      v0.23.0 h1:SGsXPZ+2l4JsgaCKkx+FQ9YZ5XEtA1GZYuoDjenLjvg=
        dep     golang.org/x/mod        v0.19.0 h1:fEdghXQSo20giMthA7cd28ZC+jts4amQ3YMXiP5oMQ8=
        dep     golang.org/x/sync       v0.7.0  h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
        build   -buildmode=exe
        build   -compiler=gc
        build   DefaultGODEBUG=httplaxcontentlength=1,httpmuxgo121=1,panicnil=1,tls10server=1,tlsrsakex=1,tlsunsafeekm=1
        build   CGO_ENABLED=1
        build   CGO_CFLAGS=
        build   CGO_CPPFLAGS=
        build   CGO_CXXFLAGS=
        build   CGO_LDFLAGS=
        build   GOARCH=amd64
        build   GOOS=windows
        build   GOAMD64=v1
ldez commented 2 months ago

Technically the problem is related to the fact the day 0 of January doesn't exist. But why diff, on Windows, produces that? :thinking:

Internally, the diff is produced based on real files (temp files), so there is a real date. And cgo has no impact on that part.

ldez commented 2 months ago

Are you using a real machine? Is the system date working on this machine?

:thinking: I think you have a problem somewhere else because it's just a call to diff on 2 existing files. It's independent of cgo or golangci-lint itself. golangci-lint just read the patch produced by diff.

The call to diff (there is no .exe extension): diff -u file01 file02

Can you call diff without the .exe extension?

I think the diff command, on your system, is an alias to a PowerShell command and not the real diff command.

mook-as commented 1 month ago

Yes, I am on a real physical machine with working time (note that the goimports output had a timestamp).

If I uninstall diff.exe, the output changes, so I'm using the correct binary:

> ./golangci-lint.exe --verbose run --no-config --disable-all --enable=goimports .
level=info msg="golangci-lint has version 1.59.1 built with go1.22.3 from 1a55854a on 2024-06-09T18:08:33Z"
level=info msg="[lintersdb] Active 1 linters: [goimports]"
level=info msg="[loader] Go packages loading at mode 7 (files|compiled_files|name) took 998.7919ms"
level=info msg="[runner/filename_unadjuster] Pre-built 0 adjustments in 615.2µs"
level=info msg="[linters_context/goanalysis] analyzers took 32.2084ms with top 10 stages: goimports: 32.2084ms, typecheck: 0s"
level=warning msg="[runner] Can't run linter goanalysis_metalinter: goimports: error computing diff: exec: \"diff\": executable file not found in %PATH%"
level=info msg="[runner] processing took 0s with stages: max_same_issues: 0s, max_from_linter: 0s, severity-rules: 0s, filename_unadjuster: 0s, nolint: 0s, max_per_file_from_linter: 0s, source_code: 0s, fixer: 0s, path_prefixer: 0s, sort_results: 0s, skip_files: 0s, invalid_iss
ue: 0s, diff: 0s, path_shortener: 0s, cgo: 0s, skip_dirs: 0s, autogenerated_exclude: 0s, identifier_marker: 0s, exclude: 0s, exclude-rules: 0s, uniq_by_line: 0s, path_prettifier: 0s"
level=info msg="[runner] linters took 34.1668ms with stages: goanalysis_metalinter: 34.0214ms"
level=error msg="Running error: can't run linter goanalysis_metalinter\ngoimports: error computing diff: exec: \"diff\": executable file not found in %PATH%"
level=info msg="Memory: 13 samples, avg is 27.0MB, max is 27.0MB"
level=info msg="Execution took 1.151631s"

Note that PowerShell aliases are like bash aliases: they are not real files, so golangci-lint can't actually call it (without explicitly invoking PowerShell).

Digging into this more, though, I think it might just be my diff.exe being broken: it returns the same date on any file. Using the diff.exe from the git installation (i.e. msys2) works fine.

Sorry for the distraction, it's all my fault!