summaryrefslogtreecommitdiffhomepage
path: root/tools/nogo/defs.bzl
diff options
context:
space:
mode:
Diffstat (limited to 'tools/nogo/defs.bzl')
-rw-r--r--tools/nogo/defs.bzl426
1 files changed, 0 insertions, 426 deletions
diff --git a/tools/nogo/defs.bzl b/tools/nogo/defs.bzl
deleted file mode 100644
index 161ea972e..000000000
--- a/tools/nogo/defs.bzl
+++ /dev/null
@@ -1,426 +0,0 @@
-"""Nogo rules."""
-
-load("//tools/bazeldefs:go.bzl", "go_context", "go_embed_libraries", "go_importpath", "go_rule")
-
-NogoConfigInfo = provider(
- "information about a nogo configuration",
- fields = {
- "srcs": "the collection of configuration files",
- },
-)
-
-def _nogo_config_impl(ctx):
- return [NogoConfigInfo(
- srcs = ctx.files.srcs,
- )]
-
-nogo_config = rule(
- implementation = _nogo_config_impl,
- attrs = {
- "srcs": attr.label_list(
- doc = "a list of yaml files (schema defined by tool/nogo/config.go).",
- allow_files = True,
- ),
- },
-)
-
-NogoTargetInfo = provider(
- "information about the Go target",
- fields = {
- "goarch": "the build architecture (GOARCH)",
- "goos": "the build OS target (GOOS)",
- },
-)
-
-def _nogo_target_impl(ctx):
- return [NogoTargetInfo(
- goarch = ctx.attr.goarch,
- goos = ctx.attr.goos,
- )]
-
-nogo_target = go_rule(
- rule,
- implementation = _nogo_target_impl,
- attrs = {
- "goarch": attr.string(
- doc = "the Go build architecture (propagated to other rules).",
- mandatory = True,
- ),
- "goos": attr.string(
- doc = "the Go OS target (propagated to other rules).",
- mandatory = True,
- ),
- },
-)
-
-def _nogo_objdump_tool_impl(ctx):
- # Construct the magic dump command.
- #
- # Note that in some cases, the input is being fed into the tool via stdin.
- # Unfortunately, the Go objdump tool expects to see a seekable file [1], so
- # we need the tool to handle this case by creating a temporary file.
- #
- # [1] https://github.com/golang/go/issues/41051
- nogo_target_info = ctx.attr._nogo_target[NogoTargetInfo]
- go_ctx = go_context(ctx, goos = nogo_target_info.goos, goarch = nogo_target_info.goarch)
- env_prefix = " ".join(["%s=%s" % (key, value) for (key, value) in go_ctx.env.items()])
- dumper = ctx.actions.declare_file(ctx.label.name)
- ctx.actions.write(dumper, "\n".join([
- "#!/bin/bash",
- "set -euo pipefail",
- "if [[ $# -eq 0 ]]; then",
- " T=$(mktemp -u -t libXXXXXX.a)",
- " cat /dev/stdin > ${T}",
- "else",
- " T=$1;",
- "fi",
- "%s %s tool objdump ${T}" % (
- env_prefix,
- go_ctx.go.path,
- ),
- "if [[ $# -eq 0 ]]; then",
- " rm -rf ${T}",
- "fi",
- "",
- ]), is_executable = True)
-
- # Include the full runfiles.
- return [DefaultInfo(
- runfiles = ctx.runfiles(files = go_ctx.runfiles.to_list()),
- executable = dumper,
- )]
-
-nogo_objdump_tool = go_rule(
- rule,
- implementation = _nogo_objdump_tool_impl,
- attrs = {
- "_nogo_target": attr.label(
- default = "//tools/nogo:target",
- cfg = "target",
- ),
- },
-)
-
-# NogoStdlibInfo is the set of standard library facts.
-NogoStdlibInfo = provider(
- "information for nogo analysis (standard library facts)",
- fields = {
- "facts": "serialized standard library facts",
- "raw_findings": "raw package findings (if relevant)",
- },
-)
-
-def _nogo_stdlib_impl(ctx):
- # Build the standard library facts.
- nogo_target_info = ctx.attr._nogo_target[NogoTargetInfo]
- go_ctx = go_context(ctx, goos = nogo_target_info.goos, goarch = nogo_target_info.goarch)
- facts = ctx.actions.declare_file(ctx.label.name + ".facts")
- raw_findings = ctx.actions.declare_file(ctx.label.name + ".raw_findings")
- config = struct(
- Srcs = [f.path for f in go_ctx.stdlib_srcs],
- GOOS = go_ctx.goos,
- GOARCH = go_ctx.goarch,
- Tags = go_ctx.tags,
- )
- config_file = ctx.actions.declare_file(ctx.label.name + ".cfg")
- ctx.actions.write(config_file, config.to_json())
- ctx.actions.run(
- inputs = [config_file] + go_ctx.stdlib_srcs,
- outputs = [facts, raw_findings],
- tools = depset(go_ctx.runfiles.to_list() + ctx.files._nogo_objdump_tool),
- executable = ctx.files._nogo_check[0],
- mnemonic = "NogoStandardLibraryAnalysis",
- progress_message = "Analyzing Go Standard Library",
- arguments = go_ctx.nogo_args + [
- "-objdump_tool=%s" % ctx.files._nogo_objdump_tool[0].path,
- "-stdlib=%s" % config_file.path,
- "-findings=%s" % raw_findings.path,
- "-facts=%s" % facts.path,
- ],
- )
-
- # Return the stdlib facts as output.
- return [NogoStdlibInfo(
- facts = facts,
- raw_findings = raw_findings,
- )]
-
-nogo_stdlib = go_rule(
- rule,
- implementation = _nogo_stdlib_impl,
- attrs = {
- "_nogo_check": attr.label(
- default = "//tools/nogo/check:check",
- cfg = "host",
- ),
- "_nogo_objdump_tool": attr.label(
- default = "//tools/nogo:objdump_tool",
- cfg = "host",
- ),
- "_nogo_target": attr.label(
- default = "//tools/nogo:target",
- cfg = "target",
- ),
- },
-)
-
-# NogoInfo is the serialized set of package facts for a nogo analysis.
-#
-# Each go_library rule will generate a corresponding nogo rule, which will run
-# with the source files as input. Note however, that the individual nogo rules
-# are simply stubs that enter into the shadow dependency tree (the "aspect").
-NogoInfo = provider(
- "information for nogo analysis",
- fields = {
- "facts": "serialized package facts",
- "raw_findings": "raw package findings (if relevant)",
- "escapes": "escape-only findings (if relevant)",
- "importpath": "package import path",
- "binaries": "package binary files",
- "srcs": "srcs (for go_test support)",
- "deps": "deps (for go_test support)",
- },
-)
-
-def _nogo_aspect_impl(target, ctx):
- # If this is a nogo rule itself (and not the shadow of a go_library or
- # go_binary rule created by such a rule), then we simply return nothing.
- # All work is done in the shadow properties for go rules. For a proto
- # library, we simply skip the analysis portion but still need to return a
- # valid NogoInfo to reference the generated binary.
- if ctx.rule.kind in ("go_library", "go_tool_library", "go_binary", "go_test"):
- srcs = ctx.rule.files.srcs
- deps = ctx.rule.attr.deps
- elif ctx.rule.kind in ("go_proto_library", "go_wrap_cc"):
- srcs = []
- deps = ctx.rule.attr.deps
- else:
- return [NogoInfo()]
-
- # If we're using the "library" attribute, then we need to aggregate the
- # original library sources and dependencies into this target to perform
- # proper type analysis.
- for embed in go_embed_libraries(ctx.rule):
- info = embed[NogoInfo]
- if hasattr(info, "srcs"):
- srcs = srcs + info.srcs
- if hasattr(info, "deps"):
- deps = deps + info.deps
-
- # Start with all target files and srcs as input.
- inputs = target.files.to_list() + srcs
-
- # Generate a shell script that dumps the binary. Annoyingly, this seems
- # necessary as the context in which a run_shell command runs does not seem
- # to cleanly allow us redirect stdout to the actual output file. Perhaps
- # I'm missing something here, but the intermediate script does work.
- binaries = target.files.to_list()
- objfiles = [f for f in binaries if f.path.endswith(".a")]
- if len(objfiles) > 0:
- # Prefer the .a files for go_library targets.
- target_objfile = objfiles[0]
- else:
- # Use the raw binary for go_binary and go_test targets.
- target_objfile = binaries[0]
- inputs.append(target_objfile)
-
- # Extract the importpath for this package.
- if ctx.rule.kind == "go_test":
- # If this is a test, then it will not be imported by anything else.
- # We can safely set the importapth to just "test". Note that this
- # is necessary if the library also imports the core library (in
- # addition to including the sources directly), which happens in
- # some complex cases (seccomp_victim).
- importpath = "test"
- else:
- importpath = go_importpath(target)
-
- # Collect all info from shadow dependencies.
- fact_map = dict()
- import_map = dict()
- all_raw_findings = []
- for dep in deps:
- # There will be no file attribute set for all transitive dependencies
- # that are not go_library or go_binary rules, such as a proto rules.
- # This is handled by the ctx.rule.kind check above.
- info = dep[NogoInfo]
- if not hasattr(info, "facts"):
- continue
-
- # Configure where to find the binary & fact files. Note that this will
- # use .x and .a regardless of whether this is a go_binary rule, since
- # these dependencies must be go_library rules.
- x_files = [f.path for f in info.binaries if f.path.endswith(".x")]
- if not len(x_files):
- x_files = [f.path for f in info.binaries if f.path.endswith(".a")]
- import_map[info.importpath] = x_files[0]
- fact_map[info.importpath] = info.facts.path
-
- # Collect all findings; duplicates are resolved at the end.
- all_raw_findings.extend(info.raw_findings)
-
- # Ensure the above are available as inputs.
- inputs.append(info.facts)
- inputs += info.binaries
-
- # Add the standard library facts.
- stdlib_info = ctx.attr._nogo_stdlib[NogoStdlibInfo]
- stdlib_facts = stdlib_info.facts
- inputs.append(stdlib_facts)
-
- # The nogo tool operates on a configuration serialized in JSON format.
- nogo_target_info = ctx.attr._nogo_target[NogoTargetInfo]
- go_ctx = go_context(ctx, goos = nogo_target_info.goos, goarch = nogo_target_info.goarch)
- facts = ctx.actions.declare_file(target.label.name + ".facts")
- raw_findings = ctx.actions.declare_file(target.label.name + ".raw_findings")
- escapes = ctx.actions.declare_file(target.label.name + ".escapes")
- config = struct(
- ImportPath = importpath,
- GoFiles = [src.path for src in srcs if src.path.endswith(".go")],
- NonGoFiles = [src.path for src in srcs if not src.path.endswith(".go")],
- GOOS = go_ctx.goos,
- GOARCH = go_ctx.goarch,
- Tags = go_ctx.tags,
- FactMap = fact_map,
- ImportMap = import_map,
- StdlibFacts = stdlib_facts.path,
- )
- config_file = ctx.actions.declare_file(target.label.name + ".cfg")
- ctx.actions.write(config_file, config.to_json())
- inputs.append(config_file)
- ctx.actions.run(
- inputs = inputs,
- outputs = [facts, raw_findings, escapes],
- tools = depset(go_ctx.runfiles.to_list() + ctx.files._nogo_objdump_tool),
- executable = ctx.files._nogo_check[0],
- mnemonic = "NogoAnalysis",
- progress_message = "Analyzing %s" % target.label,
- arguments = go_ctx.nogo_args + [
- "-binary=%s" % target_objfile.path,
- "-objdump_tool=%s" % ctx.files._nogo_objdump_tool[0].path,
- "-package=%s" % config_file.path,
- "-findings=%s" % raw_findings.path,
- "-facts=%s" % facts.path,
- "-escapes=%s" % escapes.path,
- ],
- )
-
- # Flatten all findings from all dependencies.
- #
- # This is done because all the filtering must be done at the
- # top-level nogo_test to dynamically apply a configuration.
- # This does not actually add any additional work here, but
- # will simply propagate the full list of files.
- all_raw_findings = [stdlib_info.raw_findings] + depset(all_raw_findings).to_list() + [raw_findings]
-
- # Return the package facts as output.
- return [NogoInfo(
- facts = facts,
- raw_findings = all_raw_findings,
- escapes = escapes,
- importpath = importpath,
- binaries = binaries,
- srcs = srcs,
- deps = deps,
- )]
-
-nogo_aspect = go_rule(
- aspect,
- implementation = _nogo_aspect_impl,
- attr_aspects = [
- "deps",
- "library",
- "embed",
- ],
- attrs = {
- "_nogo_check": attr.label(
- default = "//tools/nogo/check:check",
- cfg = "host",
- ),
- "_nogo_stdlib": attr.label(
- default = "//tools/nogo:stdlib",
- cfg = "host",
- ),
- "_nogo_objdump_tool": attr.label(
- default = "//tools/nogo:objdump_tool",
- cfg = "host",
- ),
- "_nogo_target": attr.label(
- default = "//tools/nogo:target",
- cfg = "target",
- ),
- },
-)
-
-def _nogo_test_impl(ctx):
- """Check nogo findings."""
-
- # Ensure there's a single dependency.
- if len(ctx.attr.deps) != 1:
- fail("nogo_test requires exactly one dep.")
- raw_findings = ctx.attr.deps[0][NogoInfo].raw_findings
- escapes = ctx.attr.deps[0][NogoInfo].escapes
-
- # Build a step that applies the configuration.
- config_srcs = ctx.attr.config[NogoConfigInfo].srcs
- findings = ctx.actions.declare_file(ctx.label.name + ".findings")
- ctx.actions.run(
- inputs = raw_findings + ctx.files.srcs + config_srcs,
- outputs = [findings],
- tools = depset(ctx.files._filter),
- executable = ctx.files._filter[0],
- mnemonic = "GoStaticAnalysis",
- progress_message = "Generating %s" % ctx.label,
- arguments = ["-input=%s" % f.path for f in raw_findings] +
- ["-config=%s" % f.path for f in config_srcs] +
- ["-output=%s" % findings.path],
- )
-
- # Build a runner that checks the filtered facts.
- #
- # Note that this calls the filter binary without any configuration, so all
- # findings will be included. But this is expected, since we've already
- # filtered out everything that should not be included.
- runner = ctx.actions.declare_file(ctx.label.name)
- runner_content = [
- "#!/bin/bash",
- "exec %s -input=%s" % (ctx.files._filter[0].short_path, findings.short_path),
- "",
- ]
- ctx.actions.write(runner, "\n".join(runner_content), is_executable = True)
-
- return [DefaultInfo(
- # The runner just executes the filter again, on the
- # newly generated filtered findings. We still need
- # the filter tool as part of our runfiles, however.
- runfiles = ctx.runfiles(files = ctx.files._filter + [findings]),
- executable = runner,
- ), OutputGroupInfo(
- # Propagate the filtered filters, for consumption by
- # build tooling. Note that the build tooling typically
- # pays attention to the mnemoic above, so this must be
- # what is expected by the tooling.
- nogo_findings = depset([findings]),
- # Expose all escape analysis findings (see above).
- nogo_escapes = depset([escapes]),
- )]
-
-nogo_test = rule(
- implementation = _nogo_test_impl,
- attrs = {
- "config": attr.label(
- mandatory = True,
- doc = "A rule of kind nogo_config.",
- ),
- "deps": attr.label_list(
- aspects = [nogo_aspect],
- doc = "Exactly one Go dependency to be analyzed.",
- ),
- "srcs": attr.label_list(
- allow_files = True,
- doc = "Relevant src files. This is ignored except to make the nogo_test directly affected by the files.",
- ),
- "_filter": attr.label(default = "//tools/nogo/filter:filter"),
- },
- test = True,
-)