Skip to content

Commit 3d474c8

Browse files
committed
internal/lsp/diff: new diff implementation to replace go-diff
The new implementation is based on Myers' paper, and is in the package diff/lcs. There is a new option newDiff, that can be set to 'old', 'new', or 'both'. The default is 'both', although that may not be the right choice for a release. See gopls/hooks/diff.go. 'both' runs both the old and new diff algorithm and saves some statistics in a file in os.Tempdir(), When (or if) the new code becomes the default, this logging (and some internal checking) will be removed. The new implementation has internal checking, which currently panics. The code in gopls/hooks/diff.go tries to save an encrypted (for privacy) version of the failing input. The package diff/myers has not been replaced, but it could be. Fixes golang/go#52966 Change-Id: Id38d76ed383c4330d9373580561765b5a2412587 Reviewed-on: https://go-review.googlesource.com/c/tools/+/396855 Reviewed-by: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> Reviewed-by: Alan Donovan <adonovan@google.com> Run-TryBot: Peter Weinberger <pjw@google.com>
1 parent a2a2477 commit 3d474c8

File tree

19 files changed

+1804
-47
lines changed

19 files changed

+1804
-47
lines changed

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
module golang.org/x/tools
22

3-
go 1.17
3+
go 1.18
44

55
require (
66
github.com/yuin/goldmark v1.4.1
77
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
88
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f
99
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
10-
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654
10+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
1111
golang.org/x/text v0.3.7
1212
)

go.sum

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,12 @@
11
github.com/yuin/goldmark v1.4.1 h1:/vn0k+RBvwlxEmP5E7SZMqNxPhfMVFEJiykr15/0XKM=
22
github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
3-
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
4-
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
53
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
64
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
7-
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
8-
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
95
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
106
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
11-
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
127
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
138
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
14-
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
15-
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
16-
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
17-
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
18-
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 h1:id054HUawV2/6IGm2IV8KZQjqtwAOo2CYlOToYqa0d0=
19-
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
20-
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
21-
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
22-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
23-
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
24-
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
25-
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
9+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
10+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
2611
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
2712
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
28-
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
29-
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
30-
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

gopls/doc/settings.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -452,6 +452,16 @@ Default: `false`.
452452

453453
<!-- END User: DO NOT MANUALLY EDIT THIS SECTION -->
454454

455+
#### **newDiff** *string*
456+
457+
newDiff enables the new diff implementation. If this is "both",
458+
for now both diffs will be run and statistics will be generateted in
459+
a file in $TMPDIR. This is a risky setting; help in trying it
460+
is appreciated. If it is "old" the old implementation is used,
461+
and if it is "new", just the new implementation is used.
462+
463+
Default: 'old'.
464+
455465
## Code Lenses
456466

457467
These are the code lenses that `gopls` currently supports. They can be enabled

gopls/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ require (
88
github.com/jba/templatecheck v0.6.0
99
github.com/sergi/go-diff v1.1.0
1010
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
11-
golang.org/x/sys v0.0.0-20220209214540-3681064d5158
11+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
1212
golang.org/x/tools v0.1.11-0.20220523181440-ccb10502d1a5
1313
golang.org/x/vuln v0.0.0-20220718121659-b9a3ad919698
1414
honnef.co/go/tools v0.3.2
@@ -20,7 +20,7 @@ require (
2020
github.com/BurntSushi/toml v1.0.0 // indirect
2121
github.com/google/safehtml v0.0.2 // indirect
2222
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e // indirect
23-
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
23+
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
2424
golang.org/x/text v0.3.7 // indirect
2525
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
2626
)

gopls/go.sum

Lines changed: 2 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,14 @@
1-
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
2-
github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
31
github.com/BurntSushi/toml v1.0.0 h1:dtDWrepsVPfW9H/4y7dDgFc2MBUSeJhlaDtK13CxFlU=
42
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
53
github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI=
6-
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
74
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
85
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
96
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
107
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
118
github.com/frankban/quicktest v1.14.2 h1:SPb1KFFmM+ybpEjPUhCCkZOM5xlovT5UbrMvWnXyBns=
129
github.com/frankban/quicktest v1.14.2/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
13-
github.com/google/go-cmdtest v0.4.0/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
14-
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
15-
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
1610
github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
1711
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
18-
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
1912
github.com/google/safehtml v0.0.2 h1:ZOt2VXg4x24bW0m2jtzAOkhoXV0iM8vNKc0paByCZqM=
2013
github.com/google/safehtml v0.0.2/go.mod h1:L4KWwDsUJdECRAEpZoBn3O64bQaywRscowZjJAzjHnU=
2114
github.com/jba/printsrc v0.2.2 h1:9OHK51UT+/iMAEBlQIIXW04qvKyF3/vvLuwW/hL8tDU=
@@ -47,8 +40,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
4740
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM=
4841
golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk=
4942
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
50-
golang.org/x/mod v0.6.0-dev.0.20211013180041-c96bc1413d57/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
51-
golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY=
5243
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s=
5344
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
5445
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -61,18 +52,15 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
6152
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6253
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
6354
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
64-
golang.org/x/sys v0.0.0-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
65-
golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
66-
golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
6755
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
56+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
57+
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
6858
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
6959
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
7060
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
7161
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
7262
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
7363
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
74-
golang.org/x/vuln v0.0.0-20220503210553-a5481fb0c8be h1:jokAF1mfylAi1iTQx7C44B7vyXUcSEMw8eDv0PzNu8s=
75-
golang.org/x/vuln v0.0.0-20220503210553-a5481fb0c8be/go.mod h1:twca1SxmF6/i2wHY/mj1vLIkkHdp+nil/yA32ZOP4kg=
7664
golang.org/x/vuln v0.0.0-20220613164644-4eb5ba49563c h1:r5bbIROBQtRRgoutV8Q3sFY58VzHW6jMBYl48ANSyS4=
7765
golang.org/x/vuln v0.0.0-20220613164644-4eb5ba49563c/go.mod h1:UZshlUPxXeGUM9I14UOawXQg6yosDE9cr1vKY/DzgWo=
7866
golang.org/x/vuln v0.0.0-20220718121659-b9a3ad919698 h1:9lgpkUgjzoIcZYp7/UPFO/0jIlYcokcEjqWm0hj9pzE=
@@ -88,14 +76,10 @@ gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
8876
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
8977
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
9078
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
91-
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
92-
honnef.co/go/tools v0.3.0 h1:2LdYUZ7CIxnYgskbUZfY7FPggmqnh6shBqfWa8Tn3XU=
93-
honnef.co/go/tools v0.3.0/go.mod h1:vlRD9XErLMGT+mDuofSr0mMMquscM/1nQqtRSsh6m70=
9479
honnef.co/go/tools v0.3.2 h1:ytYb4rOqyp1TSa2EPvNVwtPQJctSELKaMyLfqNP4+34=
9580
honnef.co/go/tools v0.3.2/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw=
9681
mvdan.cc/gofumpt v0.3.0 h1:kTojdZo9AcEYbQYhGuLf/zszYthRdhDNDUi2JKTxas4=
9782
mvdan.cc/gofumpt v0.3.0/go.mod h1:0+VyGZWleeIj5oostkOex+nDBA0eyavuDnDusAJ8ylo=
9883
mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5 h1:Jh3LAeMt1eGpxomyu3jVkmVZWW2MxZ1qIIV2TZ/nRio=
99-
mvdan.cc/unparam v0.0.0-20211214103731-d0ef000c54e5/go.mod h1:b8RRCBm0eeiWR8cfN88xeq2G5SG3VKGO+5UPWi5FSOY=
10084
mvdan.cc/xurls/v2 v2.4.0 h1:tzxjVAj+wSBmDcF6zBB7/myTy3gX9xvi8Tyr28AuQgc=
10185
mvdan.cc/xurls/v2 v2.4.0/go.mod h1:+GEjq9uNjqs8LQfM9nVnM8rff0OQ5Iash5rzX+N1CSg=

gopls/internal/hooks/diff.go

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,177 @@
55
package hooks
66

77
import (
8+
"crypto/rand"
9+
"encoding/json"
810
"fmt"
11+
"io"
12+
"log"
13+
"math/big"
14+
"os"
15+
"path/filepath"
16+
"runtime"
17+
"strings"
18+
"sync"
19+
"time"
20+
"unicode"
921

1022
"github.com/sergi/go-diff/diffmatchpatch"
1123
"golang.org/x/tools/internal/lsp/diff"
1224
"golang.org/x/tools/internal/span"
1325
)
1426

27+
// structure for saving information about diffs
28+
// while the new code is being rolled out
29+
type diffstat struct {
30+
Before, After int
31+
Oldedits, Newedits int
32+
Oldtime, Newtime time.Duration
33+
Stack string
34+
Msg string `json:",omitempty"` // for errors
35+
Ignored int `json:",omitempty"` // numbr of skipped records with 0 edits
36+
}
37+
38+
var (
39+
mu sync.Mutex // serializes writes and protects ignored
40+
difffd io.Writer
41+
ignored int // lots of the diff calls have 0 diffs
42+
)
43+
44+
var fileonce sync.Once
45+
46+
func (s *diffstat) save() {
47+
// save log records in a file in os.TempDir().
48+
// diff is frequently called with identical strings, so
49+
// these are somewhat compressed out
50+
fileonce.Do(func() {
51+
fname := filepath.Join(os.TempDir(), fmt.Sprintf("gopls-diff-%x", os.Getpid()))
52+
fd, err := os.Create(fname)
53+
if err != nil {
54+
// now what?
55+
}
56+
difffd = fd
57+
})
58+
59+
mu.Lock()
60+
defer mu.Unlock()
61+
if s.Oldedits == 0 && s.Newedits == 0 {
62+
if ignored < 15 {
63+
// keep track of repeated instances of no diffs
64+
// but only print every 15th
65+
ignored++
66+
return
67+
}
68+
s.Ignored = ignored + 1
69+
} else {
70+
s.Ignored = ignored
71+
}
72+
ignored = 0
73+
// it would be really nice to see why diff was called
74+
_, f, l, ok := runtime.Caller(2)
75+
if ok {
76+
var fname string
77+
fname = filepath.Base(f) // diff is only called from a few places
78+
s.Stack = fmt.Sprintf("%s:%d", fname, l)
79+
}
80+
x, err := json.Marshal(s)
81+
if err != nil {
82+
log.Print(err) // failure to print statistics should not stop gopls
83+
}
84+
fmt.Fprintf(difffd, "%s\n", x)
85+
}
86+
87+
// save encrypted versions of the broken input and return the file name
88+
// (the saved strings will have the same diff behavior as the user's strings)
89+
func disaster(before, after string) string {
90+
// encrypt before and after for privacy. (randomized monoalphabetic cipher)
91+
// got will contain the substitution cipher
92+
// for the runes in before and after
93+
got := map[rune]rune{}
94+
for _, r := range before {
95+
got[r] = ' ' // value doesn't matter
96+
}
97+
for _, r := range after {
98+
got[r] = ' '
99+
}
100+
repl := initrepl(len(got))
101+
i := 0
102+
for k := range got { // randomized
103+
got[k] = repl[i]
104+
i++
105+
}
106+
// use got to encrypt before and after
107+
subst := func(r rune) rune { return got[r] }
108+
first := strings.Map(subst, before)
109+
second := strings.Map(subst, after)
110+
111+
// one failure per session is enough, and more private.
112+
// this saves the last one.
113+
fname := fmt.Sprintf("%s/gopls-failed-%x", os.TempDir(), os.Getpid())
114+
fd, err := os.Create(fname)
115+
defer fd.Close()
116+
_, err = fd.Write([]byte(fmt.Sprintf("%s\n%s\n", string(first), string(second))))
117+
if err != nil {
118+
// what do we tell the user?
119+
return ""
120+
}
121+
// ask the user to send us the file, somehow
122+
return fname
123+
}
124+
125+
func initrepl(n int) []rune {
126+
repl := make([]rune, 0, n)
127+
for r := rune(0); len(repl) < n; r++ {
128+
if unicode.IsLetter(r) || unicode.IsNumber(r) {
129+
repl = append(repl, r)
130+
}
131+
}
132+
// randomize repl
133+
rdr := rand.Reader
134+
lim := big.NewInt(int64(len(repl)))
135+
for i := 1; i < n; i++ {
136+
v, _ := rand.Int(rdr, lim)
137+
k := v.Int64()
138+
repl[i], repl[k] = repl[k], repl[i]
139+
}
140+
return repl
141+
}
142+
143+
// BothDiffs edits calls both the new and old diffs, checks that the new diffs
144+
// change before into after, and attempts to preserve some statistics.
145+
func BothDiffs(uri span.URI, before, after string) (edits []diff.TextEdit, err error) {
146+
// The new diff code contains a lot of internal checks that panic when they
147+
// fail. This code catches the panics, or other failures, tries to save
148+
// the failing example (and ut wiykd ask the user to send it back to us, and
149+
// changes options.newDiff to 'old', if only we could figure out how.)
150+
stat := diffstat{Before: len(before), After: len(after)}
151+
now := time.Now()
152+
Oldedits, oerr := ComputeEdits(uri, before, after)
153+
if oerr != nil {
154+
stat.Msg += fmt.Sprintf("old:%v", oerr)
155+
}
156+
stat.Oldedits = len(Oldedits)
157+
stat.Oldtime = time.Since(now)
158+
defer func() {
159+
if r := recover(); r != nil {
160+
disaster(before, after)
161+
edits, err = Oldedits, oerr
162+
}
163+
}()
164+
now = time.Now()
165+
Newedits, rerr := diff.NComputeEdits(uri, before, after)
166+
stat.Newedits = len(Newedits)
167+
stat.Newtime = time.Now().Sub(now)
168+
got := diff.ApplyEdits(before, Newedits)
169+
if got != after {
170+
stat.Msg += "FAIL"
171+
disaster(before, after)
172+
stat.save()
173+
return Oldedits, oerr
174+
}
175+
stat.save()
176+
return Newedits, rerr
177+
}
178+
15179
func ComputeEdits(uri span.URI, before, after string) (edits []diff.TextEdit, err error) {
16180
// The go-diff library has an unresolved panic (see golang/go#278774).
17181
// TODO(rstambler): Remove the recover once the issue has been fixed

gopls/internal/hooks/diff_test.go

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,56 @@
22
// Use of this source code is governed by a BSD-style
33
// license that can be found in the LICENSE file.
44

5-
package hooks_test
5+
package hooks
66

77
import (
8+
"fmt"
9+
"io/ioutil"
10+
"os"
811
"testing"
12+
"unicode/utf8"
913

10-
"golang.org/x/tools/gopls/internal/hooks"
1114
"golang.org/x/tools/internal/lsp/diff/difftest"
1215
)
1316

1417
func TestDiff(t *testing.T) {
15-
difftest.DiffTest(t, hooks.ComputeEdits)
18+
difftest.DiffTest(t, ComputeEdits)
19+
}
20+
21+
func TestRepl(t *testing.T) {
22+
t.Skip("just for checking repl by looking at it")
23+
repl := initrepl(800)
24+
t.Errorf("%q", string(repl))
25+
t.Errorf("%d", len(repl))
26+
}
27+
28+
func TestDisaster(t *testing.T) {
29+
a := "This is a string,(\u0995) just for basic functionality"
30+
b := "Ths is another string, (\u0996) to see if disaster will store stuff correctly"
31+
fname := disaster(a, b)
32+
buf, err := ioutil.ReadFile(fname)
33+
if err != nil {
34+
t.Errorf("error %v reading %s", err, fname)
35+
}
36+
var x, y string
37+
n, err := fmt.Sscanf(string(buf), "%s\n%s\n", &x, &y)
38+
if n != 2 {
39+
t.Errorf("got %d, expected 2", n)
40+
t.Logf("read %q", string(buf))
41+
}
42+
if a == x || b == y {
43+
t.Error("failed to encrypt")
44+
}
45+
err = os.Remove(fname)
46+
if err != nil {
47+
t.Errorf("%v removing %s", err, fname)
48+
}
49+
alen, blen := utf8.RuneCount([]byte(a)), utf8.RuneCount([]byte(b))
50+
xlen, ylen := utf8.RuneCount([]byte(x)), utf8.RuneCount([]byte(y))
51+
if alen != xlen {
52+
t.Errorf("a; got %d, expected %d", xlen, alen)
53+
}
54+
if blen != ylen {
55+
t.Errorf("b: got %d expected %d", ylen, blen)
56+
}
1657
}

0 commit comments

Comments
 (0)