Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nogo: use original source files instead of coverage-instrumented #3770

Merged
merged 11 commits into from
Jan 4, 2024
54 changes: 21 additions & 33 deletions go/tools/builders/compilepkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,15 +203,6 @@ func compileArchive(
}
defer cleanup()

// As part of compilation process, rules_go does generate and/or rewrite code
// based on the original source files. We should only run static analysis
// over original source files and not the generated source as end users have
// little control over the generated source.
//
// nogoSrcsOrigin maps generated/rewritten source files back to original source.
// If the original source path is an empty string, exclude generated source from nogo run.
nogoSrcsOrigin := make(map[string]string)

if len(srcs.goSrcs) == 0 {
// We need to run the compiler to create a valid archive, even if there's nothing in it.
// Otherwise, GoPack will complain if we try to add assembly or cgo objects.
Expand All @@ -237,7 +228,6 @@ func compileArchive(
pkg: "empty",
})

nogoSrcsOrigin[emptyGoFile.Name()] = ""
}
packageName := srcs.goSrcs[0].pkg
var goSrcs, cgoSrcs []string
Expand Down Expand Up @@ -280,8 +270,18 @@ func compileArchive(
// constraints.
compilingWithCgo := haveCgo && cgoEnabled

// When coverage is set, source files will be modified during instrumentation. We should only run static analysis
// over original source files and not the modified ones.
// goSrcsNogo and cgoSrcsNogo are copies of the original source files for nogo to run static analysis.
goSrcsNogo := goSrcs
cgoSrcsNogo := cgoSrcs

// Instrument source files for coverage.
if coverMode != "" {
// deep copy original source files for nogo static analysis, avoid being modified by coverage.
goSrcsNogo = append([]string{}, goSrcs...)
cgoSrcsNogo = append([]string{}, cgoSrcs...)
emmaxy marked this conversation as resolved.
Show resolved Hide resolved
emmaxy marked this conversation as resolved.
Show resolved Hide resolved

relCoverPath := make(map[string]string)
for _, s := range coverSrcs {
relCoverPath[abs(s)] = s
Expand Down Expand Up @@ -324,7 +324,6 @@ func compileArchive(

if i < len(goSrcs) {
goSrcs[i] = coverSrc
nogoSrcsOrigin[coverSrc] = origSrc
continue
}

Expand All @@ -343,6 +342,15 @@ func compileArchive(
if err != nil {
return err
}
if coverMode != "" {
// Compile original source files, not coverage instrumented, for nogo
emmaxy marked this conversation as resolved.
Show resolved Hide resolved
_, goSrcsNogo, _, err = cgo2(goenv, goSrcsNogo, cgoSrcsNogo, cSrcs, cxxSrcs, objcSrcs, objcxxSrcs, sSrcs, hSrcs, packagePath, packageName, cc, cppFlags, cFlags, cxxFlags, objcFlags, objcxxFlags, ldFlags, cgoExportHPath)
emmaxy marked this conversation as resolved.
Show resolved Hide resolved
emmaxy marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return err
}
} else {
goSrcsNogo = goSrcs
}
emmaxy marked this conversation as resolved.
Show resolved Hide resolved

gcFlags = append(gcFlags, createTrimPath(gcFlags, srcDir))
} else {
Expand Down Expand Up @@ -429,31 +437,11 @@ func compileArchive(
// Run nogo concurrently.
var nogoChan chan error
outFactsPath := filepath.Join(workDir, nogoFact)
nogoSrcs := make([]string, 0, len(goSrcs))
for _, goSrc := range goSrcs {
// If source is found in the origin map, that means it's likely to be a generated source file
// so feed the original source file to static analyzers instead of the generated one.
//
// If origin is empty, that means the generated source file is not based on a user-provided source file
// thus ignore that entry entirely.
if originSrc, ok := nogoSrcsOrigin[goSrc]; ok {
if originSrc != "" {
nogoSrcs = append(nogoSrcs, originSrc)
}
continue
}

// TODO(sluongng): most likely what remains here are CGO-generated source files as the result of calling cgo2()
// Need to determine whether we want to feed these CGO-generated files into static analyzers.
//
// Add unknown origin source files into the mix.
nogoSrcs = append(nogoSrcs, goSrc)
}
if nogoPath != "" && len(nogoSrcs) > 0 {
if nogoPath != "" && len(goSrcsNogo) > 0 {
ctx, cancel := context.WithCancel(context.Background())
nogoChan = make(chan error)
go func() {
nogoChan <- runNogo(ctx, workDir, nogoPath, nogoSrcs, deps, packagePath, importcfgPath, outFactsPath)
nogoChan <- runNogo(ctx, workDir, nogoPath, goSrcsNogo, deps, packagePath, importcfgPath, outFactsPath)
}()
defer func() {
if nogoChan != nil {
Expand Down
5 changes: 5 additions & 0 deletions tests/core/nogo/coverage/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,8 @@ go_bazel_test(
name = "gen_code_test",
srcs = ["gen_code_test.go"],
)

go_bazel_test(
name = "coverage_cgo_test",
srcs = ["coverage_cgo_test.go"],
)
124 changes: 124 additions & 0 deletions tests/core/nogo/coverage/coverage_cgo_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
// Copyright 2019 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package coverage_cgo_test

import (
"testing"

"github.com/bazelbuild/rules_go/go/tools/bazel_testing"
)

func TestMain(m *testing.M) {
bazel_testing.TestMain(m, bazel_testing.Args{
Main: `
-- BUILD.bazel --
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test", "nogo")

go_library(
name = "foo",
cgo = True,
srcs = ["foo.go"],
importpath = "foo"
)
emmaxy marked this conversation as resolved.
Show resolved Hide resolved

go_test(
name = "foo_test",
srcs = ["foo_test.go"],
embed = [":foo"]
)

nogo(
name = "nogo",
deps = ["//noinit"],
visibility = ["//visibility:public"],
)
-- foo.go --
package foo

import "C"

func Foo() string {
return "foo"
}
-- foo_test.go --
package foo

import "testing"

func TestFoo(t *testing.T) {
if actual, expected := Foo(), "foo"; actual != expected {
t.Errorf("Foo() should return foo")
}
}
-- noinit/BUILD.bazel --
load("@io_bazel_rules_go//go:def.bzl", "go_library")

go_library(
name = "noinit",
srcs = ["analyzer.go"],
importpath = "noinit",
visibility = ["//visibility:public"],
deps = [
"@org_golang_x_tools//go/analysis",
],
)
-- noinit/analyzer.go --
package noinit
import (
"fmt"
"go/ast"

"golang.org/x/tools/go/analysis"
)

const Name = "gochecknoinits"

var Analyzer = &analysis.Analyzer{
Name: Name,
Doc: "Checks that no init functions are present in Go cod",
emmaxy marked this conversation as resolved.
Show resolved Hide resolved
Run: run,
}

func run(pass *analysis.Pass) (interface{}, error) {
for _, f := range pass.Files {
for _, decl := range f.Decls {
funcDecl, ok := decl.(*ast.FuncDecl)
if !ok {
continue
}
name := funcDecl.Name.Name
if name == "init" && funcDecl.Recv.NumFields() == 0 {
pass.Report(analysis.Diagnostic{
Pos: funcDecl.Pos(),
Message: fmt.Sprintf("don't use init function"),
Category: Name,
})
}
}
}

return nil, nil
}
`,
Nogo: `@//:nogo`,
})
}

func TestCoverageWithNogo(t *testing.T) {
emmaxy marked this conversation as resolved.
Show resolved Hide resolved
if out, err := bazel_testing.BazelOutput("coverage", "//:foo_test"); err != nil {
println(string(out))
t.Fatal(err)
}
}