diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 00ef76f78..91f61810e 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -44,6 +44,8 @@ jobs: run: | rustup target add thumbv7m-none-eabi sudo apt-get -y install gcc-arm-none-eabi + - name: Check for unknown cfg tags + run: ci/check-cfg-flags.py - name: shellcheck the CI script if: "matrix.platform == 'ubuntu-latest'" run: | diff --git a/ci/check-cfg-flags.py b/ci/check-cfg-flags.py new file mode 100755 index 000000000..85cbde853 --- /dev/null +++ b/ci/check-cfg-flags.py @@ -0,0 +1,152 @@ +#!/usr/bin/env python3 +# 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. +import sys, glob, re + +def check_feature(feature): + if feature == "std": + pass + elif feature == "no-std": + pass + elif feature == "hashbrown": + pass + elif feature == "backtrace": + pass + elif feature == "grind_signatures": + pass + elif feature == "unsafe_revoked_tx_signing": + pass + elif feature == "futures": + pass + elif feature == "tokio": + pass + elif feature == "rest-client": + pass + elif feature == "rpc-client": + pass + elif feature == "serde": + pass + elif feature == "esplora-blocking": + pass + elif feature == "esplora-async": + pass + elif feature == "async-interface": + pass + elif feature == "electrum": + pass + elif feature == "_test_utils": + pass + elif feature == "_test_vectors": + pass + elif feature == "afl": + pass + elif feature == "honggfuzz": + pass + elif feature == "libfuzzer_fuzz": + pass + elif feature == "stdin_fuzz": + pass + elif feature == "max_level_off": + pass + elif feature == "max_level_error": + pass + elif feature == "max_level_warn": + pass + elif feature == "max_level_info": + pass + elif feature == "max_level_debug": + pass + elif feature == "max_level_trace": + pass + else: + print("Bad feature: " + feature) + assert False + +def check_target_os(os): + if os == "windows": + pass + else: + assert False + +def check_cfg_tag(cfg): + if cfg == "fuzzing": + pass + elif cfg == "test": + pass + elif cfg == "debug_assertions": + pass + elif cfg == "c_bindings": + pass + elif cfg == "ldk_bench": + pass + elif cfg == "taproot": + pass + elif cfg == "require_route_graph_test": + pass + else: + print("Bad cfg tag: " + cfg) + assert False + +def check_cfg_args(cfg): + if cfg.startswith("all(") or cfg.startswith("any(") or cfg.startswith("not("): + brackets = 1 + pos = 4 + while pos < len(cfg): + if cfg[pos] == "(": + brackets += 1 + elif cfg[pos] == ")": + brackets -= 1 + if brackets == 0: + check_cfg_args(cfg[4:pos]) + if pos + 1 != len(cfg): + assert cfg[pos + 1] == "," + check_cfg_args(cfg[pos + 2:].strip()) + return + pos += 1 + assert False + assert(cfg.endswith(")")) + check_cfg_args(cfg[4:len(cfg)-1]) + else: + parts = [part.strip() for part in cfg.split(",", 1)] + if len(parts) > 1: + for part in parts: + check_cfg_args(part) + elif cfg.startswith("feature") or cfg.startswith("target_os") or cfg.startswith("target_pointer_width"): + arg = cfg + if cfg.startswith("feature"): + arg = arg[7:].strip() + elif cfg.startswith("target_os"): + arg = arg[9:].strip() + else: + arg = arg[20:].strip() + assert arg.startswith("=") + arg = arg[1:].strip() + assert arg.startswith("\"") + assert arg.endswith("\"") + arg = arg[1:len(arg)-1] + assert not "\"" in arg + if cfg.startswith("feature"): + check_feature(arg) + elif cfg.startswith("target_os"): + check_target_os(arg) + else: + assert arg == "32" or arg == "64" + else: + check_cfg_tag(cfg.strip()) + +cfg_regex = re.compile("#\[cfg\((.*)\)\]") +for path in glob.glob(sys.path[0] + "/../**/*.rs", recursive = True): + with open(path, "r") as file: + while True: + line = file.readline() + if not line: + break + if "#[cfg(" in line: + if not line.strip().startswith("//"): + cfg_part = cfg_regex.match(line.strip()).group(1) + check_cfg_args(cfg_part)