mirror of
https://github.com/github/codeql.git
synced 2026-04-12 10:34:02 +02:00
593 lines
18 KiB
Go
593 lines
18 KiB
Go
package diagnostics
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/github/codeql-go/extractor/util"
|
|
)
|
|
|
|
type sourceStruct struct {
|
|
Id string `json:"id"`
|
|
Name string `json:"name"`
|
|
ExtractorName string `json:"extractorName"`
|
|
}
|
|
|
|
type diagnosticSeverity string
|
|
|
|
const (
|
|
severityError diagnosticSeverity = "error"
|
|
severityWarning diagnosticSeverity = "warning"
|
|
severityNote diagnosticSeverity = "note"
|
|
)
|
|
|
|
type visibilityStruct struct {
|
|
StatusPage bool `json:"statusPage"` // True if the message should be displayed on the status page (defaults to false)
|
|
CliSummaryTable bool `json:"cliSummaryTable"` // True if the message should be counted in the diagnostics summary table printed by `codeql database analyze` (defaults to false)
|
|
Telemetry bool `json:"telemetry"` // True if the message should be sent to telemetry (defaults to false)
|
|
}
|
|
|
|
var fullVisibility *visibilityStruct = &visibilityStruct{true, true, true}
|
|
var telemetryOnly *visibilityStruct = &visibilityStruct{false, false, true}
|
|
|
|
type locationStruct struct {
|
|
File string `json:"file,omitempty"`
|
|
StartLine int `json:"startLine,omitempty"`
|
|
StartColumn int `json:"startColumn,omitempty"`
|
|
EndLine int `json:"endLine,omitempty"`
|
|
EndColumn int `json:"endColumn,omitempty"`
|
|
}
|
|
|
|
var noLocation *locationStruct = nil
|
|
|
|
type diagnostic struct {
|
|
Timestamp string `json:"timestamp"`
|
|
Source sourceStruct `json:"source"`
|
|
MarkdownMessage string `json:"markdownMessage"`
|
|
Severity string `json:"severity"`
|
|
Visibility *visibilityStruct `json:"visibility,omitempty"` // Use a pointer so that it is omitted if nil
|
|
Location *locationStruct `json:"location,omitempty"` // Use a pointer so that it is omitted if nil
|
|
}
|
|
|
|
var diagnosticsEmitted, diagnosticsLimit uint = 0, 100
|
|
var noDiagnosticDirPrinted bool = false
|
|
|
|
type DiagnosticsWriter interface {
|
|
WriteDiagnostic(d diagnostic)
|
|
}
|
|
|
|
type FileDiagnosticsWriter struct {
|
|
diagnosticDir string
|
|
}
|
|
|
|
func (writer *FileDiagnosticsWriter) WriteDiagnostic(d diagnostic) {
|
|
if writer == nil {
|
|
return
|
|
}
|
|
|
|
content, err := json.Marshal(d)
|
|
if err != nil {
|
|
slog.Error("Failed to encode diagnostic as JSON", slog.Any("err", err))
|
|
return
|
|
}
|
|
|
|
targetFile, err := os.CreateTemp(writer.diagnosticDir, "go-extractor.*.json")
|
|
if err != nil {
|
|
slog.Error("Failed to create diagnostic file", slog.Any("err", err))
|
|
return
|
|
}
|
|
defer func() {
|
|
if err := targetFile.Close(); err != nil {
|
|
slog.Error("Failed to close diagnostic file", slog.Any("err", err))
|
|
}
|
|
}()
|
|
|
|
_, err = targetFile.Write(content)
|
|
if err != nil {
|
|
slog.Error("Failed to write to diagnostic file", slog.Any("err", err))
|
|
}
|
|
}
|
|
|
|
var DefaultWriter *FileDiagnosticsWriter = nil
|
|
|
|
func NewFileDiagnosticsWriter() *FileDiagnosticsWriter {
|
|
diagnosticDir := os.Getenv("CODEQL_EXTRACTOR_GO_DIAGNOSTIC_DIR")
|
|
if diagnosticDir == "" {
|
|
if !noDiagnosticDirPrinted {
|
|
slog.Warn("No diagnostic directory set, so not emitting diagnostics")
|
|
noDiagnosticDirPrinted = true
|
|
}
|
|
return nil
|
|
}
|
|
|
|
return &FileDiagnosticsWriter{diagnosticDir}
|
|
}
|
|
|
|
func init() {
|
|
DefaultWriter = NewFileDiagnosticsWriter()
|
|
}
|
|
|
|
// Emits a diagnostic using the specified `DiagnosticsWriter`.
|
|
func emitDiagnosticTo(writer DiagnosticsWriter, sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
|
|
if diagnosticsEmitted < diagnosticsLimit {
|
|
diagnosticsEmitted += 1
|
|
|
|
timestamp := time.Now().UTC().Format("2006-01-02T15:04:05.000") + "Z"
|
|
|
|
var d diagnostic
|
|
|
|
if diagnosticsEmitted < diagnosticsLimit {
|
|
d = diagnostic{
|
|
timestamp,
|
|
sourceStruct{sourceid, sourcename, "go"},
|
|
markdownMessage,
|
|
string(severity),
|
|
visibility,
|
|
location,
|
|
}
|
|
} else {
|
|
d = diagnostic{
|
|
timestamp,
|
|
sourceStruct{"go/autobuilder/diagnostic-limit-reached", "Diagnostics limit exceeded", "go"},
|
|
fmt.Sprintf("CodeQL has produced more than the maximum number of diagnostics. Only the first %d have been reported.", diagnosticsLimit),
|
|
string(severityWarning),
|
|
fullVisibility,
|
|
noLocation,
|
|
}
|
|
}
|
|
|
|
writer.WriteDiagnostic(d)
|
|
}
|
|
}
|
|
|
|
// Emits a diagnostic using the default `DiagnosticsWriter`.
|
|
func emitDiagnostic(sourceid, sourcename, markdownMessage string, severity diagnosticSeverity, visibility *visibilityStruct, location *locationStruct) {
|
|
emitDiagnosticTo(DefaultWriter, sourceid, sourcename, markdownMessage, severity, visibility, location)
|
|
}
|
|
|
|
func EmitPackageDifferentOSArchitecture(pkgPath string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/package-different-os-architecture",
|
|
"An imported package is intended for a different OS or architecture",
|
|
"`"+pkgPath+"` could not be imported. Make sure the `GOOS` and `GOARCH` [environment variables are correctly set](https://docs.github.com/en/actions/learn-github-actions/variables#defining-environment-variables-for-a-single-workflow). Alternatively, [change your OS and architecture](https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners#using-a-github-hosted-runner).",
|
|
severityWarning,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func plural(n int, singular, plural string) string {
|
|
if n == 1 {
|
|
return singular
|
|
} else {
|
|
return plural
|
|
}
|
|
}
|
|
|
|
const maxNumPkgPaths = 5
|
|
|
|
func EmitCannotFindPackages(writer DiagnosticsWriter, pkgPaths []string) {
|
|
numPkgPaths := len(pkgPaths)
|
|
|
|
numPrinted := numPkgPaths
|
|
truncated := false
|
|
if numPrinted > maxNumPkgPaths {
|
|
numPrinted = maxNumPkgPaths
|
|
truncated = true
|
|
}
|
|
|
|
secondLine := "`" + strings.Join(pkgPaths[0:numPrinted], "`, `") + "`"
|
|
if truncated {
|
|
secondLine += fmt.Sprintf(" and %d more", numPkgPaths-maxNumPkgPaths)
|
|
}
|
|
|
|
message := fmt.Sprintf(
|
|
"%d package%s could not be found:\n\n%s.\n\n"+
|
|
"CodeQL is able to analyze your code without those packages, but definitions from them may not be recognized and "+
|
|
"source files that use them may only be partially analyzed.\n\n"+
|
|
"To ensure that you have comprehensive alert coverage, check that the paths are correct and make sure any private packages can be accessed by CodeQL. ",
|
|
numPkgPaths,
|
|
plural(len(pkgPaths), "", "s"),
|
|
secondLine,
|
|
)
|
|
|
|
// Depending on the environment we are running in, provide a different message for how to configure access to private registries.
|
|
if util.IsDynamicActionsWorkflow() {
|
|
// For GitHub-managed (dynamic) workflows, we offer built-in support for private registries that customers can set up.
|
|
message = message +
|
|
"Organizations [can grant access to private registries for GitHub security products](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). "
|
|
} else {
|
|
if util.IsActionsWorkflow() {
|
|
// For custom workflows, users can add a workflow step to set up credentials or environment variables.
|
|
message = message +
|
|
"To set up access to a private registry, add a step to your workflow which sets up the necessary credentials and environment variables. "
|
|
} else {
|
|
// Otherwise, we are running locally or in some other CI system.
|
|
message = message +
|
|
"To set up access to private registries, ensure that the necessary credentials and environment variables are set up for `go` to use. "
|
|
}
|
|
|
|
// This should be less likely since we improved Go project discovery. We only include it in the message if we are not running in a
|
|
// GitHub-managed workflow, since users would not be able to act on this there.
|
|
message = message +
|
|
"If any of the packages are already present in the repository, but were not found, then you may need a [custom build command](https://docs.github.com/en/code-security/how-tos/scan-code-for-vulnerabilities/manage-your-configuration/codeql-code-scanning-for-compiled-languages)."
|
|
}
|
|
|
|
emitDiagnosticTo(
|
|
writer,
|
|
"go/autobuilder/package-not-found",
|
|
"Some packages could not be found",
|
|
message,
|
|
severityWarning,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitNewerGoVersionNeeded(installedVersion string, requiredVersion string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/newer-go-version-needed",
|
|
"Newer Go version needed",
|
|
"Version `"+installedVersion+"` of Go is installed, but this is lower than `"+requiredVersion+"` required by your project's `go.mod`. [Install a newer version of Go before analyzing your project](https://github.com/actions/setup-go#basic).",
|
|
severityError,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoFilesFoundButNotProcessed() {
|
|
emitDiagnostic(
|
|
"go/autobuilder/go-files-found-but-not-processed",
|
|
"Go files were found but not processed",
|
|
"[Specify a custom build command](https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-the-codeql-workflow-for-compiled-languages) that includes one or more `go build` commands to build the `.go` files to be analyzed.",
|
|
severityError,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitRelativeImportPaths() {
|
|
emitDiagnostic(
|
|
"go/autobuilder/relative-import-paths",
|
|
"Some imports use unsupported relative package paths",
|
|
"You should replace relative package paths (that contain `.` or `..`) with absolute paths. Alternatively you can [use a Go module](https://go.dev/blog/using-go-modules).",
|
|
severityError,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
// The following diagnostics are telemetry-only.
|
|
|
|
func EmitBazelBuildFilesFound(bazelPaths []string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/bazel-build-file-found",
|
|
"Bazel BUILD files were found",
|
|
fmt.Sprintf(
|
|
"%d bazel BUILD %s found:\n\n`%s`",
|
|
len(bazelPaths),
|
|
plural(len(bazelPaths), "file was", "files were"),
|
|
strings.Join(bazelPaths, "`, `")),
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGopkgTomlFound() {
|
|
emitDiagnostic(
|
|
"go/autobuilder/gopkg-toml-found",
|
|
"A dep `Gopkg.toml` file was found",
|
|
"A dep `Gopkg.toml` file was found",
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGlideYamlFound() {
|
|
emitDiagnostic(
|
|
"go/autobuilder/glide-yaml-found",
|
|
"A Glide `glide.yaml` file was found",
|
|
"A Glide `glide.yaml` file was found",
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoWorkFound(goWorkPaths []string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/go-work-found",
|
|
"`go.work` file found",
|
|
fmt.Sprintf(
|
|
"%d `go.work` %s found:\n\n`%s`",
|
|
len(goWorkPaths),
|
|
plural(len(goWorkPaths), "file was", "files were"),
|
|
strings.Join(goWorkPaths, "`, `")),
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoFilesOutsideGoModules(goModPaths []string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/go-files-outside-go-modules",
|
|
"Go files were found outside Go modules",
|
|
"Go files were found outside of the Go modules corresponding to these `go.mod` files.\n\n`"+strings.Join(goModPaths, "`, `")+"`",
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitMultipleGoModFoundNested(goModPaths []string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/multiple-go-mod-found-nested",
|
|
"Multiple `go.mod` files were found, all nested under one root `go.mod` file",
|
|
fmt.Sprintf(
|
|
"%d `go.mod` files were found:\n\n`%s`",
|
|
len(goModPaths),
|
|
strings.Join(goModPaths, "`, `")),
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitMultipleGoModFoundNotNested(goModPaths []string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/multiple-go-mod-found-not-nested",
|
|
"Multiple `go.mod` files found, not all nested under one root `go.mod` file",
|
|
fmt.Sprintf(
|
|
"%d `go.mod` files were found:\n\n`%s`",
|
|
len(goModPaths),
|
|
strings.Join(goModPaths, "`, `")),
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitSingleRootGoModFound(goModPath string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/single-root-go-mod-found",
|
|
"A single `go.mod` file was found in the root",
|
|
"A single `go.mod` file was found.\n\n`"+goModPath+"`",
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitSingleNonRootGoModFound(goModPath string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/single-non-root-go-mod-found",
|
|
"A single, non-root `go.mod` file was found",
|
|
"A single, non-root `go.mod` file was found.\n\n`"+goModPath+"`",
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
// The following diagnostics are related to identifying the build environment.
|
|
|
|
func EmitNoGoModAndNoGoEnv(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-no-go-mod-no-go-env",
|
|
"No `go.mod` file found and no Go version in environment",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitNoGoModAndGoEnvUnsupported(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-no-go-mod-go-env-unsupported",
|
|
"No `go.mod` file found and Go version in environment is unsupported",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitNoGoModAndGoEnvSupported(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-no-go-mod-go-env-supported",
|
|
"No `go.mod` file found and Go version in environment is supported",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooHighAndNoGoEnv(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-high-no-go-env",
|
|
"Go version in `go.mod` file above supported range and no Go version in environment",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooHighAndEnvVersionTooHigh(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-high-go-env-too-high",
|
|
"Go version in `go.mod` file above supported range and Go version in environment above supported range",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooHighAndEnvVersionTooLow(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-high-go-env-too-low",
|
|
"Go version in `go.mod` file above supported range and Go version in environment below supported range",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooHighAndEnvVersionBelowMax(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-high-go-env-below-max",
|
|
"Go version in `go.mod` file above supported range and Go version in environment is supported and below the maximum supported version",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooHighAndEnvVersionMax(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-high-go-env-max",
|
|
"Go version in `go.mod` file above supported range and Go version in environment is the maximum supported version",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooLowAndNoGoEnv(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-low-no-go-env",
|
|
"Go version in `go.mod` file below supported range and no Go version in environment",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooLowAndEnvVersionUnsupported(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-low-go-env-unsupported",
|
|
"Go version in `go.mod` file below supported range and Go version in environment unsupported",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionTooLowAndEnvVersionSupported(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-too-low-go-env-supported",
|
|
"Go version in `go.mod` file below supported range and Go version in environment supported",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionSupportedAndNoGoEnv(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-supported-no-go-env",
|
|
"Go version in `go.mod` file in supported range and no Go version in environment",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionSupportedAndGoEnvUnsupported(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-supported-go-env-unsupported",
|
|
"Go version in `go.mod` file in supported range and Go version in environment unsupported",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionSupportedHigherGoEnv(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-supported-higher-than-go-env",
|
|
"The Go version in `go.mod` file is supported and higher than the Go version in environment",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitGoModVersionSupportedLowerEqualGoEnv(msg string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/env-go-mod-version-supported-lower-than-or-equal-to-go-env",
|
|
"The Go version in `go.mod` file is supported and lower than or equal to the Go version in environment",
|
|
msg,
|
|
severityNote,
|
|
telemetryOnly,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitNewerSystemGoRequired(requiredVersion string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/newer-system-go-version-required",
|
|
"The Go version installed on the system is too old to support this project",
|
|
"At least Go version `"+requiredVersion+"` is required to build this project, but the version installed on the system is older. [Install a newer version](https://github.com/actions/setup-go#basic).",
|
|
severityError,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitExtractionFailedForProjects(path []string) {
|
|
emitDiagnostic(
|
|
"go/autobuilder/extraction-failed-for-project",
|
|
"Unable to extract some Go projects",
|
|
fmt.Sprintf(
|
|
"The following %d Go project%s could not be extracted successfully:\n\n`%s`\n",
|
|
len(path),
|
|
plural(len(path), "", "s"),
|
|
strings.Join(path, "`, `")),
|
|
severityWarning,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|
|
|
|
func EmitPrivateRegistryUsed(writer DiagnosticsWriter, configs []string) {
|
|
n := len(configs)
|
|
lines := make([]string, n)
|
|
|
|
for i := range configs {
|
|
lines[i] = fmt.Sprintf("* %s", configs[i])
|
|
}
|
|
|
|
emitDiagnosticTo(
|
|
writer,
|
|
"go/autobuilder/analysis-using-private-registries",
|
|
"Go extraction used private package registries",
|
|
fmt.Sprintf(
|
|
"Go was extracted using the following private package registr%s:\n\n%s\n",
|
|
plural(n, "y", "ies"),
|
|
strings.Join(lines, "\n")),
|
|
severityNote,
|
|
fullVisibility,
|
|
noLocation,
|
|
)
|
|
}
|