/ test / lint / test_runner / src / lint_cpp.rs
lint_cpp.rs
  1  // Copyright (c) The Bitcoin Core developers
  2  // Distributed under the MIT software license, see the accompanying
  3  // file COPYING or https://opensource.org/license/mit/.
  4  
  5  use std::process::Command;
  6  
  7  use crate::util::{check_output, get_pathspecs_default_excludes, git, LintResult};
  8  
  9  pub fn lint_includes_build_config() -> LintResult {
 10      let config_path = "./cmake/bitcoin-build-config.h.in";
 11      let defines_regex = format!(
 12          r"^\s*(?!//).*({})",
 13          check_output(Command::new("grep").args(["define", "--", config_path]))
 14              .expect("grep failed")
 15              .lines()
 16              .map(|line| {
 17                  line.split_whitespace()
 18                      .nth(1)
 19                      .unwrap_or_else(|| panic!("Could not extract name in line: {line}"))
 20              })
 21              .collect::<Vec<_>>()
 22              .join("|")
 23      );
 24      let print_affected_files = |mode: bool| {
 25          // * mode==true: Print files which use the define, but lack the include
 26          // * mode==false: Print files which lack the define, but use the include
 27          let defines_files = check_output(
 28              git()
 29                  .args([
 30                      "grep",
 31                      "--perl-regexp",
 32                      if mode {
 33                          "--files-with-matches"
 34                      } else {
 35                          "--files-without-match"
 36                      },
 37                      &defines_regex,
 38                      "--",
 39                      "*.cpp",
 40                      "*.h",
 41                  ])
 42                  .args(get_pathspecs_default_excludes()),
 43          )
 44          .expect("grep failed");
 45          git()
 46              .args([
 47                  "grep",
 48                  if mode {
 49                      "--files-without-match"
 50                  } else {
 51                      "--files-with-matches"
 52                  },
 53                  if mode {
 54                      "^#include <bitcoin-build-config.h> // IWYU pragma: keep$"
 55                  } else {
 56                      "#include <bitcoin-build-config.h>" // Catch redundant includes with and without the IWYU pragma
 57                  },
 58                  "--",
 59              ])
 60              .args(defines_files.lines())
 61              .status()
 62              .expect("command error")
 63              .success()
 64      };
 65      let missing = print_affected_files(true);
 66      if missing {
 67          return Err(format!(
 68              r#"
 69  One or more files use a symbol declared in the bitcoin-build-config.h header. However, they are not
 70  including the header. This is problematic, because the header may or may not be indirectly
 71  included. If the indirect include were to be intentionally or accidentally removed, the build could
 72  still succeed, but silently be buggy. For example, a slower fallback algorithm could be picked,
 73  even though bitcoin-build-config.h indicates that a faster feature is available and should be used.
 74  
 75  If you are unsure which symbol is used, you can find it with this command:
 76  git grep --perl-regexp '{defines_regex}' -- file_name
 77  
 78  Make sure to include it with the IWYU pragma. Otherwise, IWYU may falsely instruct to remove the
 79  include again.
 80  
 81  #include <bitcoin-build-config.h> // IWYU pragma: keep
 82              "#
 83          )
 84          .trim()
 85          .to_string());
 86      }
 87      let redundant = print_affected_files(false);
 88      if redundant {
 89          return Err(r#"
 90  None of the files use a symbol declared in the bitcoin-build-config.h header. However, they are including
 91  the header. Consider removing the unused include.
 92              "#
 93          .to_string());
 94      }
 95      Ok(())
 96  }
 97  
 98  pub fn lint_std_filesystem() -> LintResult {
 99      let found = git()
100          .args([
101              "grep",
102              "--line-number",
103              "std::filesystem",
104              "--",
105              "./src/",
106              ":(exclude)src/ipc/libmultiprocess/",
107              ":(exclude)src/util/fs.h",
108              ":(exclude)src/bitcoin-chainstate.cpp",
109          ])
110          .status()
111          .expect("command error")
112          .success();
113      if found {
114          Err(r#"
115  Direct use of std::filesystem may be dangerous and buggy. Please include <util/fs.h> and use the
116  fs:: namespace, which has unsafe filesystem functions marked as deleted.
117              "#
118          .trim()
119          .to_string())
120      } else {
121          Ok(())
122      }
123  }
124  
125  pub fn lint_remove_all() -> LintResult {
126      let found = git()
127          .args([
128              "grep",
129              "--line-number",
130              "remove_all(.*)",
131              "--",
132              "./src/",
133              // These are likely not avoidable.
134              ":(exclude)src/test/kernel/test_kernel.cpp",
135              ":(exclude)src/test/util/setup_common.cpp",
136          ])
137          .status()
138          .expect("command error")
139          .success();
140      if found {
141          Err(r#"
142  Use of fs::remove_all or std::filesystem::remove_all is dangerous and should be avoided. If removal
143  is required, prefer fs::remove.
144              "#
145          .trim()
146          .to_string())
147      } else {
148          Ok(())
149      }
150  }
151  
152  pub fn lint_rpc_assert() -> LintResult {
153      let found = git()
154          .args([
155              "grep",
156              "--line-number",
157              "--extended-regexp",
158              r"\<(A|a)ss(ume|ert)\(",
159              "--",
160              "src/rpc/",
161              "src/wallet/rpc*",
162              ":(exclude)src/rpc/server.cpp",
163              // src/rpc/server.cpp is excluded from this check since it's mostly meta-code.
164          ])
165          .status()
166          .expect("command error")
167          .success();
168      if found {
169          Err(r#"
170  CHECK_NONFATAL(condition) or NONFATAL_UNREACHABLE should be used instead of assert for RPC code.
171  
172  Aborting the whole process is undesirable for RPC code. So nonfatal
173  checks should be used over assert. See: src/util/check.h
174              "#
175          .trim()
176          .to_string())
177      } else {
178          Ok(())
179      }
180  }
181  
182  pub fn lint_boost_assert() -> LintResult {
183      let found = git()
184          .args([
185              "grep",
186              "--line-number",
187              "--extended-regexp",
188              r"BOOST_ASSERT\(",
189              "--",
190              "*.cpp",
191              "*.h",
192          ])
193          .status()
194          .expect("command error")
195          .success();
196      if found {
197          Err(r#"
198  BOOST_ASSERT must be replaced with Assert, BOOST_REQUIRE, or BOOST_CHECK to avoid an unnecessary
199  include of the boost/assert.hpp dependency.
200              "#
201          .trim()
202          .to_string())
203      } else {
204          Ok(())
205      }
206  }