Skip to content

Commit a1c9282

Browse files
authored
Merge pull request #2782 from TheBlueMatt/2023-12-check-cfg-tags
Add CI test that `#[cfg]` tags are from a defined set
2 parents e839d49 + 75c0e06 commit a1c9282

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

.github/workflows/build.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ jobs:
3232
run: |
3333
rustup target add thumbv7m-none-eabi
3434
sudo apt-get -y install gcc-arm-none-eabi
35+
- name: Check for unknown cfg tags
36+
run: ci/check-cfg-flags.py
3537
- name: shellcheck the CI script
3638
if: "matrix.platform == 'ubuntu-latest'"
3739
run: |

ci/check-cfg-flags.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
#!/usr/bin/env python3
2+
# Rust is fairly relaxed in checking the validity of arguments passed to #[cfg].
3+
# While it should probably be more strict when checking features, it cannot be
4+
# strict when checking loose cfg tags, because those can be anything and are
5+
# simply passed to rustc via unconstrained arguments.
6+
#
7+
# Thus, we do it for rustc manually, but scanning all our source and checking
8+
# that all our cfg tags match a known cfg tag.
9+
import sys, glob, re
10+
11+
def check_feature(feature):
12+
if feature == "std":
13+
pass
14+
elif feature == "no-std":
15+
pass
16+
elif feature == "hashbrown":
17+
pass
18+
elif feature == "backtrace":
19+
pass
20+
elif feature == "grind_signatures":
21+
pass
22+
elif feature == "unsafe_revoked_tx_signing":
23+
pass
24+
elif feature == "futures":
25+
pass
26+
elif feature == "tokio":
27+
pass
28+
elif feature == "rest-client":
29+
pass
30+
elif feature == "rpc-client":
31+
pass
32+
elif feature == "serde":
33+
pass
34+
elif feature == "esplora-blocking":
35+
pass
36+
elif feature == "esplora-async":
37+
pass
38+
elif feature == "async-interface":
39+
pass
40+
elif feature == "electrum":
41+
pass
42+
elif feature == "_test_utils":
43+
pass
44+
elif feature == "_test_vectors":
45+
pass
46+
elif feature == "afl":
47+
pass
48+
elif feature == "honggfuzz":
49+
pass
50+
elif feature == "libfuzzer_fuzz":
51+
pass
52+
elif feature == "stdin_fuzz":
53+
pass
54+
elif feature == "max_level_off":
55+
pass
56+
elif feature == "max_level_error":
57+
pass
58+
elif feature == "max_level_warn":
59+
pass
60+
elif feature == "max_level_info":
61+
pass
62+
elif feature == "max_level_debug":
63+
pass
64+
elif feature == "max_level_trace":
65+
pass
66+
else:
67+
print("Bad feature: " + feature)
68+
assert False
69+
70+
def check_target_os(os):
71+
if os == "windows":
72+
pass
73+
else:
74+
assert False
75+
76+
def check_cfg_tag(cfg):
77+
if cfg == "fuzzing":
78+
pass
79+
elif cfg == "test":
80+
pass
81+
elif cfg == "debug_assertions":
82+
pass
83+
elif cfg == "c_bindings":
84+
pass
85+
elif cfg == "ldk_bench":
86+
pass
87+
elif cfg == "taproot":
88+
pass
89+
elif cfg == "require_route_graph_test":
90+
pass
91+
else:
92+
print("Bad cfg tag: " + cfg)
93+
assert False
94+
95+
def check_cfg_args(cfg):
96+
if cfg.startswith("all(") or cfg.startswith("any(") or cfg.startswith("not("):
97+
brackets = 1
98+
pos = 4
99+
while pos < len(cfg):
100+
if cfg[pos] == "(":
101+
brackets += 1
102+
elif cfg[pos] == ")":
103+
brackets -= 1
104+
if brackets == 0:
105+
check_cfg_args(cfg[4:pos])
106+
if pos + 1 != len(cfg):
107+
assert cfg[pos + 1] == ","
108+
check_cfg_args(cfg[pos + 2:].strip())
109+
return
110+
pos += 1
111+
assert False
112+
assert(cfg.endswith(")"))
113+
check_cfg_args(cfg[4:len(cfg)-1])
114+
else:
115+
parts = [part.strip() for part in cfg.split(",", 1)]
116+
if len(parts) > 1:
117+
for part in parts:
118+
check_cfg_args(part)
119+
elif cfg.startswith("feature") or cfg.startswith("target_os") or cfg.startswith("target_pointer_width"):
120+
arg = cfg
121+
if cfg.startswith("feature"):
122+
arg = arg[7:].strip()
123+
elif cfg.startswith("target_os"):
124+
arg = arg[9:].strip()
125+
else:
126+
arg = arg[20:].strip()
127+
assert arg.startswith("=")
128+
arg = arg[1:].strip()
129+
assert arg.startswith("\"")
130+
assert arg.endswith("\"")
131+
arg = arg[1:len(arg)-1]
132+
assert not "\"" in arg
133+
if cfg.startswith("feature"):
134+
check_feature(arg)
135+
elif cfg.startswith("target_os"):
136+
check_target_os(arg)
137+
else:
138+
assert arg == "32" or arg == "64"
139+
else:
140+
check_cfg_tag(cfg.strip())
141+
142+
cfg_regex = re.compile("#\[cfg\((.*)\)\]")
143+
for path in glob.glob(sys.path[0] + "/../**/*.rs", recursive = True):
144+
with open(path, "r") as file:
145+
while True:
146+
line = file.readline()
147+
if not line:
148+
break
149+
if "#[cfg(" in line:
150+
if not line.strip().startswith("//"):
151+
cfg_part = cfg_regex.match(line.strip()).group(1)
152+
check_cfg_args(cfg_part)

0 commit comments

Comments
 (0)