mirror of
https://github.com/reactos/syzkaller.git
synced 2024-11-26 21:00:30 +00:00
go.mod: add go-fuzz-build
Otherwise go-fuzz-build now fails with: + go-fuzz-build -libfuzzer -func FuzzDeserialize -o fuzzer.a ./prog/test -: cannot find package "." in: /syzkaller/gopath/src/github.com/google/syzkaller/vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep
This commit is contained in:
parent
c7d7f10bdf
commit
74cb4e09a5
3
Makefile
3
Makefile
@ -401,8 +401,7 @@ install_prerequisites:
|
||||
sudo apt-get install -y -q g++-s390x-linux-gnu || true
|
||||
sudo apt-get install -y -q g++-riscv64-linux-gnu || true
|
||||
sudo apt-get install -y -q ragel clang-format
|
||||
go get -u golang.org/x/tools/cmd/goyacc \
|
||||
github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
go get -u golang.org/x/tools/cmd/goyacc
|
||||
|
||||
check_copyright:
|
||||
./tools/check-copyright.sh
|
||||
|
1
go.mod
1
go.mod
@ -6,6 +6,7 @@ require (
|
||||
cloud.google.com/go v0.60.0 // indirect
|
||||
cloud.google.com/go/storage v1.10.0
|
||||
github.com/BurntSushi/toml v0.3.1 // indirect
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813
|
||||
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
|
||||
github.com/golang/protobuf v1.4.2 // indirect
|
||||
github.com/golangci/golangci-lint v1.27.0
|
||||
|
2
go.sum
2
go.sum
@ -71,6 +71,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
|
||||
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813 h1:NgO45/5mBLRVfiXerEFzH6ikcZ7DNRPS639xFg3ENzU=
|
||||
github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw=
|
||||
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
|
||||
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
|
||||
|
@ -9,5 +9,7 @@
|
||||
package tools
|
||||
|
||||
import (
|
||||
_ "github.com/dvyukov/go-fuzz/go-fuzz-build"
|
||||
_ "github.com/dvyukov/go-fuzz/go-fuzz-dep"
|
||||
_ "github.com/golangci/golangci-lint/cmd/golangci-lint"
|
||||
)
|
||||
|
5
tools/docker/env/Dockerfile
vendored
5
tools/docker/env/Dockerfile
vendored
@ -2,7 +2,7 @@
|
||||
# Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
# The image provides dev environment suitable for syzkaller development/testing.
|
||||
# It includes Go toolchain, C/C++ cross-compilers and go-fuzz.
|
||||
# It includes Go toolchain and C/C++ cross-compilers.
|
||||
|
||||
# The image is available as gcr.io/syzkaller/env.
|
||||
|
||||
@ -36,9 +36,6 @@ RUN curl https://dl.google.com/go/go1.14.2.linux-amd64.tar.gz | tar -C /usr/loca
|
||||
ENV PATH /usr/local/go/bin:/gopath/bin:$PATH
|
||||
ENV GOPATH /gopath
|
||||
|
||||
# For go-fuzz we also need sources (go-fuzz-dep).
|
||||
RUN go get github.com/dvyukov/go-fuzz/go-fuzz github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
|
||||
# Pre-create dirs for syz-dock.
|
||||
# This is necessary to make docker work with the current user,
|
||||
# otherwise --volume will create these dirs under root and then
|
||||
|
@ -7,13 +7,16 @@
|
||||
|
||||
set -eux
|
||||
export TYPE="${1}"
|
||||
export GOBIN=$(realpath .)/bin
|
||||
|
||||
function target {
|
||||
go-fuzz-build -libfuzzer -func $3 -o fuzzer.a $2
|
||||
bin/go-fuzz-build -libfuzzer -func $3 -o fuzzer.a $2
|
||||
clang -fsanitize=fuzzer fuzzer.a -o fuzzer
|
||||
./fuzzit create job --type "${TYPE}" --branch ${GITHUB_REF#refs/heads/} --revision ${GITHUB_SHA} syzkaller/$1 ./fuzzer
|
||||
}
|
||||
|
||||
go install github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
|
||||
curl -L --output fuzzit https://github.com/fuzzitdev/fuzzit/releases/download/v2.4.77/fuzzit_Linux_x86_64
|
||||
chmod a+x fuzzit
|
||||
|
||||
|
174
vendor/github.com/dvyukov/go-fuzz/LICENSE
generated
vendored
Normal file
174
vendor/github.com/dvyukov/go-fuzz/LICENSE
generated
vendored
Normal file
@ -0,0 +1,174 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
906
vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/cover.go
generated
vendored
Normal file
906
vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/cover.go
generated
vendored
Normal file
@ -0,0 +1,906 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/constant"
|
||||
"go/printer"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
. "github.com/dvyukov/go-fuzz/go-fuzz-defs"
|
||||
. "github.com/dvyukov/go-fuzz/internal/go-fuzz-types"
|
||||
)
|
||||
|
||||
const fuzzdepPkg = "_go_fuzz_dep_"
|
||||
|
||||
func instrument(pkg, fullName string, fset *token.FileSet, parsedFile *ast.File, info *types.Info, out io.Writer, blocks *[]CoverBlock, sonar *[]CoverBlock) {
|
||||
file := &File{
|
||||
fset: fset,
|
||||
pkg: pkg,
|
||||
fullName: fullName,
|
||||
astFile: parsedFile,
|
||||
blocks: blocks,
|
||||
info: info,
|
||||
}
|
||||
if sonar == nil {
|
||||
file.addImport("go-fuzz-dep", fuzzdepPkg, "CoverTab")
|
||||
ast.Walk(file, file.astFile)
|
||||
} else {
|
||||
s := &Sonar{
|
||||
fset: fset,
|
||||
fullName: fullName,
|
||||
pkg: pkg,
|
||||
blocks: sonar,
|
||||
info: info,
|
||||
}
|
||||
ast.Walk(s, file.astFile)
|
||||
}
|
||||
|
||||
file.print(out)
|
||||
}
|
||||
|
||||
type Sonar struct {
|
||||
fset *token.FileSet
|
||||
fullName string
|
||||
pkg string
|
||||
blocks *[]CoverBlock
|
||||
info *types.Info
|
||||
}
|
||||
|
||||
var sonarSeq = 0
|
||||
|
||||
func (s *Sonar) Visit(n ast.Node) ast.Visitor {
|
||||
// TODO: detect "x&mask==0", emit sonar(x, x&^mask)
|
||||
switch nn := n.(type) {
|
||||
case *ast.BinaryExpr:
|
||||
break
|
||||
|
||||
case *ast.GenDecl:
|
||||
if nn.Tok != token.VAR {
|
||||
return nil // constants and types are not interesting
|
||||
}
|
||||
return s
|
||||
|
||||
case *ast.SelectorExpr:
|
||||
return nil
|
||||
|
||||
case *ast.SwitchStmt:
|
||||
if nn.Tag == nil || nn.Body == nil {
|
||||
return s // recurse
|
||||
}
|
||||
// Replace:
|
||||
// switch a := foo(); bar(a) {
|
||||
// case x: ...
|
||||
// case y: ...
|
||||
// }
|
||||
// with:
|
||||
// switch {
|
||||
// default:
|
||||
// a := foo()
|
||||
// __tmp := bar(a)
|
||||
// switch {
|
||||
// case __tmp == x: ...
|
||||
// case __tmp == y: ...
|
||||
// }
|
||||
// }
|
||||
// The == comparisons will be instrumented later when we recurse.
|
||||
sw := new(ast.SwitchStmt)
|
||||
*sw = *nn
|
||||
var stmts []ast.Stmt
|
||||
if sw.Init != nil {
|
||||
stmts = append(stmts, sw.Init)
|
||||
sw.Init = nil
|
||||
}
|
||||
const tmpvar = "__go_fuzz_tmp"
|
||||
tmp := ast.NewIdent(tmpvar)
|
||||
typ := s.info.Types[sw.Tag]
|
||||
s.info.Types[tmp] = typ
|
||||
stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{tmp}, Tok: token.DEFINE, Rhs: []ast.Expr{sw.Tag}})
|
||||
stmts = append(stmts, &ast.AssignStmt{Lhs: []ast.Expr{ast.NewIdent("_")}, Tok: token.ASSIGN, Rhs: []ast.Expr{tmp}})
|
||||
sw.Tag = nil
|
||||
stmts = append(stmts, sw)
|
||||
for _, cas1 := range sw.Body.List {
|
||||
cas := cas1.(*ast.CaseClause)
|
||||
for i, expr := range cas.List {
|
||||
tmp := &ast.Ident{Name: tmpvar, NamePos: expr.Pos()}
|
||||
s.info.Types[tmp] = typ
|
||||
cas.List[i] = &ast.BinaryExpr{X: tmp, Op: token.EQL, Y: expr}
|
||||
}
|
||||
}
|
||||
nn.Tag = nil
|
||||
nn.Init = nil
|
||||
nn.Body = &ast.BlockStmt{List: []ast.Stmt{&ast.CaseClause{Body: stmts}}}
|
||||
return s // recurse
|
||||
|
||||
case *ast.ForStmt:
|
||||
// For condition is usually uninteresting, but produces lots of samples.
|
||||
// So we skip it if it looks boring.
|
||||
if nn.Init != nil {
|
||||
ast.Walk(s, nn.Init)
|
||||
}
|
||||
if nn.Post != nil {
|
||||
ast.Walk(s, nn.Post)
|
||||
}
|
||||
ast.Walk(s, nn.Body)
|
||||
if nn.Cond != nil {
|
||||
// Look for the following pattern:
|
||||
// for foo := ...; foo ? ...; ... { ... }
|
||||
boring := false
|
||||
if nn.Init != nil {
|
||||
if init, ok1 := nn.Init.(*ast.AssignStmt); ok1 && init.Tok == token.DEFINE && len(init.Lhs) == 1 {
|
||||
if id, ok2 := init.Lhs[0].(*ast.Ident); ok2 {
|
||||
if bex, ok3 := nn.Cond.(*ast.BinaryExpr); ok3 {
|
||||
if x, ok4 := bex.X.(*ast.Ident); ok4 && x.Name == id.Name {
|
||||
boring = true
|
||||
}
|
||||
if x, ok4 := bex.Y.(*ast.Ident); ok4 && x.Name == id.Name {
|
||||
boring = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if !boring {
|
||||
ast.Walk(s, nn.Cond)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
default:
|
||||
return s // recurse
|
||||
}
|
||||
|
||||
// TODO: handle map index expressions (especially useful for strings).
|
||||
// E.g. when code matches a read in identifier against a set of known identifiers.
|
||||
// For the record, it looks as follows. However, it is tricky to distinguish
|
||||
// from slice/array index and map assignments...
|
||||
//. . . . . . . *ast.IndexExpr {
|
||||
//. . . . . . . . X: *ast.Ident {
|
||||
//. . . . . . . . . Name: "m"
|
||||
//. . . . . . . . }
|
||||
//. . . . . . . . Index: *ast.Ident {
|
||||
//. . . . . . . . . Name: "s"
|
||||
//. . . . . . . . }
|
||||
//. . . . . . . }
|
||||
|
||||
// TODO: transform expressions so that lhs expression contains a variable
|
||||
// and rhs contains all constant operands. For example, for (real code from vp8 codec):
|
||||
// cf := (b[0]>>4)&7 == 5
|
||||
// we would like to transform it to:
|
||||
// b[0] & (7<<4) == 5<<4
|
||||
// and then to:
|
||||
// b[0] == 5<<4 | b & ^(7<<4)
|
||||
// and emit:
|
||||
// Sonar(b[0], 5<<4 | b & ^(7<<4), SonarEQL)
|
||||
// This will allow the fuzzer to figure out what bytes it needs to replace
|
||||
// with what bytes in order to crack this condition.
|
||||
// Similarly, for:
|
||||
// x/3 == 100
|
||||
// we would like to emit:
|
||||
// Sonar(x, 100*3, SonarEQL)
|
||||
|
||||
// TODO: intercept strings.Index/HasPrefix and similar functions.
|
||||
|
||||
nn := n.(*ast.BinaryExpr)
|
||||
var flags uint8
|
||||
switch nn.Op {
|
||||
case token.EQL:
|
||||
flags = SonarEQL
|
||||
break
|
||||
case token.NEQ:
|
||||
flags = SonarNEQ
|
||||
break
|
||||
case token.LSS:
|
||||
flags = SonarLSS
|
||||
break
|
||||
case token.GTR:
|
||||
flags = SonarGTR
|
||||
break
|
||||
case token.LEQ:
|
||||
flags = SonarLEQ
|
||||
break
|
||||
case token.GEQ:
|
||||
flags = SonarGEQ
|
||||
break
|
||||
default:
|
||||
return s // recurse
|
||||
}
|
||||
// Replace:
|
||||
// x != y
|
||||
// with:
|
||||
// func() _go_fuzz_dep_.Bool { v1 := x; v2 := y; go-fuzz-dep.Sonar(v1, v2, flags); return v1 != v2 }() == true
|
||||
// Using "== true" lets us modify the AST Node in-place.
|
||||
v1 := nn.X
|
||||
v2 := nn.Y
|
||||
ast.Walk(s, v1)
|
||||
ast.Walk(s, v2)
|
||||
if isCap(v1) || isCap(v2) {
|
||||
// Haven't seen useful cases yet.
|
||||
return s
|
||||
}
|
||||
if isLen(v1) || isLen(v2) {
|
||||
// TODO: we could pass both length value and the len argument.
|
||||
// For example, if the code is:
|
||||
// name := ... // obtained from input
|
||||
// if len(name) > 5 { ... }
|
||||
// If we would have the name value at runtime, we will know
|
||||
// what part of the input to alter to affect len result.
|
||||
flags |= SonarLength
|
||||
}
|
||||
|
||||
checkType := func(tv types.TypeAndValue) bool {
|
||||
// Comparisons of pointers, maps, chans and bool are not interesting.
|
||||
if _, ok := tv.Type.(*types.Pointer); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := tv.Type.(*types.Map); ok {
|
||||
return false
|
||||
}
|
||||
if _, ok := tv.Type.(*types.Chan); ok {
|
||||
return false
|
||||
}
|
||||
s := tv.Type.Underlying().String()
|
||||
if s == "bool" || s == "ideal bool" || s == "error" ||
|
||||
s == "untyped nil" || s == "unsafe.Pointer" {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
if !checkType(s.info.Types[v1]) || !checkType(s.info.Types[v2]) {
|
||||
return nil
|
||||
}
|
||||
var tv types.TypeAndValue
|
||||
if isConstExpr(s.info, v1) {
|
||||
flags |= SonarConst1
|
||||
} else {
|
||||
tv = s.info.Types[v1]
|
||||
}
|
||||
if isConstExpr(s.info, v2) {
|
||||
flags |= SonarConst2
|
||||
} else {
|
||||
tv = s.info.Types[v2]
|
||||
}
|
||||
if flags&SonarConst1 != 0 && flags&SonarConst2 != 0 {
|
||||
return nil
|
||||
}
|
||||
id := int(flags) | sonarSeq<<8
|
||||
startPos := s.fset.Position(nn.Pos())
|
||||
endPos := s.fset.Position(nn.End())
|
||||
*s.blocks = append(*s.blocks, CoverBlock{sonarSeq, s.fullName, startPos.Line, startPos.Column, endPos.Line, endPos.Column, int(flags)})
|
||||
sonarSeq++
|
||||
block := &ast.BlockStmt{}
|
||||
|
||||
typstr := tv.Type.String()
|
||||
if strings.HasPrefix(typstr, s.pkg+".") {
|
||||
typstr = typstr[len(s.pkg)+1:]
|
||||
}
|
||||
if idx := strings.LastIndexByte(typstr, '/'); idx != -1 {
|
||||
typstr = typstr[idx+1:]
|
||||
}
|
||||
conv := func(name string, v ast.Expr) ast.Expr {
|
||||
// Convert const to the type of the other expr.
|
||||
isConst := isConstExpr(s.info, v)
|
||||
badConst := false
|
||||
if isConst {
|
||||
c := s.info.Types[v].Value
|
||||
switch c.Kind() {
|
||||
case constant.Int:
|
||||
if v, ok := constant.Int64Val(c); !ok || int64(int(v)) != v {
|
||||
// Such const can't be used outside of its current context,
|
||||
// because it will be converted to int and that will fail.
|
||||
badConst = true
|
||||
}
|
||||
case constant.Float:
|
||||
badConst = true
|
||||
}
|
||||
}
|
||||
if badConst || isWeirdShift(s.info, v) {
|
||||
v = &ast.CallExpr{
|
||||
Fun: ast.NewIdent(typstr),
|
||||
Args: []ast.Expr{v},
|
||||
}
|
||||
s.info.Types[v] = tv
|
||||
}
|
||||
if !isConst {
|
||||
// Assign to a temp to avoid double side-effects.
|
||||
tmp := ast.NewIdent(name)
|
||||
block.List = append(block.List, &ast.AssignStmt{Tok: token.DEFINE, Lhs: []ast.Expr{tmp}, Rhs: []ast.Expr{v}})
|
||||
v = tmp
|
||||
s.info.Types[v] = tv
|
||||
}
|
||||
return v
|
||||
}
|
||||
v1 = conv("__gofuzz_v1", v1)
|
||||
v2 = conv("__gofuzz_v2", v2)
|
||||
|
||||
block.List = append(block.List,
|
||||
&ast.ExprStmt{
|
||||
X: &ast.CallExpr{
|
||||
Fun: &ast.SelectorExpr{X: ast.NewIdent(fuzzdepPkg), Sel: ast.NewIdent("Sonar")},
|
||||
Args: []ast.Expr{v1, v2, &ast.BasicLit{Kind: token.INT, Value: strconv.Itoa(id)}},
|
||||
},
|
||||
},
|
||||
&ast.ReturnStmt{Results: []ast.Expr{&ast.BinaryExpr{Op: nn.Op, X: v1, Y: v2}}},
|
||||
)
|
||||
nn.X = &ast.CallExpr{
|
||||
Fun: &ast.FuncLit{
|
||||
Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent("_go_fuzz_dep_.Bool")}}}},
|
||||
Body: block,
|
||||
},
|
||||
}
|
||||
nn.Y = &ast.BasicLit{Kind: token.INT, Value: "true"}
|
||||
nn.Op = token.EQL
|
||||
return nil
|
||||
}
|
||||
|
||||
func isWeirdShift(info *types.Info, n ast.Expr) bool {
|
||||
w := &WeirdShiftWalker{info: info}
|
||||
ast.Walk(w, n)
|
||||
return w.found
|
||||
}
|
||||
|
||||
type WeirdShiftWalker struct {
|
||||
info *types.Info
|
||||
found bool
|
||||
}
|
||||
|
||||
func (w *WeirdShiftWalker) Visit(n ast.Node) ast.Visitor {
|
||||
if bin, ok := n.(*ast.BinaryExpr); ok && (bin.Op == token.SHL || bin.Op == token.SHR) && isConstExpr(w.info, bin.X) {
|
||||
w.found = true
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func isConstExpr(info *types.Info, n ast.Expr) bool {
|
||||
tv := info.Types[n]
|
||||
if tv.Type == nil && tv.Value == nil {
|
||||
panic(fmt.Sprintf("untyped expression: %#v", n))
|
||||
}
|
||||
return tv.Value != nil
|
||||
}
|
||||
|
||||
func isCap(n ast.Expr) bool {
|
||||
if call, ok := n.(*ast.CallExpr); ok {
|
||||
if id, ok2 := call.Fun.(*ast.Ident); ok2 {
|
||||
return id.Name == "cap"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isLen(n ast.Expr) bool {
|
||||
if call, ok := n.(*ast.CallExpr); ok {
|
||||
if id, ok2 := call.Fun.(*ast.Ident); ok2 {
|
||||
return id.Name == "len"
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type LiteralCollector struct {
|
||||
ctxt *Context
|
||||
lits map[Literal]struct{}
|
||||
}
|
||||
|
||||
func (lc *LiteralCollector) Visit(n ast.Node) (w ast.Visitor) {
|
||||
switch nn := n.(type) {
|
||||
default:
|
||||
return lc // recurse
|
||||
case *ast.ImportSpec:
|
||||
return nil
|
||||
case *ast.Field:
|
||||
return nil // ignore field tags
|
||||
case *ast.CallExpr:
|
||||
switch fn := nn.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
if fn.Name == "panic" {
|
||||
return nil
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
if id, ok := fn.X.(*ast.Ident); ok && (id.Name == "fmt" || id.Name == "errors") {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return lc
|
||||
case *ast.BasicLit:
|
||||
lit := nn.Value
|
||||
switch nn.Kind {
|
||||
case token.STRING:
|
||||
lc.lits[Literal{lc.unquote(lit), true}] = struct{}{}
|
||||
case token.CHAR:
|
||||
lc.lits[Literal{lc.unquote(lit), false}] = struct{}{}
|
||||
case token.INT:
|
||||
if lit[0] < '0' || lit[0] > '9' {
|
||||
lc.ctxt.failf("unsupported literal '%v'", lit)
|
||||
}
|
||||
v, err := strconv.ParseInt(lit, 0, 64)
|
||||
if err != nil {
|
||||
u, err := strconv.ParseUint(lit, 0, 64)
|
||||
if err != nil {
|
||||
lc.ctxt.failf("failed to parse int literal '%v': %v", lit, err)
|
||||
}
|
||||
v = int64(u)
|
||||
}
|
||||
var val []byte
|
||||
if v >= -(1<<7) && v < 1<<8 {
|
||||
val = append(val, byte(v))
|
||||
} else if v >= -(1<<15) && v < 1<<16 {
|
||||
val = append(val, byte(v), byte(v>>8))
|
||||
} else if v >= -(1<<31) && v < 1<<32 {
|
||||
val = append(val, byte(v), byte(v>>8), byte(v>>16), byte(v>>24))
|
||||
} else {
|
||||
val = append(val, byte(v), byte(v>>8), byte(v>>16), byte(v>>24), byte(v>>32), byte(v>>40), byte(v>>48), byte(v>>56))
|
||||
}
|
||||
lc.lits[Literal{string(val), false}] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func trimComments(file *ast.File, fset *token.FileSet) []*ast.CommentGroup {
|
||||
var comments []*ast.CommentGroup
|
||||
for _, group := range file.Comments {
|
||||
var list []*ast.Comment
|
||||
for _, comment := range group.List {
|
||||
if strings.HasPrefix(comment.Text, "//go:") && fset.Position(comment.Slash).Column == 1 {
|
||||
list = append(list, comment)
|
||||
}
|
||||
}
|
||||
if list != nil {
|
||||
comments = append(comments, &ast.CommentGroup{List: list})
|
||||
}
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func initialComments(content []byte) []byte {
|
||||
// Derived from go/build.Context.shouldBuild.
|
||||
end := 0
|
||||
p := content
|
||||
for len(p) > 0 {
|
||||
line := p
|
||||
if i := bytes.IndexByte(line, '\n'); i >= 0 {
|
||||
line, p = line[:i], p[i+1:]
|
||||
} else {
|
||||
p = p[len(p):]
|
||||
}
|
||||
line = bytes.TrimSpace(line)
|
||||
if len(line) == 0 { // Blank line.
|
||||
end = len(content) - len(p)
|
||||
continue
|
||||
}
|
||||
if !bytes.HasPrefix(line, slashslash) { // Not comment line.
|
||||
break
|
||||
}
|
||||
}
|
||||
return content[:end]
|
||||
}
|
||||
|
||||
type File struct {
|
||||
fset *token.FileSet
|
||||
pkg string
|
||||
fullName string
|
||||
astFile *ast.File
|
||||
blocks *[]CoverBlock
|
||||
info *types.Info
|
||||
}
|
||||
|
||||
var slashslash = []byte("//")
|
||||
|
||||
func (f *File) Visit(node ast.Node) ast.Visitor {
|
||||
switch n := node.(type) {
|
||||
case *ast.FuncDecl:
|
||||
if n.Name.String() == "init" {
|
||||
// Don't instrument init functions.
|
||||
// They run regardless of what we do, so it is just noise.
|
||||
return nil
|
||||
}
|
||||
case *ast.GenDecl:
|
||||
if n.Tok != token.VAR {
|
||||
return nil // constants and types are not interesting
|
||||
}
|
||||
|
||||
case *ast.BlockStmt:
|
||||
// If it's a switch or select, the body is a list of case clauses; don't tag the block itself.
|
||||
if len(n.List) > 0 {
|
||||
switch n.List[0].(type) {
|
||||
case *ast.CaseClause: // switch
|
||||
for _, n := range n.List {
|
||||
clause := n.(*ast.CaseClause)
|
||||
clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false)
|
||||
}
|
||||
return f
|
||||
case *ast.CommClause: // select
|
||||
for _, n := range n.List {
|
||||
clause := n.(*ast.CommClause)
|
||||
clause.Body = f.addCounters(clause.Pos(), clause.End(), clause.Body, false)
|
||||
}
|
||||
return f
|
||||
}
|
||||
}
|
||||
n.List = f.addCounters(n.Lbrace, n.Rbrace+1, n.List, true) // +1 to step past closing brace.
|
||||
case *ast.IfStmt:
|
||||
if n.Init != nil {
|
||||
ast.Walk(f, n.Init)
|
||||
}
|
||||
if n.Cond != nil {
|
||||
ast.Walk(f, n.Cond)
|
||||
}
|
||||
ast.Walk(f, n.Body)
|
||||
if n.Else == nil {
|
||||
// Add else because we want coverage for "not taken".
|
||||
n.Else = &ast.BlockStmt{
|
||||
Lbrace: n.Body.End(),
|
||||
Rbrace: n.Body.End(),
|
||||
}
|
||||
}
|
||||
// The elses are special, because if we have
|
||||
// if x {
|
||||
// } else if y {
|
||||
// }
|
||||
// we want to cover the "if y". To do this, we need a place to drop the counter,
|
||||
// so we add a hidden block:
|
||||
// if x {
|
||||
// } else {
|
||||
// if y {
|
||||
// }
|
||||
// }
|
||||
switch stmt := n.Else.(type) {
|
||||
case *ast.IfStmt:
|
||||
block := &ast.BlockStmt{
|
||||
Lbrace: n.Body.End(), // Start at end of the "if" block so the covered part looks like it starts at the "else".
|
||||
List: []ast.Stmt{stmt},
|
||||
Rbrace: stmt.End(),
|
||||
}
|
||||
n.Else = block
|
||||
case *ast.BlockStmt:
|
||||
stmt.Lbrace = n.Body.End() // Start at end of the "if" block so the covered part looks like it starts at the "else".
|
||||
default:
|
||||
panic("unexpected node type in if")
|
||||
}
|
||||
ast.Walk(f, n.Else)
|
||||
return nil
|
||||
case *ast.ForStmt:
|
||||
// TODO: handle increment statement
|
||||
case *ast.SelectStmt:
|
||||
// Don't annotate an empty select - creates a syntax error.
|
||||
if n.Body == nil || len(n.Body.List) == 0 {
|
||||
return nil
|
||||
}
|
||||
case *ast.SwitchStmt:
|
||||
hasDefault := false
|
||||
if n.Body == nil {
|
||||
n.Body = new(ast.BlockStmt)
|
||||
}
|
||||
for _, s := range n.Body.List {
|
||||
if cas, ok := s.(*ast.CaseClause); ok && cas.List == nil {
|
||||
hasDefault = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasDefault {
|
||||
// Add default case to get additional coverage.
|
||||
n.Body.List = append(n.Body.List, &ast.CaseClause{})
|
||||
}
|
||||
|
||||
// Don't annotate an empty switch - creates a syntax error.
|
||||
if n.Body == nil || len(n.Body.List) == 0 {
|
||||
return nil
|
||||
}
|
||||
case *ast.TypeSwitchStmt:
|
||||
// Don't annotate an empty type switch - creates a syntax error.
|
||||
// TODO: add default case
|
||||
if n.Body == nil || len(n.Body.List) == 0 {
|
||||
return nil
|
||||
}
|
||||
case *ast.BinaryExpr:
|
||||
if n.Op == token.LAND || n.Op == token.LOR {
|
||||
// Replace:
|
||||
// x && y
|
||||
// with:
|
||||
// x && func() T { return y }
|
||||
// where T is a bool of the same type as n (and x and y).
|
||||
|
||||
// Spelling T correctly is a little tricky.
|
||||
// go/types gives us a canonical name for T,
|
||||
// but we can't always use that canonical name in the code directly;
|
||||
// in the general case, it is of the form a/b/c/d.U.
|
||||
// When U is the built-in bool, or defined in the current package,
|
||||
// or defined in a dot-imported package, we want just U.
|
||||
// When U is in another package, we want d.U.
|
||||
// When U is in another package, imported under the name e, we want e.U.
|
||||
// (And when the built-in bool type is shadowed, we're just screwed.)
|
||||
// Handling all of these cases correctly is hard (it requires parsing the imports),
|
||||
// so we handle just the common cases.
|
||||
|
||||
// types.Default maps untyped bools to typed bools.
|
||||
typ := types.Default(f.info.Types[n].Type).String()
|
||||
// If we're in the current package, strip the package path.
|
||||
if strings.HasPrefix(typ, f.pkg+".") {
|
||||
typ = typ[len(f.pkg)+1:]
|
||||
}
|
||||
// If we're still in a package, assume it was imported with a reasonable name.
|
||||
if i := strings.LastIndexByte(typ, '/'); i >= 0 {
|
||||
typ = typ[i+1:]
|
||||
}
|
||||
|
||||
n.Y = &ast.CallExpr{
|
||||
Fun: &ast.FuncLit{
|
||||
Type: &ast.FuncType{Results: &ast.FieldList{List: []*ast.Field{{Type: ast.NewIdent(typ)}}}},
|
||||
Body: &ast.BlockStmt{List: []ast.Stmt{&ast.ReturnStmt{Results: []ast.Expr{n.Y}}}},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *File) addImport(path, name, anyIdent string) {
|
||||
newImport := &ast.ImportSpec{
|
||||
Name: ast.NewIdent(name),
|
||||
Path: &ast.BasicLit{
|
||||
Kind: token.STRING,
|
||||
Value: fmt.Sprintf("%q", path),
|
||||
},
|
||||
}
|
||||
impDecl := &ast.GenDecl{
|
||||
Tok: token.IMPORT,
|
||||
Specs: []ast.Spec{
|
||||
newImport,
|
||||
},
|
||||
}
|
||||
// Make the new import the first Decl in the file.
|
||||
astFile := f.astFile
|
||||
astFile.Decls = append(astFile.Decls, nil)
|
||||
copy(astFile.Decls[1:], astFile.Decls[0:])
|
||||
astFile.Decls[0] = impDecl
|
||||
astFile.Imports = append(astFile.Imports, newImport)
|
||||
|
||||
// Now refer to the package, just in case it ends up unused.
|
||||
// That is, append to the end of the file the declaration
|
||||
// var _ = _cover_atomic_.AddUint32
|
||||
reference := &ast.GenDecl{
|
||||
Tok: token.VAR,
|
||||
Specs: []ast.Spec{
|
||||
&ast.ValueSpec{
|
||||
Names: []*ast.Ident{
|
||||
ast.NewIdent("_"),
|
||||
},
|
||||
Values: []ast.Expr{
|
||||
&ast.SelectorExpr{
|
||||
X: ast.NewIdent(name),
|
||||
Sel: ast.NewIdent(anyIdent),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
astFile.Decls = append(astFile.Decls, reference)
|
||||
}
|
||||
|
||||
func (f *File) addCounters(pos, blockEnd token.Pos, list []ast.Stmt, extendToClosingBrace bool) []ast.Stmt {
|
||||
// Special case: make sure we add a counter to an empty block. Can't do this below
|
||||
// or we will add a counter to an empty statement list after, say, a return statement.
|
||||
if len(list) == 0 {
|
||||
return []ast.Stmt{f.newCounter(pos, blockEnd, 0)}
|
||||
}
|
||||
// We have a block (statement list), but it may have several basic blocks due to the
|
||||
// appearance of statements that affect the flow of control.
|
||||
var newList []ast.Stmt
|
||||
for {
|
||||
// Find first statement that affects flow of control (break, continue, if, etc.).
|
||||
// It will be the last statement of this basic block.
|
||||
var last int
|
||||
end := blockEnd
|
||||
for last = 0; last < len(list); last++ {
|
||||
end = f.statementBoundary(list[last])
|
||||
if f.endsBasicSourceBlock(list[last]) {
|
||||
extendToClosingBrace = false // Block is broken up now.
|
||||
last++
|
||||
break
|
||||
}
|
||||
}
|
||||
if extendToClosingBrace {
|
||||
end = blockEnd
|
||||
}
|
||||
if pos != end { // Can have no source to cover if e.g. blocks abut.
|
||||
newList = append(newList, f.newCounter(pos, end, last))
|
||||
}
|
||||
newList = append(newList, list[0:last]...)
|
||||
list = list[last:]
|
||||
if len(list) == 0 {
|
||||
break
|
||||
}
|
||||
pos = list[0].Pos()
|
||||
}
|
||||
return newList
|
||||
}
|
||||
|
||||
func (f *File) endsBasicSourceBlock(s ast.Stmt) bool {
|
||||
switch s := s.(type) {
|
||||
case *ast.BlockStmt:
|
||||
// Treat blocks like basic blocks to avoid overlapping counters.
|
||||
return true
|
||||
case *ast.BranchStmt:
|
||||
return true
|
||||
case *ast.ForStmt:
|
||||
return true
|
||||
case *ast.IfStmt:
|
||||
return true
|
||||
case *ast.LabeledStmt:
|
||||
return f.endsBasicSourceBlock(s.Stmt)
|
||||
case *ast.RangeStmt:
|
||||
return true
|
||||
case *ast.SwitchStmt:
|
||||
return true
|
||||
case *ast.SelectStmt:
|
||||
return true
|
||||
case *ast.TypeSwitchStmt:
|
||||
return true
|
||||
case *ast.ExprStmt:
|
||||
// Calls to panic change the flow.
|
||||
// We really should verify that "panic" is the predefined function,
|
||||
// but without type checking we can't and the likelihood of it being
|
||||
// an actual problem is vanishingly small.
|
||||
if call, ok := s.X.(*ast.CallExpr); ok {
|
||||
if ident, ok := call.Fun.(*ast.Ident); ok && ident.Name == "panic" && len(call.Args) == 1 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
found, _ := hasFuncLiteral(s)
|
||||
return found
|
||||
}
|
||||
|
||||
func (f *File) statementBoundary(s ast.Stmt) token.Pos {
|
||||
// Control flow statements are easy.
|
||||
switch s := s.(type) {
|
||||
case *ast.BlockStmt:
|
||||
// Treat blocks like basic blocks to avoid overlapping counters.
|
||||
return s.Lbrace
|
||||
case *ast.IfStmt:
|
||||
found, pos := hasFuncLiteral(s.Init)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
found, pos = hasFuncLiteral(s.Cond)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
return s.Body.Lbrace
|
||||
case *ast.ForStmt:
|
||||
found, pos := hasFuncLiteral(s.Init)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
found, pos = hasFuncLiteral(s.Cond)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
found, pos = hasFuncLiteral(s.Post)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
return s.Body.Lbrace
|
||||
case *ast.LabeledStmt:
|
||||
return f.statementBoundary(s.Stmt)
|
||||
case *ast.RangeStmt:
|
||||
found, pos := hasFuncLiteral(s.X)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
return s.Body.Lbrace
|
||||
case *ast.SwitchStmt:
|
||||
found, pos := hasFuncLiteral(s.Init)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
found, pos = hasFuncLiteral(s.Tag)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
return s.Body.Lbrace
|
||||
case *ast.SelectStmt:
|
||||
return s.Body.Lbrace
|
||||
case *ast.TypeSwitchStmt:
|
||||
found, pos := hasFuncLiteral(s.Init)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
return s.Body.Lbrace
|
||||
}
|
||||
found, pos := hasFuncLiteral(s)
|
||||
if found {
|
||||
return pos
|
||||
}
|
||||
return s.End()
|
||||
}
|
||||
|
||||
var counterGen uint32
|
||||
|
||||
func genCounter() int {
|
||||
counterGen++
|
||||
id := counterGen
|
||||
buf := []byte{byte(id), byte(id >> 8), byte(id >> 16), byte(id >> 24)}
|
||||
hash := sha1.Sum(buf)
|
||||
return int(uint16(hash[0]) | uint16(hash[1])<<8)
|
||||
}
|
||||
|
||||
func (f *File) newCounter(start, end token.Pos, numStmt int) ast.Stmt {
|
||||
cnt := genCounter()
|
||||
|
||||
if f.blocks != nil {
|
||||
s := f.fset.Position(start)
|
||||
e := f.fset.Position(end)
|
||||
*f.blocks = append(*f.blocks, CoverBlock{cnt, f.fullName, s.Line, s.Column, e.Line, e.Column, numStmt})
|
||||
}
|
||||
|
||||
idx := &ast.BasicLit{
|
||||
Kind: token.INT,
|
||||
Value: strconv.Itoa(cnt),
|
||||
}
|
||||
counter := &ast.IndexExpr{
|
||||
X: &ast.SelectorExpr{
|
||||
X: ast.NewIdent(fuzzdepPkg),
|
||||
Sel: ast.NewIdent("CoverTab"),
|
||||
},
|
||||
Index: idx,
|
||||
}
|
||||
return &ast.IncDecStmt{
|
||||
X: counter,
|
||||
Tok: token.INC,
|
||||
}
|
||||
}
|
||||
|
||||
func (f *File) print(w io.Writer) {
|
||||
cfg := printer.Config{
|
||||
Mode: printer.SourcePos,
|
||||
Tabwidth: 8,
|
||||
Indent: 0,
|
||||
}
|
||||
cfg.Fprint(w, f.fset, f.astFile)
|
||||
}
|
||||
|
||||
type funcLitFinder token.Pos
|
||||
|
||||
func (f *funcLitFinder) Visit(node ast.Node) (w ast.Visitor) {
|
||||
if f.found() {
|
||||
return nil // Prune search.
|
||||
}
|
||||
switch n := node.(type) {
|
||||
case *ast.FuncLit:
|
||||
*f = funcLitFinder(n.Body.Lbrace)
|
||||
return nil // Prune search.
|
||||
}
|
||||
return f
|
||||
}
|
||||
|
||||
func (f *funcLitFinder) found() bool {
|
||||
return token.Pos(*f) != token.NoPos
|
||||
}
|
||||
|
||||
func hasFuncLiteral(n ast.Node) (bool, token.Pos) {
|
||||
if n == nil {
|
||||
return false, 0
|
||||
}
|
||||
var literal funcLitFinder
|
||||
ast.Walk(&literal, n)
|
||||
return literal.found(), token.Pos(literal)
|
||||
}
|
||||
|
||||
func (lc *LiteralCollector) unquote(s string) string {
|
||||
t, err := strconv.Unquote(s)
|
||||
if err != nil {
|
||||
lc.ctxt.failf("cover: improperly quoted string %q\n", s)
|
||||
}
|
||||
return t
|
||||
}
|
882
vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/main.go
generated
vendored
Normal file
882
vendor/github.com/dvyukov/go-fuzz/go-fuzz-build/main.go
generated
vendored
Normal file
@ -0,0 +1,882 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"archive/zip"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
"fmt"
|
||||
"go/ast"
|
||||
"go/parser"
|
||||
"go/token"
|
||||
"go/types"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"runtime/pprof"
|
||||
"strings"
|
||||
"text/template"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/tools/go/packages"
|
||||
|
||||
. "github.com/dvyukov/go-fuzz/internal/go-fuzz-types"
|
||||
)
|
||||
|
||||
var (
|
||||
flagTag = flag.String("tags", "", "a space-separated list of build tags to consider satisfied during the build")
|
||||
flagOut = flag.String("o", "", "output file")
|
||||
flagFunc = flag.String("func", "", "preferred entry function")
|
||||
flagWork = flag.Bool("work", false, "don't remove working directory")
|
||||
flagRace = flag.Bool("race", false, "enable race detector")
|
||||
flagCPU = flag.Bool("cpuprofile", false, "generate cpu profile in cpu.pprof")
|
||||
flagLibFuzzer = flag.Bool("libfuzzer", false, "output static archive for use with libFuzzer")
|
||||
flagBuildX = flag.Bool("x", false, "print the commands if build fails")
|
||||
flagPreserve = flag.String("preserve", "", "a comma-separated list of import paths not to instrument")
|
||||
)
|
||||
|
||||
func makeTags() string {
|
||||
tags := "gofuzz"
|
||||
if *flagLibFuzzer {
|
||||
tags += " gofuzz_libfuzzer"
|
||||
}
|
||||
if *flagRace {
|
||||
tags += " race"
|
||||
}
|
||||
if len(*flagTag) > 0 {
|
||||
tags += " " + *flagTag
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
// basePackagesConfig returns a base golang.org/x/tools/go/packages.Config
|
||||
// that clients can then modify and use for calls to go/packages.
|
||||
func basePackagesConfig() *packages.Config {
|
||||
cfg := new(packages.Config)
|
||||
|
||||
// Note that we do not set GO111MODULE here in order to respect any GO111MODULE
|
||||
// setting by the user as we are finding dependencies. Note, however, that
|
||||
// we are still setting up a GOPATH to build, so we later will force
|
||||
// GO111MODULE to be off when building so that we are in GOPATH mode.
|
||||
// If the user has not set GO111MODULE, the meaning here is
|
||||
// left up to cmd/go (defaulting to 'auto' in Go 1.11-1.13,
|
||||
// but likely defaulting to 'on' at some point during Go 1.14
|
||||
// development cycle).
|
||||
// Also note that we are leaving the overall cfg structure
|
||||
// in place to support future experimentation, etc.
|
||||
cfg.Env = os.Environ()
|
||||
return cfg
|
||||
}
|
||||
|
||||
// checkModVendor reports if the GOFLAGS env variable
|
||||
// contains -mod=vendor, which enables vendoring for modules.
|
||||
func checkModVendor() bool {
|
||||
val := os.Getenv("GOFLAGS")
|
||||
for _, s := range strings.Split(val, " ") {
|
||||
if s == "-mod=vendor" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// main copies the package with all dependent packages into a temp dir,
|
||||
// instruments Go source files there, and builds setting GOROOT to the temp dir.
|
||||
func main() {
|
||||
flag.Parse()
|
||||
c := new(Context)
|
||||
|
||||
if flag.NArg() > 1 {
|
||||
c.failf("usage: go-fuzz-build [pkg]")
|
||||
}
|
||||
|
||||
pkg := "."
|
||||
if flag.NArg() == 1 {
|
||||
pkg = flag.Arg(0)
|
||||
}
|
||||
if *flagFunc != "" && !isFuzzFuncName(*flagFunc) {
|
||||
c.failf("provided -func=%v, but %v is not a fuzz function name", *flagFunc, *flagFunc)
|
||||
}
|
||||
if *flagLibFuzzer && *flagRace {
|
||||
c.failf("-race and -libfuzzer are incompatible")
|
||||
}
|
||||
if checkModVendor() {
|
||||
// We don't support -mod=vendor with modules.
|
||||
// Part of the issue is go-fuzz-dep and go-fuzz-defs
|
||||
// won't be in the user's vendor directory.
|
||||
c.failf("GOFLAGS with -mod=vendor is not supported")
|
||||
}
|
||||
|
||||
c.startProfiling() // start pprof as requested
|
||||
c.loadPkg(pkg) // load and typecheck pkg
|
||||
c.getEnv() // discover GOROOT, GOPATH
|
||||
c.loadStd() // load standard library
|
||||
c.calcIgnore() // calculate set of packages to ignore
|
||||
c.makeWorkdir() // create workdir
|
||||
defer c.cleanup() // delete workdir as needed, etc.
|
||||
c.populateWorkdir() // copy tools and packages to workdir as needed
|
||||
|
||||
if *flagOut == "" {
|
||||
ext := ".zip"
|
||||
if *flagLibFuzzer {
|
||||
ext = ".a"
|
||||
}
|
||||
*flagOut = c.pkgs[0].Name + "-fuzz" + ext
|
||||
}
|
||||
|
||||
// Gather literals, instrument, and compile.
|
||||
// Order matters here!
|
||||
// buildInstrumentedBinary (and instrumentPackages) modify the AST.
|
||||
// (We don't want to re-parse and re-typecheck every time, for performance.)
|
||||
// So we gather literals first, while the AST is pristine.
|
||||
// Then we add coverage and build.
|
||||
// Then we add sonar and build.
|
||||
// TODO: migrate to use cmd/internal/edit instead of AST modification.
|
||||
// This has several benefits: (1) It is easier to work with.
|
||||
// (2) 'go cover' has switched to it; we would get the benefit of
|
||||
// upstream bug fixes, of which there has been at least one (around gotos and labels).
|
||||
// (3) It leaves the AST intact, so we are less order-sensitive.
|
||||
// The primary blocker is that we want good line numbers for when we find crashers.
|
||||
// go/printer handles this automatically using Mode printer.SourcePos.
|
||||
// We'd need to implement that support ourselves. (It's do-able but non-trivial.)
|
||||
// See also https://golang.org/issue/29824.
|
||||
lits := c.gatherLiterals()
|
||||
var blocks, sonar []CoverBlock
|
||||
|
||||
if *flagLibFuzzer {
|
||||
archive := c.buildInstrumentedBinary(&blocks, nil)
|
||||
c.moveFile(archive, *flagOut)
|
||||
return
|
||||
}
|
||||
|
||||
coverBin := c.buildInstrumentedBinary(&blocks, nil)
|
||||
sonarBin := c.buildInstrumentedBinary(nil, &sonar)
|
||||
metaData := c.createMeta(lits, blocks, sonar)
|
||||
defer func() {
|
||||
os.Remove(coverBin)
|
||||
os.Remove(sonarBin)
|
||||
os.Remove(metaData)
|
||||
}()
|
||||
|
||||
outf, err := os.Create(*flagOut)
|
||||
if err != nil {
|
||||
c.failf("failed to create output file: %v", err)
|
||||
}
|
||||
zipw := zip.NewWriter(outf)
|
||||
zipFile := func(name, datafile string) {
|
||||
w, err := zipw.Create(name)
|
||||
if err != nil {
|
||||
c.failf("failed to create zip file: %v", err)
|
||||
}
|
||||
f, err := os.Open(datafile)
|
||||
if err != nil {
|
||||
c.failf("failed to open data file %v", datafile)
|
||||
}
|
||||
if _, err := io.Copy(w, f); err != nil {
|
||||
c.failf("failed to write %v to zip file: %v", datafile, err)
|
||||
}
|
||||
// best effort: close and remove our temp file
|
||||
f.Close()
|
||||
os.Remove(datafile)
|
||||
}
|
||||
zipFile("cover.exe", coverBin)
|
||||
zipFile("sonar.exe", sonarBin)
|
||||
zipFile("metadata", metaData)
|
||||
if err := zipw.Close(); err != nil {
|
||||
c.failf("failed to close zip file: %v", err)
|
||||
}
|
||||
if err := outf.Close(); err != nil {
|
||||
c.failf("failed to close out file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Context holds state for a go-fuzz-build run.
|
||||
type Context struct {
|
||||
fuzzpkg *packages.Package // package containing Fuzz function
|
||||
pkgs []*packages.Package // typechecked root packages
|
||||
|
||||
std map[string]bool // set of packages in the standard library
|
||||
ignore map[string]bool // set of packages to ignore during instrumentation
|
||||
|
||||
allFuncs []string // all fuzz functions found in package
|
||||
|
||||
workdir string
|
||||
GOROOT string
|
||||
GOPATH string
|
||||
|
||||
cpuprofile *os.File
|
||||
|
||||
cmdGoHasTrimPath bool // does the active version of cmd/go have the -trimpath flag?
|
||||
}
|
||||
|
||||
// getEnv determines GOROOT and GOPATH and updates c accordingly.
|
||||
func (c *Context) getEnv() {
|
||||
env := map[string]string{
|
||||
"GOROOT": "",
|
||||
"GOPATH": "",
|
||||
}
|
||||
for k := range env {
|
||||
v := os.Getenv(k)
|
||||
if v != "" {
|
||||
env[k] = v
|
||||
continue
|
||||
}
|
||||
// TODO: make a single call ("go env GOROOT GOPATH") instead
|
||||
out, err := exec.Command("go", "env", k).CombinedOutput()
|
||||
if err != nil || len(out) == 0 {
|
||||
c.failf("%s is not set and failed to locate it: 'go env %s' returned '%s' (%v)", k, k, out, err)
|
||||
}
|
||||
env[k] = strings.TrimSpace(string(out))
|
||||
}
|
||||
c.GOROOT = env["GOROOT"]
|
||||
c.GOPATH = env["GOPATH"]
|
||||
|
||||
out, err := exec.Command("go", "list", "-f", "'{{context.ReleaseTags}}'", "runtime").CombinedOutput()
|
||||
if err != nil || len(out) == 0 {
|
||||
c.failf("go list -f '{{context.ReleaseTags}}' runtime returned '%s' (%v)", out, err)
|
||||
}
|
||||
c.cmdGoHasTrimPath = bytes.Contains(out, []byte("go1.13"))
|
||||
}
|
||||
|
||||
// startProfiling starts pprof profiling, if requested.
|
||||
func (c *Context) startProfiling() {
|
||||
if !*flagCPU {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
c.cpuprofile, err = os.Create("cpu.pprof")
|
||||
if err != nil {
|
||||
c.failf("could not create cpu profile: %v", err)
|
||||
}
|
||||
pprof.StartCPUProfile(c.cpuprofile)
|
||||
}
|
||||
|
||||
// loadPkg loads, parses, and typechecks pkg (the package containing the Fuzz function),
|
||||
// go-fuzz-dep, and their dependencies.
|
||||
func (c *Context) loadPkg(pkg string) {
|
||||
// Resolve pkg.
|
||||
// See https://golang.org/issue/30826 and https://golang.org/issue/30828.
|
||||
rescfg := basePackagesConfig()
|
||||
rescfg.Mode = packages.NeedName
|
||||
rescfg.BuildFlags = []string{"-tags", makeTags()}
|
||||
respkgs, err := packages.Load(rescfg, pkg)
|
||||
if err != nil {
|
||||
c.failf("could not resolve package %q: %v", pkg, err)
|
||||
}
|
||||
if len(respkgs) != 1 {
|
||||
paths := make([]string, len(respkgs))
|
||||
for i, p := range respkgs {
|
||||
paths[i] = p.PkgPath
|
||||
}
|
||||
c.failf("cannot build multiple packages, but %q resolved to: %v", pkg, strings.Join(paths, ", "))
|
||||
}
|
||||
if respkgs[0].Name == "main" {
|
||||
c.failf("cannot fuzz package main")
|
||||
}
|
||||
pkgpath := respkgs[0].PkgPath
|
||||
|
||||
// Load, parse, and type-check all packages.
|
||||
// We'll use the type information later.
|
||||
// This also provides better error messages in the case
|
||||
// of invalid code than trying to compile instrumented code.
|
||||
cfg := basePackagesConfig()
|
||||
cfg.Mode = packages.LoadAllSyntax
|
||||
cfg.BuildFlags = []string{"-tags", makeTags()}
|
||||
// use custom ParseFile in order to get comments
|
||||
cfg.ParseFile = func(fset *token.FileSet, filename string, src []byte) (*ast.File, error) {
|
||||
return parser.ParseFile(fset, filename, src, parser.ParseComments)
|
||||
}
|
||||
// We need to load:
|
||||
// * the target package, obviously
|
||||
// * go-fuzz-dep, since we use it for instrumentation
|
||||
// * reflect, if we are using libfuzzer, since its generated main function requires it
|
||||
loadpkgs := []string{pkg, "github.com/dvyukov/go-fuzz/go-fuzz-dep"}
|
||||
if *flagLibFuzzer {
|
||||
loadpkgs = append(loadpkgs, "reflect")
|
||||
}
|
||||
initial, err := packages.Load(cfg, loadpkgs...)
|
||||
if err != nil {
|
||||
c.failf("could not load packages: %v", err)
|
||||
}
|
||||
|
||||
// Stop if any package had errors.
|
||||
if packages.PrintErrors(initial) > 0 {
|
||||
c.failf("typechecking of %v failed", pkg)
|
||||
}
|
||||
|
||||
c.pkgs = initial
|
||||
|
||||
// Find the fuzz package among c.pkgs.
|
||||
for _, p := range initial {
|
||||
if p.PkgPath == pkgpath {
|
||||
c.fuzzpkg = p
|
||||
break
|
||||
}
|
||||
}
|
||||
if c.fuzzpkg == nil {
|
||||
c.failf("internal error: failed to find fuzz package; please file an issue")
|
||||
}
|
||||
|
||||
// Find all fuzz functions in fuzzpkg.
|
||||
foundFlagFunc := false
|
||||
s := c.fuzzpkg.Types.Scope()
|
||||
for _, n := range s.Names() {
|
||||
if !isFuzzFuncName(n) {
|
||||
continue
|
||||
}
|
||||
// Check that n is a function with an appropriate signature.
|
||||
typ := s.Lookup(n).Type()
|
||||
sig, ok := typ.(*types.Signature)
|
||||
if !ok || sig.Variadic() || !isFuzzSig(sig) {
|
||||
if n == *flagFunc {
|
||||
c.failf("provided -func=%v, but %v is not a fuzz function", *flagFunc, *flagFunc)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// n is a fuzz function.
|
||||
c.allFuncs = append(c.allFuncs, n)
|
||||
foundFlagFunc = foundFlagFunc || n == *flagFunc
|
||||
}
|
||||
|
||||
if len(c.allFuncs) == 0 {
|
||||
c.failf("could not find any fuzz functions in %v", c.fuzzpkg.PkgPath)
|
||||
}
|
||||
if len(c.allFuncs) > 255 {
|
||||
c.failf("go-fuzz-build supports a maximum of 255 fuzz functions, found %v; please file an issue", len(c.allFuncs))
|
||||
}
|
||||
|
||||
if *flagFunc != "" {
|
||||
// Specific fuzz function requested.
|
||||
// If the requested function doesn't exist, fail.
|
||||
if !foundFlagFunc {
|
||||
c.failf("could not find fuzz function %v in %v", *flagFunc, c.fuzzpkg.PkgPath)
|
||||
}
|
||||
} else {
|
||||
// No specific fuzz function requested.
|
||||
// If there's only one fuzz function, mark it as preferred.
|
||||
// If there's more than one...
|
||||
// ...for go-fuzz, that's fine; one can be specified later on the command line.
|
||||
// ...for libfuzzer, that's not fine, as there is no way to specify one later.
|
||||
if len(c.allFuncs) == 1 {
|
||||
*flagFunc = c.allFuncs[0]
|
||||
} else if *flagLibFuzzer {
|
||||
c.failf("must specify a fuzz function with -libfuzzer, found: %v", strings.Join(c.allFuncs, ", "))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isFuzzSig reports whether sig is of the form
|
||||
// func FuzzFunc(data []byte) int
|
||||
func isFuzzSig(sig *types.Signature) bool {
|
||||
return tupleHasTypes(sig.Params(), "[]byte") && tupleHasTypes(sig.Results(), "int")
|
||||
}
|
||||
|
||||
// tupleHasTypes reports whether tuple is composed of
|
||||
// elements with exactly the types in types.
|
||||
func tupleHasTypes(tuple *types.Tuple, types ...string) bool {
|
||||
if tuple.Len() != len(types) {
|
||||
return false
|
||||
}
|
||||
for i, t := range types {
|
||||
if tuple.At(i).Type().String() != t {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func isFuzzFuncName(name string) bool {
|
||||
return isTest(name, "Fuzz")
|
||||
}
|
||||
|
||||
// isTest is copied verbatim, along with its name,
|
||||
// from GOROOT/src/cmd/go/internal/load/test.go.
|
||||
// isTest tells whether name looks like a test (or benchmark, according to prefix).
|
||||
// It is a Test (say) if there is a character after Test that is not a lower-case letter.
|
||||
// We don't want TesticularCancer.
|
||||
func isTest(name, prefix string) bool {
|
||||
if !strings.HasPrefix(name, prefix) {
|
||||
return false
|
||||
}
|
||||
if len(name) == len(prefix) { // "Test" is ok
|
||||
return true
|
||||
}
|
||||
rune, _ := utf8.DecodeRuneInString(name[len(prefix):])
|
||||
return !unicode.IsLower(rune)
|
||||
}
|
||||
|
||||
// loadStd finds the set of standard library package paths.
|
||||
func (c *Context) loadStd() {
|
||||
// Find out what packages are in the standard library.
|
||||
cfg := basePackagesConfig()
|
||||
cfg.Mode = packages.NeedName
|
||||
stdpkgs, err := packages.Load(cfg, "std")
|
||||
if err != nil {
|
||||
c.failf("could not load standard library: %v", err)
|
||||
}
|
||||
c.std = make(map[string]bool, len(stdpkgs))
|
||||
for _, p := range stdpkgs {
|
||||
c.std[p.PkgPath] = true
|
||||
}
|
||||
}
|
||||
|
||||
// makeWorkdir creates the workdir, logging as requested.
|
||||
func (c *Context) makeWorkdir() {
|
||||
// TODO: make workdir stable, so that we can use cmd/go's build cache?
|
||||
// See https://github.com/golang/go/issues/29430.
|
||||
var err error
|
||||
c.workdir, err = ioutil.TempDir("", "go-fuzz-build")
|
||||
if err != nil {
|
||||
c.failf("failed to create temp dir: %v", err)
|
||||
}
|
||||
if *flagWork {
|
||||
fmt.Printf("workdir: %v\n", c.workdir)
|
||||
}
|
||||
}
|
||||
|
||||
// cleanup ensures a clean exit. It should be called on all (controllable) exit paths.
|
||||
func (c *Context) cleanup() {
|
||||
if !*flagWork && c.workdir != "" {
|
||||
os.RemoveAll(c.workdir)
|
||||
}
|
||||
if c.cpuprofile != nil {
|
||||
pprof.StopCPUProfile()
|
||||
c.cpuprofile.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// populateWorkdir prepares workdir for builds.
|
||||
func (c *Context) populateWorkdir() {
|
||||
// TODO: instead of reconstructing the world,
|
||||
// can we use a bunch of replace directives in a go.mod?
|
||||
|
||||
// TODO: make all this I/O concurrent (up to a limit).
|
||||
// It's a non-trivial part of build time.
|
||||
// Question: Do it here or in copyDir?
|
||||
|
||||
// TODO: See if we can avoid making toolchain copies,
|
||||
// using some combination of env vars and toolexec.
|
||||
if *flagLibFuzzer || *flagRace {
|
||||
c.copyDir(filepath.Join(c.GOROOT, "src", "runtime", "cgo"), filepath.Join(c.workdir, "goroot", "src", "runtime", "cgo"))
|
||||
}
|
||||
if *flagRace {
|
||||
c.copyDir(filepath.Join(c.GOROOT, "src", "runtime", "race"), filepath.Join(c.workdir, "goroot", "src", "runtime", "race"))
|
||||
c.copyDir(filepath.Join(c.GOROOT, "src", "sync", "atomic"), filepath.Join(c.workdir, "goroot", "src", "sync", "atomic"))
|
||||
}
|
||||
c.copyDir(filepath.Join(c.GOROOT, "pkg", "tool"), filepath.Join(c.workdir, "goroot", "pkg", "tool"))
|
||||
if _, err := os.Stat(filepath.Join(c.GOROOT, "pkg", "include")); err == nil {
|
||||
c.copyDir(filepath.Join(c.GOROOT, "pkg", "include"), filepath.Join(c.workdir, "goroot", "pkg", "include"))
|
||||
} else {
|
||||
// Cross-compilation is not implemented.
|
||||
c.copyDir(filepath.Join(c.GOROOT, "pkg", runtime.GOOS+"_"+runtime.GOARCH), filepath.Join(c.workdir, "goroot", "pkg", runtime.GOOS+"_"+runtime.GOARCH))
|
||||
}
|
||||
|
||||
// Clone our package, go-fuzz-deps, and all dependencies.
|
||||
// TODO: we might not need to do this for all packages.
|
||||
// We know that we'll be writing out instrumented Go code later;
|
||||
// we could instead just os.MkdirAll and copy non-Go files here.
|
||||
// We'd still need to do a full package clone for packages that
|
||||
// we aren't instrumenting (c.ignore).
|
||||
packages.Visit(c.pkgs, nil, func(p *packages.Package) {
|
||||
c.clonePackage(p)
|
||||
})
|
||||
c.copyFuzzDep()
|
||||
}
|
||||
|
||||
func (c *Context) createMeta(lits map[Literal]struct{}, blocks []CoverBlock, sonar []CoverBlock) string {
|
||||
meta := MetaData{Blocks: blocks, Sonar: sonar, Funcs: c.allFuncs, DefaultFunc: *flagFunc}
|
||||
for k := range lits {
|
||||
meta.Literals = append(meta.Literals, k)
|
||||
}
|
||||
data, err := json.Marshal(meta)
|
||||
if err != nil {
|
||||
c.failf("failed to serialize meta information: %v", err)
|
||||
}
|
||||
f := c.tempFile()
|
||||
c.writeFile(f, data)
|
||||
return f
|
||||
}
|
||||
|
||||
func (c *Context) buildInstrumentedBinary(blocks *[]CoverBlock, sonar *[]CoverBlock) string {
|
||||
c.instrumentPackages(blocks, sonar)
|
||||
mainPkg := c.createFuzzMain()
|
||||
outf := c.tempFile()
|
||||
args := []string{"build", "-tags", makeTags()}
|
||||
if *flagBuildX {
|
||||
args = append(args, "-x")
|
||||
|
||||
if *flagWork {
|
||||
args = append(args, "-work")
|
||||
}
|
||||
}
|
||||
if *flagRace {
|
||||
args = append(args, "-race")
|
||||
}
|
||||
if *flagLibFuzzer {
|
||||
args = append(args, "-buildmode=c-archive")
|
||||
}
|
||||
if c.cmdGoHasTrimPath {
|
||||
args = append(args, "-trimpath")
|
||||
}
|
||||
args = append(args, "-o", outf, mainPkg)
|
||||
cmd := exec.Command("go", args...)
|
||||
|
||||
// We are constructing a GOPATH environment, so while building
|
||||
// we force GOPATH mode here via GO111MODULE=off.
|
||||
cmd.Env = append(os.Environ(),
|
||||
"GOROOT="+filepath.Join(c.workdir, "goroot"),
|
||||
"GOPATH="+filepath.Join(c.workdir, "gopath"),
|
||||
"GO111MODULE=off",
|
||||
)
|
||||
if out, err := cmd.CombinedOutput(); err != nil {
|
||||
c.failf("failed to execute go build: %v\n%v", err, string(out))
|
||||
}
|
||||
return outf
|
||||
}
|
||||
|
||||
func (c *Context) calcIgnore() {
|
||||
// No reason to instrument these.
|
||||
c.ignore = map[string]bool{
|
||||
"runtime/cgo": true,
|
||||
"runtime/pprof": true,
|
||||
"runtime/race": true,
|
||||
}
|
||||
|
||||
// Roots: must not instrument these, nor any of their dependencies, to avoid import cycles.
|
||||
// Fortunately, these are mostly packages that are non-deterministic,
|
||||
// noisy (because they are low level), and/or not interesting.
|
||||
// We could manually maintain this list, but that makes go-fuzz-build
|
||||
// fragile in the face of internal standard library package changes.
|
||||
roots := c.packagesNamed("runtime", "github.com/dvyukov/go-fuzz/go-fuzz-dep")
|
||||
packages.Visit(roots, func(p *packages.Package) bool {
|
||||
c.ignore[p.PkgPath] = true
|
||||
return true
|
||||
}, nil)
|
||||
|
||||
// Ignore any packages requested explicitly by the user.
|
||||
paths := strings.Split(*flagPreserve, ",")
|
||||
for _, path := range paths {
|
||||
c.ignore[path] = true
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) gatherLiterals() map[Literal]struct{} {
|
||||
nolits := map[string]bool{
|
||||
"math": true,
|
||||
"os": true,
|
||||
"unicode": true,
|
||||
}
|
||||
|
||||
lits := make(map[Literal]struct{})
|
||||
visit := func(pkg *packages.Package) {
|
||||
if c.ignore[pkg.PkgPath] || nolits[pkg.PkgPath] {
|
||||
return
|
||||
}
|
||||
for _, f := range pkg.Syntax {
|
||||
ast.Walk(&LiteralCollector{lits: lits, ctxt: c}, f)
|
||||
}
|
||||
}
|
||||
|
||||
packages.Visit(c.pkgs, nil, visit)
|
||||
return lits
|
||||
}
|
||||
|
||||
func (c *Context) copyFuzzDep() {
|
||||
// Standard library packages can't depend on non-standard ones.
|
||||
// So we pretend that go-fuzz-dep is a standard one.
|
||||
// go-fuzz-dep depends on go-fuzz-defs, which creates a problem.
|
||||
// Fortunately (and intentionally), go-fuzz-defs contains only constants,
|
||||
// which can be duplicated safely.
|
||||
// So we eliminate the import statement and copy go-fuzz-defs/defs.go
|
||||
// directly into the go-fuzz-dep package.
|
||||
newDir := filepath.Join(c.workdir, "goroot", "src", "go-fuzz-dep")
|
||||
c.mkdirAll(newDir)
|
||||
dep := c.packageNamed("github.com/dvyukov/go-fuzz/go-fuzz-dep")
|
||||
for _, f := range dep.GoFiles {
|
||||
data := c.readFile(f)
|
||||
// Eliminate the dot import.
|
||||
data = bytes.Replace(data, []byte(`. "github.com/dvyukov/go-fuzz/go-fuzz-defs"`), nil, -1)
|
||||
c.writeFile(filepath.Join(newDir, filepath.Base(f)), data)
|
||||
}
|
||||
|
||||
defs := c.packageNamed("github.com/dvyukov/go-fuzz/go-fuzz-defs")
|
||||
for _, f := range defs.GoFiles {
|
||||
data := c.readFile(f)
|
||||
// Adjust package name to match go-fuzz-deps.
|
||||
data = bytes.Replace(data, []byte("\npackage base"), []byte("\npackage gofuzzdep"), -1)
|
||||
c.writeFile(filepath.Join(newDir, "defs.go"), data)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) funcMain() []byte {
|
||||
t := mainSrc
|
||||
if *flagLibFuzzer {
|
||||
t = mainSrcLibFuzzer
|
||||
}
|
||||
dot := map[string]interface{}{"Pkg": c.fuzzpkg.PkgPath, "AllFuncs": c.allFuncs, "DefaultFunc": *flagFunc}
|
||||
buf := new(bytes.Buffer)
|
||||
if err := t.Execute(buf, dot); err != nil {
|
||||
c.failf("could not execute template: %v", err)
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (c *Context) createFuzzMain() string {
|
||||
mainPkg := filepath.Join(c.fuzzpkg.PkgPath, "go.fuzz.main")
|
||||
path := filepath.Join(c.workdir, "gopath", "src", mainPkg)
|
||||
c.mkdirAll(path)
|
||||
c.writeFile(filepath.Join(path, "main.go"), c.funcMain())
|
||||
return mainPkg
|
||||
}
|
||||
|
||||
func (c *Context) clonePackage(p *packages.Package) {
|
||||
root := "goroot"
|
||||
if !c.std[p.PkgPath] {
|
||||
root = "gopath"
|
||||
}
|
||||
newDir := filepath.Join(c.workdir, root, "src", p.PkgPath)
|
||||
c.mkdirAll(newDir)
|
||||
|
||||
if p.PkgPath == "unsafe" {
|
||||
// Write a dummy file. go/packages explicitly returns an empty GoFiles for it,
|
||||
// for reasons that are unclear, but cmd/go wants there to be a Go file in the package.
|
||||
c.writeFile(filepath.Join(newDir, "unsafe.go"), []byte(`package unsafe`))
|
||||
return
|
||||
}
|
||||
|
||||
// Copy all the source code.
|
||||
|
||||
// Use GoFiles instead of CompiledGoFiles here.
|
||||
// If we use CompiledGoFiles, we end up with code that cmd/go won't compile.
|
||||
// See https://golang.org/issue/30479 and Context.instrumentPackages.
|
||||
for _, f := range p.GoFiles {
|
||||
dst := filepath.Join(newDir, filepath.Base(f))
|
||||
c.copyFile(f, dst)
|
||||
}
|
||||
for _, f := range p.OtherFiles {
|
||||
dst := filepath.Join(newDir, filepath.Base(f))
|
||||
c.copyFile(f, dst)
|
||||
}
|
||||
|
||||
// TODO: do we need to look for and copy go.mod?
|
||||
}
|
||||
|
||||
// packageNamed extracts the package listed in path.
|
||||
func (c *Context) packageNamed(path string) (pkgs *packages.Package) {
|
||||
all := c.packagesNamed(path)
|
||||
if len(all) != 1 {
|
||||
c.failf("got multiple packages, requested only %v", path)
|
||||
}
|
||||
return all[0]
|
||||
}
|
||||
|
||||
// packagesNamed extracts the packages listed in paths.
|
||||
func (c *Context) packagesNamed(paths ...string) (pkgs []*packages.Package) {
|
||||
pre := func(p *packages.Package) bool {
|
||||
for _, path := range paths {
|
||||
if p.PkgPath == path {
|
||||
pkgs = append(pkgs, p)
|
||||
break
|
||||
}
|
||||
}
|
||||
return len(pkgs) < len(paths) // continue only if we have not succeeded yet
|
||||
}
|
||||
packages.Visit(c.pkgs, pre, nil)
|
||||
return pkgs
|
||||
}
|
||||
|
||||
func (c *Context) instrumentPackages(blocks *[]CoverBlock, sonar *[]CoverBlock) {
|
||||
visit := func(pkg *packages.Package) {
|
||||
if c.ignore[pkg.PkgPath] {
|
||||
return
|
||||
}
|
||||
|
||||
root := "goroot"
|
||||
if !c.std[pkg.PkgPath] {
|
||||
root = "gopath"
|
||||
}
|
||||
path := filepath.Join(c.workdir, root, "src", pkg.PkgPath) // TODO: need filepath.FromSlash for pkg.PkgPath?
|
||||
|
||||
for i, fullName := range pkg.CompiledGoFiles {
|
||||
fname := filepath.Base(fullName)
|
||||
if !strings.HasSuffix(fname, ".go") {
|
||||
// This is a cgo-generated file.
|
||||
// Instrumenting it currently does not work.
|
||||
// We copied the original Go file as part of copyPackageRewrite,
|
||||
// so we can just skip this one.
|
||||
// See https://golang.org/issue/30479.
|
||||
continue
|
||||
}
|
||||
f := pkg.Syntax[i]
|
||||
|
||||
// TODO: rename trimComments?
|
||||
f.Comments = trimComments(f, pkg.Fset)
|
||||
|
||||
buf := new(bytes.Buffer)
|
||||
content := c.readFile(fullName)
|
||||
buf.Write(initialComments(content)) // Retain '// +build' directives.
|
||||
instrument(pkg.PkgPath, fullName, pkg.Fset, f, pkg.TypesInfo, buf, blocks, sonar)
|
||||
tmp := c.tempFile()
|
||||
c.writeFile(tmp, buf.Bytes())
|
||||
outpath := filepath.Join(path, fname)
|
||||
if runtime.GOOS == "windows" {
|
||||
os.Remove(outpath)
|
||||
}
|
||||
c.moveFile(tmp, outpath)
|
||||
}
|
||||
}
|
||||
|
||||
packages.Visit(c.pkgs, nil, visit)
|
||||
}
|
||||
|
||||
func (c *Context) copyDir(dir, newDir string) {
|
||||
c.mkdirAll(newDir)
|
||||
files, err := ioutil.ReadDir(dir)
|
||||
if err != nil {
|
||||
c.failf("failed to scan dir '%v': %v", dir, err)
|
||||
}
|
||||
for _, f := range files {
|
||||
if f.IsDir() {
|
||||
c.copyDir(filepath.Join(dir, f.Name()), filepath.Join(newDir, f.Name()))
|
||||
continue
|
||||
}
|
||||
src := filepath.Join(dir, f.Name())
|
||||
dst := filepath.Join(newDir, f.Name())
|
||||
c.copyFile(src, dst)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) copyFile(src, dst string) {
|
||||
r, err := os.Open(src)
|
||||
if err != nil {
|
||||
c.failf("copyFile: could not read %v", src, err)
|
||||
}
|
||||
w, err := os.OpenFile(dst, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0700)
|
||||
if err != nil {
|
||||
c.failf("copyFile: could not write %v: %v", dst, err)
|
||||
}
|
||||
if _, err := io.Copy(w, r); err != nil {
|
||||
c.failf("copyFile: copying failed: %v", err)
|
||||
}
|
||||
if err := r.Close(); err != nil {
|
||||
c.failf("copyFile: closing %v failed: %v", src, err)
|
||||
}
|
||||
if err := w.Close(); err != nil {
|
||||
c.failf("copyFile: closing %v failed: %v", dst, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) moveFile(src, dst string) {
|
||||
c.copyFile(src, dst)
|
||||
err := os.Remove(src)
|
||||
if err != nil {
|
||||
c.failf("moveFile: removing %q failed: %v", src, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) failf(str string, args ...interface{}) {
|
||||
c.cleanup()
|
||||
fmt.Fprintf(os.Stderr, str+"\n", args...)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// tempFile creates and deletes a temp file, and returns its path.
|
||||
// This is helpful when you need a temp path for an output file
|
||||
// that will be created by an external tool (go build) or by a call to writeFile.
|
||||
func (c *Context) tempFile() string {
|
||||
outf, err := ioutil.TempFile("", "go-fuzz")
|
||||
if err != nil {
|
||||
c.failf("failed to create temp file: %v", err)
|
||||
}
|
||||
outf.Close()
|
||||
os.Remove(outf.Name()) // necessary on Windows
|
||||
return outf.Name()
|
||||
}
|
||||
|
||||
func (c *Context) readFile(name string) []byte {
|
||||
data, err := ioutil.ReadFile(name)
|
||||
if err != nil {
|
||||
c.failf("failed to read temp file: %v", err)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *Context) writeFile(name string, data []byte) {
|
||||
if err := ioutil.WriteFile(name, data, 0700); err != nil {
|
||||
c.failf("failed to write temp file: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Context) mkdirAll(dir string) {
|
||||
if err := os.MkdirAll(dir, 0700); err != nil {
|
||||
c.failf("failed to create temp dir: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
var mainSrc = template.Must(template.New("main").Parse(`
|
||||
package main
|
||||
|
||||
import (
|
||||
target "{{.Pkg}}"
|
||||
dep "go-fuzz-dep"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fns := []func([]byte)int {
|
||||
{{range .AllFuncs}}
|
||||
target.{{.}},
|
||||
{{end}}
|
||||
}
|
||||
dep.Main(fns)
|
||||
}
|
||||
`))
|
||||
|
||||
var mainSrcLibFuzzer = template.Must(template.New("main").Parse(`
|
||||
package main
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
"reflect"
|
||||
target "{{.Pkg}}"
|
||||
dep "go-fuzz-dep"
|
||||
)
|
||||
|
||||
// #cgo CFLAGS: -Wall -Werror
|
||||
// #ifdef __linux__
|
||||
// __attribute__((weak, section("__libfuzzer_extra_counters")))
|
||||
// #else
|
||||
// #error Currently only Linux is supported
|
||||
// #endif
|
||||
// unsigned char GoFuzzCoverageCounters[65536];
|
||||
import "C"
|
||||
|
||||
//export LLVMFuzzerInitialize
|
||||
func LLVMFuzzerInitialize(argc uintptr, argv uintptr) int {
|
||||
dep.Initialize(unsafe.Pointer(&C.GoFuzzCoverageCounters[0]), 65536)
|
||||
return 0
|
||||
}
|
||||
|
||||
//export LLVMFuzzerTestOneInput
|
||||
func LLVMFuzzerTestOneInput(data uintptr, size uint64) int {
|
||||
sh := &reflect.SliceHeader{
|
||||
Data: data,
|
||||
Len: int(size),
|
||||
Cap: int(size),
|
||||
}
|
||||
|
||||
input := *(*[]byte)(unsafe.Pointer(sh))
|
||||
target.{{.DefaultFunc}}(input)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func main() {
|
||||
}
|
||||
`))
|
36
vendor/github.com/dvyukov/go-fuzz/go-fuzz-defs/defs.go
generated
vendored
Normal file
36
vendor/github.com/dvyukov/go-fuzz/go-fuzz-defs/defs.go
generated
vendored
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// Package defs provides constants required by go-fuzz-build, go-fuzz, and instrumented code.
|
||||
package base
|
||||
|
||||
// This package has a special interaction with go-fuzz-dep:
|
||||
// It is copied into a package with it by go-fuzz-build.
|
||||
// Only things that can be safely duplicated without confusion,
|
||||
// like constants, should be added to this package.
|
||||
// And any additions should be tested carefully. :)
|
||||
|
||||
const (
|
||||
CoverSize = 64 << 10
|
||||
MaxInputSize = 1 << 20
|
||||
SonarRegionSize = 1 << 20
|
||||
)
|
||||
|
||||
const (
|
||||
SonarEQL = iota
|
||||
SonarNEQ
|
||||
SonarLSS
|
||||
SonarGTR
|
||||
SonarLEQ
|
||||
SonarGEQ
|
||||
|
||||
SonarOpMask = 7
|
||||
SonarLength = 1 << 3
|
||||
SonarSigned = 1 << 4
|
||||
SonarString = 1 << 5
|
||||
SonarConst1 = 1 << 6
|
||||
SonarConst2 = 1 << 7
|
||||
|
||||
SonarHdrLen = 6
|
||||
SonarMaxLen = 20
|
||||
)
|
22
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/cover.go
generated
vendored
Normal file
22
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/cover.go
generated
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2019 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// +build gofuzz
|
||||
|
||||
package gofuzzdep
|
||||
|
||||
import (
|
||||
. "github.com/dvyukov/go-fuzz/go-fuzz-defs"
|
||||
)
|
||||
|
||||
// Bool is just a bool.
|
||||
// It is used by code autogenerated by go-fuzz-build
|
||||
// to avoid compilation errors when a user's code shadows the built-in bool.
|
||||
type Bool = bool
|
||||
|
||||
// CoverTab holds code coverage.
|
||||
// It is initialized to a new array so that instrumentation
|
||||
// executed during process initialization has somewhere to write to.
|
||||
// It is replaced by a newly initialized array when it is
|
||||
// time for actual instrumentation to commence.
|
||||
var CoverTab = new([CoverSize]byte)
|
10
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/doc.go
generated
vendored
Normal file
10
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/doc.go
generated
vendored
Normal file
@ -0,0 +1,10 @@
|
||||
// Package gofuzzdep contains the business logic used to monitor the fuzzing.
|
||||
//
|
||||
// It is handled specially by go-fuzz-build; see the comments in package go-fuzz-defs.
|
||||
//
|
||||
// Be particularly careful about adding imports to go-fuzz-dep:
|
||||
// Any package imported by go-fuzz-dep cannot be instrumented (on pain of import cycles),
|
||||
// which reduces the effectiveness of go-fuzz on any other package that imports it.
|
||||
// That is why (e.g.) there are hand-rolled serialization functions instead of using encoding/binary,
|
||||
// and hand-rolled syscall-based communication instead of using package net or os.
|
||||
package gofuzzdep
|
99
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main.go
generated
vendored
Normal file
99
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main.go
generated
vendored
Normal file
@ -0,0 +1,99 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// +build gofuzz
|
||||
// +build !gofuzz_libfuzzer
|
||||
|
||||
package gofuzzdep
|
||||
|
||||
import (
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
"syscall"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
. "github.com/dvyukov/go-fuzz/go-fuzz-defs"
|
||||
)
|
||||
|
||||
func Main(fns []func([]byte) int) {
|
||||
mem, inFD, outFD := setupCommFile()
|
||||
CoverTab = (*[CoverSize]byte)(unsafe.Pointer(&mem[0]))
|
||||
input := mem[CoverSize : CoverSize+MaxInputSize]
|
||||
sonarRegion = mem[CoverSize+MaxInputSize:]
|
||||
runtime.GOMAXPROCS(1) // makes coverage more deterministic, we parallelize on higher level
|
||||
for {
|
||||
fnidx, n := read(inFD)
|
||||
if n > uint64(len(input)) {
|
||||
println("invalid input length")
|
||||
syscall.Exit(1)
|
||||
}
|
||||
for i := range CoverTab {
|
||||
CoverTab[i] = 0
|
||||
}
|
||||
atomic.StoreUint32(&sonarPos, 0)
|
||||
t0 := time.Now()
|
||||
res := fns[fnidx](input[:n:n])
|
||||
ns := time.Since(t0)
|
||||
write(outFD, uint64(res), uint64(ns), uint64(atomic.LoadUint32(&sonarPos)))
|
||||
}
|
||||
}
|
||||
|
||||
// read reads little-endian-encoded uint8+uint64 from fd.
|
||||
func read(fd FD) (uint8, uint64) {
|
||||
rd := 0
|
||||
var buf [9]byte
|
||||
for rd != len(buf) {
|
||||
n, err := fd.read(buf[rd:])
|
||||
if err == syscall.EINTR {
|
||||
continue
|
||||
}
|
||||
if n == 0 {
|
||||
syscall.Exit(1)
|
||||
}
|
||||
if err != nil {
|
||||
println("failed to read fd =", fd, "errno =", err.(syscall.Errno))
|
||||
syscall.Exit(1)
|
||||
}
|
||||
rd += n
|
||||
}
|
||||
return buf[0], deserialize64(buf[1:])
|
||||
}
|
||||
|
||||
// write writes little-endian-encoded vals... to fd.
|
||||
func write(fd FD, vals ...uint64) {
|
||||
var tmp [3 * 8]byte
|
||||
buf := tmp[:len(vals)*8]
|
||||
for i, v := range vals {
|
||||
serialize64(buf[i*8:], v)
|
||||
}
|
||||
wr := 0
|
||||
for wr != len(buf) {
|
||||
n, err := fd.write(buf[wr:])
|
||||
if err == syscall.EINTR {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
println("failed to read fd =", fd, "errno =", err.(syscall.Errno))
|
||||
syscall.Exit(1)
|
||||
}
|
||||
wr += n
|
||||
}
|
||||
}
|
||||
|
||||
// writeStr writes strings s to fd.
|
||||
func writeStr(fd FD, s string) {
|
||||
buf := []byte(s)
|
||||
wr := 0
|
||||
for wr != len(buf) {
|
||||
n, err := fd.write(buf[wr:])
|
||||
if err == syscall.EINTR {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
println("failed to read fd =", fd, "errno =", err.(syscall.Errno))
|
||||
syscall.Exit(1)
|
||||
}
|
||||
wr += n
|
||||
}
|
||||
}
|
20
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main_libFuzzer.go
generated
vendored
Normal file
20
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/main_libFuzzer.go
generated
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// +build gofuzz
|
||||
// +build gofuzz_libfuzzer
|
||||
|
||||
package gofuzzdep
|
||||
|
||||
import (
|
||||
"unsafe"
|
||||
|
||||
. "github.com/dvyukov/go-fuzz/go-fuzz-defs"
|
||||
)
|
||||
|
||||
func Initialize(coverTabPtr unsafe.Pointer, coverTabSize uint64) {
|
||||
if coverTabSize != CoverSize {
|
||||
panic("Incorrect cover tab size")
|
||||
}
|
||||
CoverTab = (*[CoverSize]byte)(coverTabPtr)
|
||||
}
|
201
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sonar.go
generated
vendored
Normal file
201
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sonar.go
generated
vendored
Normal file
@ -0,0 +1,201 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// +build gofuzz
|
||||
|
||||
package gofuzzdep
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"unsafe"
|
||||
|
||||
. "github.com/dvyukov/go-fuzz/go-fuzz-defs"
|
||||
)
|
||||
|
||||
var (
|
||||
sonarRegion []byte
|
||||
sonarPos uint32
|
||||
)
|
||||
|
||||
const failure = ^uint8(0)
|
||||
|
||||
type iface struct {
|
||||
typ unsafe.Pointer
|
||||
val unsafe.Pointer
|
||||
}
|
||||
|
||||
// Sonar is called by instrumentation code to notify go-fuzz about comparisons.
|
||||
// Low 8 bits of id are flags, the rest is unique id of a comparison.
|
||||
func Sonar(v1, v2 interface{}, id uint32) {
|
||||
var buf [SonarHdrLen + 2*SonarMaxLen]byte
|
||||
n1, f1 := serialize(v1, v2, buf[SonarHdrLen:])
|
||||
if n1 == failure {
|
||||
return
|
||||
}
|
||||
n2, f2 := serialize(v2, v1, buf[SonarHdrLen+n1:])
|
||||
if n2 == failure {
|
||||
return
|
||||
}
|
||||
// Ideal const operands are converted to signed int,
|
||||
// but it does not mean that the comparison is signed
|
||||
// unless the other operand is signed.
|
||||
if id&SonarConst1 != 0 {
|
||||
f1 &^= SonarSigned
|
||||
}
|
||||
if id&SonarConst2 != 0 {
|
||||
f2 &^= SonarSigned
|
||||
}
|
||||
id |= uint32(f1 | f2)
|
||||
serialize32(buf[:], id)
|
||||
buf[4] = n1
|
||||
buf[5] = n2
|
||||
n := uint32(SonarHdrLen + n1 + n2)
|
||||
pos := atomic.LoadUint32(&sonarPos)
|
||||
for {
|
||||
if pos+n > uint32(len(sonarRegion)) {
|
||||
return
|
||||
}
|
||||
if atomic.CompareAndSwapUint32(&sonarPos, pos, pos+n) {
|
||||
break
|
||||
}
|
||||
pos = atomic.LoadUint32(&sonarPos)
|
||||
}
|
||||
copy(sonarRegion[pos:pos+n], buf[:])
|
||||
}
|
||||
|
||||
func serialize(v, v2 interface{}, buf []byte) (n, flags uint8) {
|
||||
switch vv := v.(type) {
|
||||
case int8:
|
||||
buf[0] = byte(vv)
|
||||
return 1, SonarSigned
|
||||
case uint8:
|
||||
buf[0] = byte(vv)
|
||||
return 1, 0
|
||||
case int16:
|
||||
return serialize16(buf, uint16(vv)), SonarSigned
|
||||
case uint16:
|
||||
return serialize16(buf, vv), 0
|
||||
case int32:
|
||||
return serialize32(buf, uint32(vv)), SonarSigned
|
||||
case uint32:
|
||||
return serialize32(buf, vv), 0
|
||||
case int64:
|
||||
return serialize64(buf, uint64(vv)), SonarSigned
|
||||
case uint64:
|
||||
return serialize64(buf, vv), 0
|
||||
case int:
|
||||
if unsafe.Sizeof(vv) == 4 {
|
||||
return serialize32(buf, uint32(vv)), SonarSigned
|
||||
} else {
|
||||
return serialize64(buf, uint64(vv)), SonarSigned
|
||||
}
|
||||
case uint:
|
||||
if unsafe.Sizeof(vv) == 4 {
|
||||
return serialize32(buf, uint32(vv)), 0
|
||||
} else {
|
||||
return serialize64(buf, uint64(vv)), 0
|
||||
}
|
||||
case string:
|
||||
if len(vv) > SonarMaxLen {
|
||||
return failure, 0
|
||||
}
|
||||
return uint8(copy(buf, vv)), SonarString
|
||||
case [1]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [2]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [3]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [4]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [5]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [6]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [7]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [8]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [9]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [10]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [11]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [12]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [13]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [14]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [15]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [16]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [17]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [18]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [19]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
case [20]byte:
|
||||
return uint8(copy(buf, vv[:])), SonarString
|
||||
default:
|
||||
// Special case: string literal is compared with a variable of
|
||||
// user type with string underlying type:
|
||||
// type Name string
|
||||
// var name Name
|
||||
// if name == "foo" { ... }
|
||||
if _, ok := v2.(string); ok {
|
||||
s := *(*string)((*iface)(unsafe.Pointer(&v)).val)
|
||||
if len(s) <= SonarMaxLen {
|
||||
return uint8(copy(buf[:], s)), SonarString
|
||||
}
|
||||
}
|
||||
return failure, 0
|
||||
}
|
||||
}
|
||||
|
||||
// The serialization routines here match those of encoding/binary.LittleEndian.
|
||||
// They are copied here because importing encoding/binary creates import cycles.
|
||||
|
||||
func serialize16(buf []byte, v uint16) uint8 {
|
||||
_ = buf[1]
|
||||
buf[0] = byte(v >> 0)
|
||||
buf[1] = byte(v >> 8)
|
||||
return 2
|
||||
}
|
||||
|
||||
func serialize32(buf []byte, v uint32) uint8 {
|
||||
_ = buf[3]
|
||||
buf[0] = byte(v >> 0)
|
||||
buf[1] = byte(v >> 8)
|
||||
buf[2] = byte(v >> 16)
|
||||
buf[3] = byte(v >> 24)
|
||||
return 4
|
||||
}
|
||||
|
||||
func serialize64(buf []byte, v uint64) uint8 {
|
||||
_ = buf[7]
|
||||
buf[0] = byte(v >> 0)
|
||||
buf[1] = byte(v >> 8)
|
||||
buf[2] = byte(v >> 16)
|
||||
buf[3] = byte(v >> 24)
|
||||
buf[4] = byte(v >> 32)
|
||||
buf[5] = byte(v >> 40)
|
||||
buf[6] = byte(v >> 48)
|
||||
buf[7] = byte(v >> 56)
|
||||
return 8
|
||||
}
|
||||
|
||||
func deserialize64(buf []byte) uint64 {
|
||||
_ = buf[7]
|
||||
return uint64(buf[0])<<0 |
|
||||
uint64(buf[1])<<8 |
|
||||
uint64(buf[2])<<16 |
|
||||
uint64(buf[3])<<24 |
|
||||
uint64(buf[4])<<32 |
|
||||
uint64(buf[5])<<40 |
|
||||
uint64(buf[6])<<48 |
|
||||
uint64(buf[7])<<56
|
||||
}
|
32
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_posix.go
generated
vendored
Normal file
32
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_posix.go
generated
vendored
Normal file
@ -0,0 +1,32 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// +build darwin linux freebsd dragonfly openbsd netbsd
|
||||
// +build gofuzz
|
||||
|
||||
package gofuzzdep
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
. "github.com/dvyukov/go-fuzz/go-fuzz-defs"
|
||||
)
|
||||
|
||||
type FD int
|
||||
|
||||
func setupCommFile() ([]byte, FD, FD) {
|
||||
mem, err := syscall.Mmap(3, 0, CoverSize+MaxInputSize+SonarRegionSize, syscall.PROT_READ|syscall.PROT_WRITE, syscall.MAP_SHARED)
|
||||
if err != nil {
|
||||
println("failed to mmap fd = 3 errno =", err.(syscall.Errno))
|
||||
syscall.Exit(1)
|
||||
}
|
||||
return mem, 4, 5
|
||||
}
|
||||
|
||||
func (fd FD) read(buf []byte) (int, error) {
|
||||
return syscall.Read(int(fd), buf)
|
||||
}
|
||||
|
||||
func (fd FD) write(buf []byte) (int, error) {
|
||||
return syscall.Write(int(fd), buf)
|
||||
}
|
56
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_windows.go
generated
vendored
Normal file
56
vendor/github.com/dvyukov/go-fuzz/go-fuzz-dep/sys_windows.go
generated
vendored
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// +build gofuzz
|
||||
|
||||
package gofuzzdep
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
. "github.com/dvyukov/go-fuzz/go-fuzz-defs"
|
||||
)
|
||||
|
||||
// Can't import reflect because of import cycles.
|
||||
type sliceHeader struct {
|
||||
addr uintptr
|
||||
l, c int
|
||||
}
|
||||
|
||||
type FD syscall.Handle
|
||||
|
||||
func setupCommFile() ([]byte, FD, FD) {
|
||||
const (
|
||||
size = CoverSize + MaxInputSize + SonarRegionSize
|
||||
FILE_MAP_ALL_ACCESS = 0xF001F
|
||||
)
|
||||
mapping := readEnvParam("GO_FUZZ_COMM_FD")
|
||||
addr, err := syscall.MapViewOfFile(mapping, FILE_MAP_ALL_ACCESS, 0, 0, size)
|
||||
if err != nil {
|
||||
println("failed to mmap comm file:", err.Error())
|
||||
syscall.Exit(1)
|
||||
}
|
||||
hdr := sliceHeader{addr, size, size}
|
||||
mem := *(*[]byte)(unsafe.Pointer(&hdr))
|
||||
in := FD(readEnvParam("GO_FUZZ_IN_FD"))
|
||||
out := FD(readEnvParam("GO_FUZZ_OUT_FD"))
|
||||
return mem, in, out
|
||||
}
|
||||
|
||||
func readEnvParam(name string) syscall.Handle {
|
||||
v, _ := syscall.Getenv(name)
|
||||
var x uintptr
|
||||
for i := 0; i < len(v); i++ {
|
||||
x = x*10 + uintptr(v[i]-'0')
|
||||
}
|
||||
return syscall.Handle(x)
|
||||
}
|
||||
|
||||
func (fd FD) read(buf []byte) (int, error) {
|
||||
return syscall.Read(syscall.Handle(fd), buf)
|
||||
}
|
||||
|
||||
func (fd FD) write(buf []byte) (int, error) {
|
||||
return syscall.Write(syscall.Handle(fd), buf)
|
||||
}
|
28
vendor/github.com/dvyukov/go-fuzz/internal/go-fuzz-types/types.go
generated
vendored
Normal file
28
vendor/github.com/dvyukov/go-fuzz/internal/go-fuzz-types/types.go
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
// Copyright 2015 go-fuzz project authors. All rights reserved.
|
||||
// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
|
||||
|
||||
// Package types provides types shared between go-fuzz-build and go-fuzz.
|
||||
package types
|
||||
|
||||
type CoverBlock struct {
|
||||
ID int
|
||||
File string
|
||||
StartLine int
|
||||
StartCol int
|
||||
EndLine int
|
||||
EndCol int
|
||||
NumStmt int
|
||||
}
|
||||
|
||||
type Literal struct {
|
||||
Val string
|
||||
IsStr bool
|
||||
}
|
||||
|
||||
type MetaData struct {
|
||||
Literals []Literal
|
||||
Blocks []CoverBlock
|
||||
Sonar []CoverBlock
|
||||
Funcs []string // fuzz function names; must have length > 0
|
||||
DefaultFunc string // default function to fuzz
|
||||
}
|
6
vendor/modules.txt
vendored
6
vendor/modules.txt
vendored
@ -21,6 +21,12 @@ github.com/OpenPeeDeeP/depguard
|
||||
github.com/bombsimon/wsl/v3
|
||||
# github.com/davecgh/go-spew v1.1.1
|
||||
github.com/davecgh/go-spew/spew
|
||||
# github.com/dvyukov/go-fuzz v0.0.0-20200318091601-be3528f3a813
|
||||
## explicit
|
||||
github.com/dvyukov/go-fuzz/go-fuzz-build
|
||||
github.com/dvyukov/go-fuzz/go-fuzz-defs
|
||||
github.com/dvyukov/go-fuzz/go-fuzz-dep
|
||||
github.com/dvyukov/go-fuzz/internal/go-fuzz-types
|
||||
# github.com/fatih/color v1.7.0
|
||||
github.com/fatih/color
|
||||
# github.com/fsnotify/fsnotify v1.4.7
|
||||
|
Loading…
Reference in New Issue
Block a user