diff options
author | nechda <nechda@yandex-team.com> | 2023-11-24 15:21:45 +0300 |
---|---|---|
committer | nechda <nechda@yandex-team.com> | 2023-11-24 16:34:52 +0300 |
commit | e1cb6168fb4a6a04716240f41e65465ece3deec5 (patch) | |
tree | e654688b36c280ecfc2a32154763660e74005f20 | |
parent | 407cda542d4c3b5048e8a342d4bf4db7ed560ff7 (diff) | |
download | ydb-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.conf | 1 | ||||
-rw-r--r-- | build/scripts/clang_static_analyzer.py | 98 | ||||
-rw-r--r-- | build/ymake.core.conf | 21 |
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 |