Skip to content

Commit 75c0e06

Browse files
committed
Add CI test that #[cfg] tags are from a defined set
Rust is fairly relaxed in checking the validity of arguments passed to #[cfg]. While it should probably be more strict when checking features, it cannot be strict when checking loose cfg tags, because those can be anything and are simply passed to rustc via unconstrained arguments. Thus, we do it for rustc manually, but scanning all our source and checking that all our cfg tags match a known cfg tag. Fixes #2184
1 parent ec8e0fe commit 75c0e06

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
@@ -44,6 +44,8 @@ jobs:
4444
run: |
4545
rustup target add thumbv7m-none-eabi
4646
sudo apt-get -y install gcc-arm-none-eabi
47+
- name: Check for unknown cfg tags
48+
run: ci/check-cfg-flags.py
4749
- name: shellcheck the CI script
4850
if: "matrix.platform == 'ubuntu-latest'"
4951
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)