mirror of
https://github.com/JakeHillion/drgn.git
synced 2024-12-23 09:43:06 +00:00
24609a3a2e
This adds an --enable-compiler-warnings flag that: * Defines a canonical list of warnings that we enforce. For now, this is -Wall -Wformat-overflow=2 -Wformat-truncation=2, but we can add to it going forward. * Enables warnings by default. * Allows erroring on warnings. We recommend that developers use this and use it for the CI. Signed-off-by: Omar Sandoval <osandov@osandov.com>
263 lines
8.3 KiB
Python
Executable File
263 lines
8.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
import os.path
|
|
import re
|
|
import subprocess
|
|
import sys
|
|
import sysconfig
|
|
import tempfile
|
|
|
|
import yaml
|
|
|
|
BUILD_BASE = "build/compile_commands"
|
|
CDB = BUILD_BASE + "/compile_commands.json"
|
|
|
|
IWYU_REGEXES = [
|
|
("add", r"(.*) should add these lines:"),
|
|
("remove", r"(.*) should remove these lines:"),
|
|
("include_list", r"The full include-list for (.*):"),
|
|
("none", r"---"),
|
|
("none", r"\(.* has correct #includes/fwd-decls\)"),
|
|
]
|
|
|
|
|
|
# Python.h is the canonical header for the Python C API. The actual definitions
|
|
# come from internal header files, so we need an IWYU mapping file. Ideally we
|
|
# could do this with include mappings. Unfortunately, Python.h uses ""-style
|
|
# includes for those headers, one of which is "object.h". This conflicts with
|
|
# libdrgn's "object.h", and IWYU doesn't seem to have a way to distinguish
|
|
# between those in the mapping file. So, we generate symbol mappings with the
|
|
# find-all-symbols Clang tool.
|
|
def gen_python_mapping_file(mapping_path):
|
|
# These headers are guaranteed to be included by Python.h. See
|
|
# https://docs.python.org/3/c-api/intro.html#include-files.
|
|
IMPLIED_HEADERS = (
|
|
"<assert.h>",
|
|
"<errno.h>",
|
|
"<limits.h>",
|
|
"<stdio.h>",
|
|
"<stdlib.h>",
|
|
"<string.h>",
|
|
)
|
|
|
|
include = sysconfig.get_path("include")
|
|
platinclude = sysconfig.get_path("platinclude")
|
|
|
|
with open(
|
|
mapping_path + ".tmp", "w"
|
|
) as imp, tempfile.TemporaryDirectory() as tmpdir:
|
|
imp.write("[\n")
|
|
for header in IMPLIED_HEADERS:
|
|
imp.write(
|
|
f' {{"include": ["{header}", "public", "<Python.h>", "public"]}},\n'
|
|
)
|
|
|
|
build_dir = os.path.join(tmpdir, "build")
|
|
os.mkdir(build_dir)
|
|
source = os.path.join(build_dir, "python.c")
|
|
with open(source, "w") as f:
|
|
f.write("#include <Python.h>")
|
|
|
|
commands = [
|
|
{
|
|
"arguments": [
|
|
"clang",
|
|
"-I",
|
|
include,
|
|
"-I",
|
|
platinclude,
|
|
"-c",
|
|
"python.c",
|
|
],
|
|
"directory": build_dir,
|
|
"file": "python.c",
|
|
}
|
|
]
|
|
with open(os.path.join(build_dir, "compile_commands.json"), "w") as f:
|
|
json.dump(commands, f)
|
|
|
|
symbols_dir = os.path.join(tmpdir, "find_all_symbols")
|
|
os.mkdir(symbols_dir)
|
|
subprocess.check_call(
|
|
[
|
|
"find-all-symbols",
|
|
"-p=" + build_dir,
|
|
"--output-dir=" + symbols_dir,
|
|
source,
|
|
]
|
|
)
|
|
|
|
find_all_symbols_db = os.path.join(tmpdir, "find_all_symbols_db.yaml")
|
|
subprocess.check_call(
|
|
[
|
|
"find-all-symbols",
|
|
"-p=" + build_dir,
|
|
"--merge-dir=" + symbols_dir,
|
|
find_all_symbols_db,
|
|
]
|
|
)
|
|
|
|
with open(find_all_symbols_db, "r") as f:
|
|
for document in yaml.safe_load_all(f):
|
|
name = document["Name"]
|
|
path = document["FilePath"]
|
|
if path.startswith(include + "/"):
|
|
header = path[len(include) + 1 :]
|
|
elif path.startswith(platinclude + "/"):
|
|
header = path[len(platinclude) + 1 :]
|
|
else:
|
|
continue
|
|
if header == "pyconfig.h":
|
|
# Probably best not to use these.
|
|
continue
|
|
imp.write(
|
|
f' {{"symbol": ["{name}", "private", "<Python.h>", "public"]}}, # From {header}\n'
|
|
)
|
|
# "cpython/object.h" defines struct _typeobject { ... } PyTypeObject.
|
|
# For some reason, include-what-you-mean wants struct _typeobject, but
|
|
# find-all-symbols only reports PyTypeObject. Add it manually.
|
|
imp.write(
|
|
' {"symbol": ["_typeobject", "private", "<Python.h>", "public"]}, # From cpython/object.h\n'
|
|
)
|
|
|
|
imp.write("]\n")
|
|
|
|
os.rename(mapping_path + ".tmp", mapping_path)
|
|
|
|
|
|
def iwyu_associated_header(path):
|
|
with open(path, "r") as f:
|
|
match = re.search(
|
|
r'^\s*#\s*include\s+"([^"]+)"\s+//\s+IWYU\s+pragma:\s+associated',
|
|
f.read(),
|
|
re.M,
|
|
)
|
|
if match:
|
|
return os.path.join(os.path.dirname(path), match.group(1))
|
|
if path.endswith(".c"):
|
|
return path[:-2] + ".h"
|
|
return None
|
|
|
|
|
|
def ignore_line(path, state, line):
|
|
# include-what-you-use/include-what-you-use#969: iwyu recommends bogus
|
|
# forward declarations for the anonymous unions generated by
|
|
# BINARY_OP_SIGNED_2C.
|
|
if line.endswith("::;"):
|
|
return True
|
|
|
|
# include-what-you-use/include-what-you-use#971: drgn.h "exports" a forward
|
|
# declaration of several opaque types, but iwyu doesn't have such a notion.
|
|
if re.fullmatch(
|
|
r"struct drgn_(language|platform|program|register|stack_trace|symbol);", line
|
|
):
|
|
paths = [path]
|
|
associated_header = iwyu_associated_header(path)
|
|
if associated_header is not None:
|
|
paths.append(associated_header)
|
|
for path in paths:
|
|
with open(path, "r") as f:
|
|
if re.search(r'^#include "(drgn.h|drgnpy.h)"', f.read(), re.M):
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="run include-what-you-use on drgn")
|
|
parser.add_argument(
|
|
"source", nargs="*", help="run on given file instead of all source files"
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
if args.source:
|
|
sources = {os.path.realpath(source) for source in args.source}
|
|
|
|
os.makedirs(BUILD_BASE, exist_ok=True)
|
|
subprocess.check_call(
|
|
[
|
|
"bear",
|
|
"--output",
|
|
CDB,
|
|
"--append",
|
|
"--",
|
|
sys.executable,
|
|
"setup.py",
|
|
"build",
|
|
"-b",
|
|
BUILD_BASE,
|
|
"build_ext",
|
|
]
|
|
)
|
|
|
|
python_mapping_file = os.path.join(
|
|
BUILD_BASE,
|
|
f"python.{sysconfig.get_platform()}.{sysconfig.get_python_version()}.imp",
|
|
)
|
|
if not os.path.exists(python_mapping_file):
|
|
gen_python_mapping_file(python_mapping_file)
|
|
|
|
with open(CDB, "r") as f:
|
|
commands = json.load(f)
|
|
|
|
for command in commands:
|
|
if (
|
|
args.source
|
|
and os.path.realpath(os.path.join(command["directory"], command["file"]))
|
|
not in sources
|
|
):
|
|
continue
|
|
|
|
with subprocess.Popen(
|
|
["include-what-you-use"]
|
|
+ command["arguments"][1:]
|
|
+ [
|
|
"-Xiwyu",
|
|
"--mapping_file=" + os.path.abspath(python_mapping_file),
|
|
"-w", # We don't want warnings from Clang.
|
|
],
|
|
cwd=command["directory"],
|
|
universal_newlines=True,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
) as proc:
|
|
state = "none"
|
|
header = None
|
|
lines = []
|
|
for line in proc.stdout:
|
|
line = line.rstrip("\n")
|
|
match = None
|
|
for new_state, regex in IWYU_REGEXES:
|
|
match = re.fullmatch(regex, line)
|
|
if match:
|
|
break
|
|
if match:
|
|
state = new_state
|
|
if state != "none":
|
|
path = os.path.relpath(
|
|
os.path.join(command["directory"], match.group(1))
|
|
)
|
|
if state in ("add", "remove"):
|
|
header = f"{path} should {state} these lines:"
|
|
else:
|
|
header = None
|
|
lines.clear()
|
|
elif (
|
|
line
|
|
and state != "include_list"
|
|
and not ignore_line(path, state, line)
|
|
):
|
|
if header is not None:
|
|
print("\n" + header)
|
|
header = None
|
|
print(line)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|