aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornechda <nechda@yandex-team.com>2023-11-24 15:21:45 +0300
committernechda <nechda@yandex-team.com>2023-11-24 16:34:52 +0300
commite1cb6168fb4a6a04716240f41e65465ece3deec5 (patch)
treee654688b36c280ecfc2a32154763660e74005f20
parent407cda542d4c3b5048e8a342d4bf4db7ed560ff7 (diff)
downloadydb-e1cb6168fb4a6a04716240f41e65465ece3deec5.tar.gz
Support CSA
# О чем этот PR? Добавляем возможность автоматического запуска Clang Static Analyzer (CSA) при сборке бинарников # Уже же есть clang tidy зачем нам еще и clang static analyzer? Да, clang tidy включает в себя возможности static analyzer, но данный PR расширает возможности CSA, а конкретно: 1. Фильтрация анализируемых файлов -- CSA по дефолту такое делать не умеет, а стандартные чекеры находят всякий мусор в либах контриба 2. Возможность подгружать свои собственные плагины -- они же чекеры # А нам точно нужен CSA? Да, нужен, так как он находит провисшие ссылки https://a.yandex-team.ru/review/4679116/details -- проезды по памяти ловить ооочень сложно ## Чтобы следить за процессом В CI падает большое количество тестов, поэтому параллельно идет CI в котором изменений никаких нет, для отслеживания динамики падающих тестов https://a.yandex-team.ru/review/4868604/details
-rw-r--r--build/conf/compilers/gnu_compiler.conf1
-rw-r--r--build/scripts/clang_static_analyzer.py98
-rw-r--r--build/ymake.core.conf21
3 files changed, 120 insertions, 0 deletions
diff --git a/build/conf/compilers/gnu_compiler.conf b/build/conf/compilers/gnu_compiler.conf
index 5601722fe9..aedbfaeedc 100644
--- a/build/conf/compilers/gnu_compiler.conf
+++ b/build/conf/compilers/gnu_compiler.conf
@@ -184,6 +184,7 @@ when (($TIME_TRACE == "yes" || $COMPILER_TIME_TRACE == "yes") && $_HAS_TIME_TRAC
_C_CPP_KV_STYLE=${hide;kv:"p CC"} ${hide;kv:"pc green"}
_CPP_ARGS=\
+ $CLANG_STATIC_ANALYZER_OPTIONS && \
$CLANG_TIDY_ARGS \
$YNDEXER_ARGS \
$RETRY_ARGS \
diff --git a/build/scripts/clang_static_analyzer.py b/build/scripts/clang_static_analyzer.py
new file mode 100644
index 0000000000..fa14293e37
--- /dev/null
+++ b/build/scripts/clang_static_analyzer.py
@@ -0,0 +1,98 @@
+import subprocess
+import sys
+import os
+import re
+import argparse
+import yaml
+
+CLANG_SA_CONFIG='static_analyzer.yaml'
+
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--testing-src", required=True)
+ parser.add_argument("--clang-bin", required=True)
+ parser.add_argument("--source-root", required=True)
+ parser.add_argument("--config-file", required=True)
+ parser.add_argument("--plugins-begin", dest='plugins', action='append', nargs='+', required=True)
+ parser.add_argument("--plugins-end", action='store_true', required=True)
+ return parser.parse_known_args()
+
+def find_config(config_path):
+ # For unifying config files names
+ basename = os.path.basename(config_path)
+ if basename != CLANG_SA_CONFIG:
+ msg = "The config file should be called {}, but {} passed".format(CLANG_SA_CONFIG, basename)
+ raise ValueError(msg)
+ if not os.path.isfile(config_path):
+ raise ValueError("Cant find config file {}".format(config_path))
+ return config_path
+
+def parse_config(config_file):
+ conf = None
+ try:
+ with open(config_file, 'r') as afile:
+ conf = yaml.safe_load(afile)
+ except:
+ conf = None
+ return conf
+
+def should_analyze(filename, conf):
+ include_files = conf.get('include_files')
+ exclude_files = conf.get('exclude_files')
+
+ if not include_files:
+ return False
+
+ include = re.match(include_files, filename)
+ exclude = re.match(exclude_files, filename) if exclude_files else False
+
+ return include and not exclude
+
+def load_plugins(conf, plugins):
+ load_cmds = []
+ for plugin in filter(lambda path: os.path.isfile(path), plugins):
+ load_cmds.extend(["-Xclang", "-load", "-Xclang", plugin])
+ return load_cmds
+
+def main():
+ args, clang_cmd = parse_args()
+
+ # Try to find config file and parse them
+ config_file = find_config(args.config_file)
+ conf = parse_config(config_file)
+
+ # Ensure we have at least one check
+ if ('checks' not in conf) or (not conf['checks']):
+ raise ValueError("There are no checks in the config file")
+
+ # Ensure that file match regex
+ if not should_analyze(args.testing_src, conf):
+ return 0
+
+ # Prepare args
+ analyzer_opts = [
+ '-Wno-unused-command-line-argument',
+ '--analyze',
+ '--analyzer-outputtext',
+ '--analyzer-no-default-checks'
+ ]
+ analyzer_opts.extend(['-Xanalyzer', '-analyzer-werror'])
+ analyzer_opts.extend(['-Xanalyzer', '-analyzer-checker=' + ','.join(conf['checks'])])
+
+ # Load additional plugins
+ analyzer_opts.extend(load_plugins(conf, args.plugins[0]))
+
+ run_cmd = [args.clang_bin, args.testing_src] + clang_cmd + analyzer_opts
+ p = subprocess.run(run_cmd)
+
+ return p.returncode
+
+if __name__ == '__main__':
+ ret_code = 0
+ try:
+ ret_code = main()
+ except Exception as e:
+ print >> sys.stderr, "\n[Error]: " + str(e)
+ ret_code = 1
+ exit(ret_code)
+
diff --git a/build/ymake.core.conf b/build/ymake.core.conf
index d700f76b85..d14fb74fc1 100644
--- a/build/ymake.core.conf
+++ b/build/ymake.core.conf
@@ -103,6 +103,27 @@ when ($USE_PREBUILT_TOOLS == "yes") {
}
+### @usage: SELECT_CLANG_SA_CONFIG(static_analyzer.yaml)
+###
+### Select config file for clang static analyzer.
+### The file should be called static_analyzer.yaml.
+macro SELECT_CLANG_SA_CONFIG(config) {
+ SET(_CLANG_SA_CONFIG ${input:config})
+}
+
+# Helper macro for unwrapping sequence of files
+macro _CLANG_SA_UNWRAP_PLUGINS(Plugins{input}[]) {
+ .CMD=${input:Plugins}
+}
+
+CLANG_SA_PLUGINS=
+when ($CLANG_SA_ENABLE == "yes" && $_CLANG_SA_CONFIG) {
+ CLANG_STATIC_ANALYZER_OPTIONS=$YMAKE_PYTHON ${input:"build/scripts/clang_static_analyzer.py"} "--testing-src" ${input:SRC} "--clang-bin" $CXX_COMPILER "--source-root" $(SOURCE_ROOT) "--config-file" ${input:_CLANG_SA_CONFIG} "--plugins-begin" "dummy_param" $_CLANG_SA_UNWRAP_PLUGINS($CLANG_SA_PLUGINS) "--plugins-end" $C_FLAGS_PLATFORM $GCC_COMPILE_FLAGS $CXXFLAGS $SRCFLAGS
+}
+otherwise {
+ CLANG_STATIC_ANALYZER_OPTIONS=
+}
+
FAIL_MODULE_CMD=$YMAKE_PYTHON3 ${input:"build/scripts/fail_module_cmd.py"} $TARGET ${kv;hide:"p ER"} ${kv;hide:"pc red"}
DEFAULT_TIDY_CONFIG=build/config/tests/clang_tidy/config.yaml
PROJECT_TIDY_CONFIG=build/config/tests/clang_tidy/config.yaml