diff --git a/.github/workflows/install-deps.sh b/.github/workflows/install-deps.sh index 37bf443384..224035b25c 100755 --- a/.github/workflows/install-deps.sh +++ b/.github/workflows/install-deps.sh @@ -17,7 +17,16 @@ sudo apt-get install \ libssl-dev \ clang-14 \ clang-tidy-14 \ - cppcheck + cppcheck + +# Source: https://fbinfer.com/docs/getting-started +VERSION=1.1.0; \ +curl -sSL "https://github.com/facebook/infer/releases/download/v$VERSION/infer-linux64-v$VERSION.tar.xz" \ +| sudo tar -C /opt -xJ && \ +sudo ln -s "/opt/infer-linux64-v$VERSION/bin/infer" /usr/local/bin/infer + +ldd --version +infer help --version sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-14 9999 sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-14 9999 diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index fb6d6f7d0b..2d350690bf 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -74,11 +74,20 @@ jobs: if: ${{ matrix.os == 'ubuntu-20.04' }} run: sudo apt-get update && sudo apt-get install g++-13 clang clang-tidy cppcheck + # https://github.com/facebook/infer/blob/main/docker/1.1.0/Dockerfile + run: + INFER_VERSION=v1.2.0; \ + cd /opt && \ + curl -sL \ + https://github.com/facebook/infer/releases/download/${INFER_VERSION}/infer-linux-x86_64-${INFER_VERSION}.tar.xz | \ + tar xJ && \ + rm -f /infer && \ + ln -s ${PWD}/infer-linux-x86_64-$INFER_VERSION /infer - name: "Install run-time dependencies (OSX)" if: ${{ matrix.os == 'macos-10.15' }} run: - brew install llvm cppcheck g++-13 + brew install llvm cppcheck g++-13 infer - name: "Install run-time dependencies (Windows)" if: ${{ matrix.os == 'windows-2019' }} diff --git a/analyzer/codechecker_analyzer/analysis_manager.py b/analyzer/codechecker_analyzer/analysis_manager.py index 6b22ca4231..c087262fa2 100644 --- a/analyzer/codechecker_analyzer/analysis_manager.py +++ b/analyzer/codechecker_analyzer/analysis_manager.py @@ -216,7 +216,7 @@ def prepare_check(action, analyzer_config, output_dir, def handle_success( - rh, result_file, result_base, skip_handlers, + rh, result_file, result_base, filter_handlers, rs_handler: ReviewStatusHandler, capture_analysis_output, success_dir ): @@ -230,7 +230,7 @@ def handle_success( save_output(os.path.join(success_dir, result_base), rh.analyzer_stdout, rh.analyzer_stderr) - rh.postprocess_result(skip_handlers, rs_handler) + rh.postprocess_result(filter_handlers, rs_handler) # Generated reports will be handled separately at store. @@ -487,7 +487,8 @@ def check(check_data): skiplist handler is None if no skip file was configured. """ actions_map, action, analyzer_config, \ - output_dir, skip_handlers, rs_handler, quiet_output_on_stdout, \ + output_dir, skip_handlers, filter_handlers, \ + rs_handler, quiet_output_on_stdout, \ capture_analysis_output, generate_reproducer, analysis_timeout, \ ctu_reanalyze_on_failure, \ output_dirs, statistics_data = check_data @@ -605,7 +606,7 @@ def handle_analysis_result(success, zip_file=zip_file): if success: handle_success(rh, result_file, result_base, - skip_handlers, rs_handler, + filter_handlers, rs_handler, capture_analysis_output, success_dir) elif not generate_reproducer: handle_failure(source_analyzer, rh, @@ -719,7 +720,7 @@ def skip_cpp(compile_actions, skip_handlers): def start_workers(actions_map, actions, analyzer_config_map, - jobs, output_path, skip_handlers, + jobs, output_path, skip_handlers, filter_handlers, rs_handler: ReviewStatusHandler, metadata_tool, quiet_analyze, capture_analysis_output, generate_reproducer, timeout, ctu_reanalyze_on_failure, statistics_data, manager, @@ -785,6 +786,7 @@ def signal_handler(signum, _): analyzer_config_map.get(build_action.analyzer_type), output_path, skip_handlers, + filter_handlers, rs_handler, quiet_analyze, capture_analysis_output, diff --git a/analyzer/codechecker_analyzer/analyzer.py b/analyzer/codechecker_analyzer/analyzer.py index 9b83037315..b788461e9f 100644 --- a/analyzer/codechecker_analyzer/analyzer.py +++ b/analyzer/codechecker_analyzer/analyzer.py @@ -130,7 +130,8 @@ def __has_enabled_checker(ch: AnalyzerConfigHandler): for _, (state, _) in ch.checks().items()) -def perform_analysis(args, skip_handlers, rs_handler: ReviewStatusHandler, +def perform_analysis(args, skip_handlers, filter_handlers, + rs_handler: ReviewStatusHandler, actions, metadata_tool, compile_cmd_count): """ Perform static analysis via the given (or if not, all) analyzers, @@ -333,6 +334,7 @@ def perform_analysis(args, skip_handlers, rs_handler: ReviewStatusHandler, config_map, args.jobs, args.output_path, skip_handlers, + filter_handlers, rs_handler, metadata_tool, 'quiet' in args, diff --git a/analyzer/codechecker_analyzer/analyzers/analyzer_base.py b/analyzer/codechecker_analyzer/analyzers/analyzer_base.py index a510db4634..473b908338 100644 --- a/analyzer/codechecker_analyzer/analyzers/analyzer_base.py +++ b/analyzer/codechecker_analyzer/analyzers/analyzer_base.py @@ -102,7 +102,7 @@ def construct_result_handler(self, buildaction, report_output, """ raise NotImplementedError("Subclasses should implement this!") - def analyze(self, analyzer_cmd, res_handler, proc_callback=None): + def analyze(self, analyzer_cmd, res_handler, proc_callback=None, env=None): """ Run the analyzer. """ @@ -111,12 +111,17 @@ def analyze(self, analyzer_cmd, res_handler, proc_callback=None): LOG.debug_analyzer('\n%s', ' '.join([shlex.quote(x) for x in analyzer_cmd])) + if not env: + env = analyzer_context.get_context().get_env_for_bin( + analyzer_cmd[0]) + res_handler.analyzer_cmd = analyzer_cmd try: ret_code, stdout, stderr \ = SourceAnalyzer.run_proc(analyzer_cmd, res_handler.buildaction.directory, - proc_callback) + proc_callback, + env) res_handler.analyzer_returncode = ret_code res_handler.analyzer_stdout = stdout res_handler.analyzer_stderr = stderr @@ -140,7 +145,7 @@ def post_analyze(self, result_handler): """ @staticmethod - def run_proc(command, cwd=None, proc_callback=None): + def run_proc(command, cwd=None, proc_callback=None, env=None): """ Just run the given command and return the return code and the stdout and stderr outputs of the process. @@ -156,8 +161,6 @@ def signal_handler(signum, _): signal.signal(signal.SIGINT, signal_handler) - env = analyzer_context.get_context().get_env_for_bin(command[0]) - LOG.debug('\nexecuting:%s\n', command) LOG.debug('\nENV:\n') LOG.debug(env) diff --git a/analyzer/codechecker_analyzer/analyzers/analyzer_types.py b/analyzer/codechecker_analyzer/analyzers/analyzer_types.py index 130288c728..5ea6ee1404 100644 --- a/analyzer/codechecker_analyzer/analyzers/analyzer_types.py +++ b/analyzer/codechecker_analyzer/analyzers/analyzer_types.py @@ -25,13 +25,16 @@ from .clangsa.analyzer import ClangSA from .cppcheck.analyzer import Cppcheck from .gcc.analyzer import Gcc +from .infer.analyzer import Infer LOG = get_logger('analyzer') supported_analyzers = {ClangSA.ANALYZER_NAME: ClangSA, ClangTidy.ANALYZER_NAME: ClangTidy, Cppcheck.ANALYZER_NAME: Cppcheck, - Gcc.ANALYZER_NAME: Gcc} + Gcc.ANALYZER_NAME: Gcc, + Infer.ANALYZER_NAME: Infer + } def is_ctu_capable(): diff --git a/analyzer/codechecker_analyzer/analyzers/infer/LICENSE.txt b/analyzer/codechecker_analyzer/analyzers/infer/LICENSE.txt new file mode 100644 index 0000000000..264eac1052 --- /dev/null +++ b/analyzer/codechecker_analyzer/analyzers/infer/LICENSE.txt @@ -0,0 +1,24 @@ +MIT License + +Copyright (c) Facebook, Inc. and its affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + +The following license is valid for descriptions.json file located in the +current directory (codechecker/analyzer/codechecker_analyzer/analyzers/infer/) \ No newline at end of file diff --git a/analyzer/codechecker_analyzer/analyzers/infer/__init__.py b/analyzer/codechecker_analyzer/analyzers/infer/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/analyzer/codechecker_analyzer/analyzers/infer/analyzer.py b/analyzer/codechecker_analyzer/analyzers/infer/analyzer.py new file mode 100644 index 0000000000..2c5e3a54ff --- /dev/null +++ b/analyzer/codechecker_analyzer/analyzers/infer/analyzer.py @@ -0,0 +1,250 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Module for Facebook Infer analyzer related methods +""" +from collections import defaultdict +# TODO distutils will be removed in python3.12 +import shlex +import subprocess +import json +from pathlib import Path + +from codechecker_common.logger import get_logger + +from codechecker_analyzer import analyzer_context + +from .. import analyzer_base +from ..config_handler import CheckerState + +from .config_handler import InferConfigHandler +from .result_handler import InferResultHandler +from codechecker_analyzer.buildlog.log_parser import IGNORED_OPTIONS_GCC + +LOG = get_logger('analyzer.infer') + + +class Infer(analyzer_base.SourceAnalyzer): + """ + Constructs the Infer analyzer commands. + """ + + ANALYZER_NAME = 'infer' + + @classmethod + def analyzer_binary(cls): + return analyzer_context.get_context() \ + .analyzer_binaries[cls.ANALYZER_NAME] + + def add_checker_config(self, checker_cfg): + # TODO + pass + + def get_analyzer_mentioned_files(self, output): + """ + This is mostly used for CTU. + """ + return set() + + def construct_analyzer_cmd(self, result_handler): + """ + Construct analyzer command for Infer. + """ + # TODO: This is not a try-catch block, like the other analyzers. Why + # should it? Should the others be? When can list creating list to have + # unforeseen exceptions where a general catch is justified? + config = self.config_handler + + analyzer_cmd = [Infer.analyzer_binary(), 'run', '--keep-going', + '--project-root', '/'] + + for checker_name, value in config.checks().items(): + filtered_name = checker_name.replace("infer-", "") + filtered_name = filtered_name.replace("-", "_") + filtered_name = filtered_name.upper() + + if value[0] == CheckerState.DISABLED: + analyzer_cmd.extend(['--disable-issue-type', filtered_name]) + else: + analyzer_cmd.extend(['--enable-issue-type', filtered_name]) + + output_dir = Path(result_handler.workspace, "infer", + result_handler.buildaction_hash) + output_dir.mkdir(exist_ok=True, parents=True) + analyzer_cmd.extend(['-o', str(output_dir)]) + analyzer_cmd.append('--') + + cmd_filtered = [] + for cmd in shlex.split(self.buildaction.original_command): + if IGNORED_OPTIONS_GCC.match(cmd) and \ + self.buildaction.lang in ['c', 'c++']: + continue + cmd_filtered.append(cmd) + + if self.buildaction.lang == 'c++': + cmd_filtered.append('-stdlib=libc++') + + analyzer_cmd.extend(cmd_filtered) + LOG.debug_analyzer("Running analysis command " + f"'{shlex.join(analyzer_cmd)}'") + + return analyzer_cmd + + @classmethod + def get_analyzer_checkers(cls): + """ + Return the list of the supported checkers. + """ + command = [cls.analyzer_binary(), "help", "--list-issue-types"] + desc = json.load( + open(Path(__file__).parent / "descriptions.json", + "r", encoding="utf-8")) + checker_list = [] + try: + env = analyzer_context.get_context().get_env_for_bin( + cls.analyzer_binary()) + env.update(TZ='UTC') + output = subprocess.check_output(command, + stderr=subprocess.DEVNULL, + env=env) + for entry in output.decode().split('\n'): + data = entry.strip().split(":") + if len(data) < 7: + continue + + entry_id = data[0].lower() + if entry_id in desc: + description = desc[entry_id] + else: + checker = data[6] if len(data) == 7 else data[5] + description = f"used by '{checker}' checker" + + entry_id = entry_id.replace("_", "-") + checker_list.append((f"infer-{entry_id}", + description)) + return checker_list + except (subprocess.CalledProcessError) as e: + LOG.error(e.stderr) + except (OSError) as e: + LOG.error(e.errno) + return [] + + @classmethod + def get_analyzer_config(cls): + """ + Config options for infer. + """ + return [] + + @classmethod + def get_checker_config(cls): + """ + Config options for infer checkers. + """ + return [] + + def analyze(self, analyzer_cmd, res_handler, proc_callback=None, env=None): + + env = analyzer_context.get_context().get_env_for_bin( + analyzer_cmd[0]) + env.update(TZ='UTC') + + result_handler = super().analyze( + analyzer_cmd, res_handler, proc_callback, env) + + if result_handler.analyzer_returncode != 0: + LOG.error(result_handler.analyzer_stderr) + + return result_handler + + def post_analyze(self, result_handler: InferResultHandler): + """ + Post process the results after the analysis. + """ + + @classmethod + def resolve_missing_binary(cls, configured_binary, environ): + """ + In case of the configured binary for the analyzer is not found in the + PATH, this method is used to find a callable binary. + """ + + @classmethod + def get_binary_version(cls, details=False) -> str: + """ + Return the analyzer version. + """ + # No need to LOG here, we will emit a warning later anyway. + if not cls.analyzer_binary(): + return None + version = [cls.analyzer_binary(), '--version'] + environ = analyzer_context.get_context().get_env_for_bin( + cls.analyzer_binary()) + environ.update(TZ='UTC') + try: + output = subprocess.check_output(version, + env=environ, + encoding="utf-8", + errors="ignore") + output = output.split('\n', maxsplit=1)[0] + return output.strip().split(" ")[-1][1:] + except (subprocess.CalledProcessError, OSError) as oerr: + LOG.warning("Failed to get analyzer version: %s", + ' '.join(version)) + LOG.warning(oerr) + return None + + @classmethod + def is_binary_version_incompatible(cls): + """ + Check the version compatibility of the given analyzer binary. + """ + return None + + def construct_result_handler(self, buildaction, report_output, + skiplist_handler): + """ + See base class for docs. + """ + res_handler = InferResultHandler(buildaction, report_output, + self.config_handler.report_hash) + + res_handler.skiplist_handler = skiplist_handler + + return res_handler + + @classmethod + def construct_config_handler(cls, args): + handler = InferConfigHandler() + + analyzer_config = defaultdict(list) + + if 'analyzer_config' in args and \ + isinstance(args.analyzer_config, list): + for cfg in args.analyzer_config: + if cfg.analyzer == cls.ANALYZER_NAME: + analyzer_config[cfg.option].append(cfg.value) + + handler.analyzer_config = analyzer_config + + checkers = cls.get_analyzer_checkers() + + try: + cmdline_checkers = args.ordered_checkers + except AttributeError: + LOG.debug_analyzer('No checkers were defined in ' + 'the command line for %s', + cls.ANALYZER_NAME) + cmdline_checkers = [] + + handler.initialize_checkers( + checkers, + cmdline_checkers, + 'enable_all' in args and args.enable_all) + + return handler diff --git a/analyzer/codechecker_analyzer/analyzers/infer/config_handler.py b/analyzer/codechecker_analyzer/analyzers/infer/config_handler.py new file mode 100644 index 0000000000..757ed19dc4 --- /dev/null +++ b/analyzer/codechecker_analyzer/analyzers/infer/config_handler.py @@ -0,0 +1,17 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Config handler for Infer analyzer. +""" +from .. import config_handler + + +class InferConfigHandler(config_handler.AnalyzerConfigHandler): + """ + Configuration handler for Infer analyzer. + """ diff --git a/analyzer/codechecker_analyzer/analyzers/infer/descriptions.json b/analyzer/codechecker_analyzer/analyzers/infer/descriptions.json new file mode 100644 index 0000000000..0a9bafd750 --- /dev/null +++ b/analyzer/codechecker_analyzer/analyzers/infer/descriptions.json @@ -0,0 +1,200 @@ +{ + "arbitrary_code_execution_under_lock": "A call that may execute arbitrary code (such as registered, or chained, callbacks) is made while holding a lock. This code may deadlock whenever the callbacks obtain locks themselves, so it is an unsafe pattern.", + "bad_arg": "Bad arg in Erlang: Reports an error when the type of an argument is wrong or the argument is badly formed. Corresponds to the badarg error in the Erlang runtime.", + "bad_arg_latent": "A latent BAD_ARG. See the documentation on Pulse latent issues.", + "bad_generator": "Bad generator in Erlang: Reports an error when a wrong type is used in a generator. Corresponds to the bad_generator error in the Erlang runtime.", + "bad_generator_latent": "A latent BAD_GENERATOR. See the documentation on Pulse latent issues.", + "bad_key": "Bad key in Erlang: Reports an error when trying to access or update a non-existing key in a map. Corresponds to the {badkey,K} error in the Erlang runtime.", + "bad_key_latent": "A latent BAD_KEY. See the documentation on Pulse latent issues.", + "bad_map": "Bad map in Erlang: Reports an error when trying to access or update a key for a term that is not a map. Corresponds to the {badmap,...} error in the Erlang runtime.", + "bad_map_latent": "A latent BAD_MAP. See the documentation on Pulse latent issues.", + "bad_record": "Bad record in Erlang: Reports an error when trying to access or update a record with the wrong name. Corresponds to the {badrecord,Name} error in the Erlang runtime.", + "bad_record_latent": "A latent BAD_RECORD. See the documentation on Pulse latent issues.", + "bad_return": "Bad return in Erlang: The dynamic type of a returned value disagrees with the static type given in the spec.", + "bad_return_latent": "A latent BAD_RETURN. See the documentation on Pulse latent issues.", + "biabduction_memory_leak": "See MEMORY_LEAK_C.", + "biabduction_retain_cycle": "See RETAIN_CYCLE.", + "block_parameter_not_null_checked": "This error type is reported only in Objective-C/Objective-C++. It happens when a method has a block as a parameter, and the block is executed in the method's body without checking it for nil first. If a nil block is passed to the method, then this will cause a crash. For example:", + "buffer_overrun_l1": "This is reported when outside of buffer bound is accessed. It can corrupt memory and may introduce security issues in C/C++.", + "buffer_overrun_l2": "See BUFFER_OVERRUN_L1", + "buffer_overrun_l3": "See BUFFER_OVERRUN_L1", + "buffer_overrun_l4": "See BUFFER_OVERRUN_L1", + "buffer_overrun_l5": "See BUFFER_OVERRUN_L1", + "buffer_overrun_s2": "See BUFFER_OVERRUN_L1", + "buffer_overrun_u5": "See BUFFER_OVERRUN_L1", + "captured_strong_self": "This check is about when a strong pointer to self is captured in a block. This could lead to retain cycles or unexpected behavior since to avoid retain cycles one usually uses a local strong pointer or a captured weak pointer instead.", + "checkers_allocates_memory": "A method annotated with @NoAllocation transitively calls new.", + "checkers_annotation_reachability_error": "A method annotated with an annotation @A transitively calls a method annotated @B where the combination of annotations is forbidden (for example, @UiThread calling @WorkerThread).", + "checkers_calls_expensive_method": "A method annotated with @PerformanceCritical transitively calls a method annotated @Expensive.", + "checkers_expensive_overrides_unannotated": "A method annotated with @Expensive overrides an un-annotated method.", + "checkers_fragment_retains_view": "This error type is Android-specific. It fires when a Fragment type fails to nullify one or more of its declared View fields in onDestroyView. In performance-sensitive applications, a Fragment should initialize all View's in onCreateView and nullify them in onDestroyView. If a Fragment is placed on the back stack and fails to nullify a View in onDestroyView, it will retain a useless reference to that View that will not be cleaned up until the Fragment is resumed or destroyed.", + "checkers_printf_args": "This error is reported when the argument types to a printf method do not match the format string.", + "config_impact": "Infer reports this issue when an expensive function is called without a config check. The config is usually a boolean value that enables experimental new features and it is defined per application/codebase, e.g. gatekeepers. To determine whether a function is expensive or not, the checker relies on modeled functions that are assumed to be expensive, e.g. string operations, regular expression match, or DB accesses.", + "config_impact_strict": "This is similar to CONFIG_IMPACT issue but the analysis reports all ungated codes irrespective of whether they are expensive or not.", + "config_usage": "Infer reports this issue when a config value is used as branch condition in a function. The config is usually a boolean value that enables experimental new features and it is defined per application/codebase, e.g. gatekeepers.", + "constant_address_dereference": "This is reported when an address at an absolute location, e.g. 1234, is dereferenced. It is a more general version of the NULLPTR_DEREFERENCE error type that is reported when the address is a constant other than zero.", + "constant_address_dereference_latent": "A latent CONSTANT_ADDRESS_DEREFERENCE. See the documentation on Pulse latent issues.", + "create_intent_from_uri": "Create an intent/start a component using a (possibly user-controlled) URI. may or may not be an issue depending on where the URI comes from.", + "cross_site_scripting": "Untrusted data flows into HTML; XSS risk.", + "cxx_ref_captured_in_block": "This check flags when a C++ reference is captured in an escaping block. This means that the block will be leaving the current scope, i.e. it is not annotated with __attribute__((noescape)).", + "dangling_pointer_dereference": "DATALOG_FACT", + "datalog_fact": "Datalog fact used as input for a datalog solver.", + "data_flow_to_sink": "A flow of data was detected to a sink.", + "deadlock": "This error is currently reported in Java. A deadlock occurs when two distinct threads try to acquire two locks in reverse orders. The following code illustrates a textbook example. Of course, in real deadlocks, the lock acquisitions may be separated by deeply nested call chains.", + "dead_store": "This error is reported in C++. It fires when the value assigned to a variables is never used (e.g., int i = 1; i = 2; return i;).", + "divide_by_zero": "EMPTY_VECTOR_ACCESS", + "empty_vector_access": "This error type is reported only in C++, in versions >= C++11.", + "execution_time_complexity_increase": "Infer reports this issue when the execution time complexity of a program increases in degree: e.g. from constant to linear or from logarithmic to quadratic. This issue type is only reported in differential mode: i.e when we are comparing the cost analysis results of two runs of infer on a file. Check out examples in here.", + "execution_time_complexity_increase_ui_thread": "Infer reports this issue when the execution time complexity of the procedure increases in degree and the procedure runs on the UI (main) thread.", + "execution_time_unreachable_at_exit": "This issue type indicates that the program's execution doesn't reach the exit node (where our analysis computes the final cost of the procedure). Hence, we cannot compute a static bound for the procedure.", + "expensive_execution_time": "[EXPERIMENTAL] This warning indicates that the procedure has non-constant and non-top execution cost. By default, this issue type is disabled. To enable it, set enabled=true in costKind.ml.", + "expensive_loop_invariant_call": "We report this issue type when a function is loop-invariant and also expensive (i.e. at least has linear complexity as determined by the cost analysis).", + "exposed_insecure_intent_handling": "Undocumented.", + "guardedby_violation": "A field annotated with @GuardedBy is being accessed by a call-chain that starts at a non-private method without synchronization.", + "impure_function": "This issue type indicates impure functions. For instance, below functions would be marked as impure:", + "inefficient_keyset_iterator": "This issue is raised when", + "inferbo_alloc_is_big": "malloc is passed a large constant value (>=10^6). For example, int n = 1000000; malloc(n); generates INFERBO_ALLOC_IS_BIG on malloc(n).", + "inferbo_alloc_is_negative": "malloc is called with a negative size. For example, int n = 3 - 5; malloc(n); generates INFERBO_ALLOC_IS_NEGATIVE on malloc(n).", + "inferbo_alloc_is_zero": "malloc is called with a zero size. For example, int n = 3 - 3; malloc(n); generates INFERBO_ALLOC_IS_ZERO on malloc(n).", + "inferbo_alloc_may_be_big": "malloc may be called with a large value. For example, int n = b ? 3 : 1000000; malloc(n); generates INFERBO_ALLOC_MAY_BE_BIG on malloc(n).", + "inferbo_alloc_may_be_negative": "malloc may be called with a negative value. For example, int n = b ? 3 : -5; malloc(n); generates INFERBO_ALLOC_MAY_BE_NEGATIVE on malloc(n).", + "infinite_execution_time": "This warning indicates that Infer was not able to determine a static upper bound on the execution cost of the procedure. By default, this issue type is disabled.", + "example-1-t-due-to-expressivity": "For instance, Inferbo's interval analysis is limited to affine expressions. Hence, we can't statically estimate an upper bound on the below example and obtain T(unknown) cost:", + "example-2-t-due-to-unmodeled-calls": "Another common case where we get T cost is when Infer cannot statically determine the range of values for loop bounds. For instance,", + "example-3-t-due-to-calling-another-t-costed-function": "Since the analysis is inter-procedural, another example we can have T cost is if at least one of the callees has T cost.", + "insecure_intent_handling": "Undocumented.", + "integer_overflow_l1": "This is reported when integer overflow occurred by integer operations such as addition, subtraction, and multiplication. For example, int n = INT_MAX; int m = n + 3; generates a INTEGER_OVERFLOW_L1 on n + 3.", + "integer_overflow_l2": "See INTEGER_OVERFLOW_L1", + "integer_overflow_l5": "See INTEGER_OVERFLOW_L1", + "integer_overflow_u5": "See INTEGER_OVERFLOW_L1", + "interface_not_thread_safe": "This error indicates that you have invoked an interface method not annotated with @ThreadSafe from a thread-safe context (e.g., code that uses locks or is marked @ThreadSafe). The fix is to add the @ThreadSafe annotation to the interface or to the interface method. For background on why these annotations are needed, see the detailed explanation here.", + "invalid_sil": "The SIL instruction does not conform to the expected subset of instructions expected for the front-end of the language for the analyzed code.", + "invariant_call": "We report this issue type when a function call is loop-invariant and hoistable, i.e.", + "ipc_on_ui_thread": "A blocking Binder IPC call occurs on the UI thread.", + "javascript_injection": "Untrusted data flows into JavaScript.", + "lab_resource_leak": "Toy issue.", + "lockless_violation": "A method implements an interface signature annotated with @Lockless but which transitively acquires a lock.", + "lock_consistency_violation": "This is an error reported on C++ and Objective C classes whenever:", + "fixing-lock-consistency-violation-reports": "Avoid the offending access (most often the read). Of course, this may not be possible. Use synchronization to protect the read, by using the same lock protecting the corresponding write. Make the method doing the read access private. This should silence the warning, since Infer looks for a pair of non-private methods. Objective-C: Infer considers a method as private if it's not exported in the header-file interface.", + "logging_private_data": "Undocumented.", + "memory_leak_c": "Memory leak in C", + "memory-leak-in-c": "This error type is only reported in C and Objective-C code. In Java we do not report memory leaks because it is a garbage collected language.", + "memory-leak-in-objective-c": "Additionally, in Objective-C, Infer reports memory leaks that happen when objects from Core Foundation or Core Graphics don't get released.", + "memory_leak_cpp": "See MEMORY_LEAK_C", + "missing_required_prop": "This issues is reported when a required @Prop is missing.", + "examples": "Assume that the following Litho Component specification is defined as follows where prop1 is optional and prop2 is required.", + "mixed_self_weakself": "This check reports an issue when an Objective-C block captures both self and weakSelf, a weak pointer to self. Possibly the developer meant to capture only weakSelf to avoid a retain cycle, but made a typo and used self instead of strongSelf. In this case, this could cause a retain cycle.", + "modifies_immutable": "This issue type indicates modifications to fields marked as @Immutable. For instance, below function mutateArray would be marked as modifying immutable field testArray:", + "multiple_weakself": "This check reports when an Objective-C block uses weakSelf (a weak pointer to self) more than once. This could lead to unexpected behaviour. Even if weakSelf is not nil in the first use, it could be nil in the following uses since the object that weakSelf points to could be freed anytime.", + "mutual_recursion_cycle": "A recursive call or mutually recursive call has been detected. This does not mean that the program won't terminate, just that the code is recursive. You should double-check if the recursion is intended and if it can lead to non-termination or a stack overflow.", + "nil_block_call": "This check reports when one tries to call an Objective-C block that is nil. This causes a crash.", + "nil_block_call_latent": "A latent NIL_BLOCK_CALL. See the documentation on Pulse latent issues.", + "nil_insertion_into_collection": "This checks reports when nil is passed to collections in Objective-C such as arrays and dictionaries. This causes a crash.", + "arrays": "Adding objects to an array, inserting objects at a given index, or replacing objects at a given index, can all lead to a crash when the object is nil.", + "dictionaries": "Adding a nil value in a dictionary causes a crash. If the concept of nil is required, one can add [NSNull null] instead.", + "nil_insertion_into_collection_latent": "A latent NIL_INSERTION_INTO_COLLECTION. See the documentation on Pulse latent issues.", + "nil_messaging_to_non_pod": "In Objective-C, calling a method on nil (or in Objective-C terms, sending a message to nil) does not crash, it simply returns a falsy value (nil/0/false). However, sending a message that returns a non-POD C++ type (POD being \"Plain Old Data\", essentially anything that cannot be compiled as a C-style struct) to nil causes undefined behaviour.", + "nil_messaging_to_non_pod_latent": "A latent NIL_MESSAGING_TO_NON_POD. See the documentation on Pulse latent issues.", + "no_matching_branch_in_try": "No matching branch is found when evaluating the of section of a try expression. Corresponds to the {try_clause,V} error in the Erlang runtime.", + "no_matching_branch_in_try_latent": "A latent NO_MATCHING_BRANCH_IN_TRY. See the documentation on Pulse latent issues.", + "no_matching_case_clause": "No matching case clause in Erlang: Reports an error when none of the clauses of a case match the expression. Corresponds to the {case_clause,V} error in the Erlang runtime.", + "no_matching_case_clause_latent": "A latent NO_MATCHING_CASE_CLAUSE. See the documentation on Pulse latent issues.", + "no_matching_else_clause": "No matching else clause in Erlang: Reports an error when none of the clauses of an else match the short-circuit result from maybe body. Corresponds to the {else_clause,V} error in the Erlang runtime.", + "no_matching_else_clause_latent": "A latent NO_MATCHING_ELSE_CLAUSE. See the documentation on Pulse latent issues.", + "no_matching_function_clause": "No matching function clause in Erlang: Reports an error when none of the clauses of a function match the arguments of a call. Corresponds to the function_clause error in the Erlang runtime.", + "no_matching_function_clause_latent": "A latent NO_MATCHING_FUNCTION_CLAUSE. See the documentation on Pulse latent issues.", + "no_match_of_rhs": "No match of right hand side value in Erlang: Reports an error when the right hand side value of a match expression does not match the pattern on the left hand side. Corresponds to the {badmatch,V} error in the Erlang runtime.", + "no_match_of_rhs_latent": "A latent NO_MATCH_OF_RHS. See the documentation on Pulse latent issues.", + "no_true_branch_in_if": "No true branch when evaluating an if expression in Erlang: Reports an error when none of the branches of an if expression evaluate to true. Corresponds to the if_clause error in the Erlang runtime.", + "no_true_branch_in_if_latent": "A latent NO_TRUE_BRANCH_IN_IF. See the documentation on Pulse latent issues.", + "nullptr_dereference": "Infer reports null dereference bugs in Java, C, C++, and Objective-C when it is possible that the null pointer is dereferenced, leading to a crash.", + "null-dereference-in-java": "Many of Infer's reports of potential Null Pointer Exceptions (NPE) come from code of the form", + "null-dereference-in-c": "Here is an example of an inter-procedural null dereference bug in C:", + "null-dereference-in-objective-c": "In Objective-C, null dereferences are less common than in Java, but they still happen and their cause can be hidden. In general, passing a message to nil does not cause a crash and returns nil, but dereferencing a pointer directly does cause a crash.", + "nullptr_dereference_in_nullsafe_class": "Infer reports null dereference bugs in Java, C, C++, and Objective-C when it is possible that the null pointer is dereferenced, leading to a crash.", + "null-dereference-in-java-1": "Many of Infer's reports of potential Null Pointer Exceptions (NPE) come from code of the form", + "null-dereference-in-c-1": "Here is an example of an inter-procedural null dereference bug in C:", + "null-dereference-in-objective-c-1": "In Objective-C, null dereferences are less common than in Java, but they still happen and their cause can be hidden. In general, passing a message to nil does not cause a crash and returns nil, but dereferencing a pointer directly does cause a crash.", + "nullptr_dereference_in_nullsafe_class_latent": "A latent NULLPTR_DEREFERENCE_IN_NULLSAFE_CLASS. See the documentation on Pulse latent issues.", + "nullptr_dereference_latent": "A latent NULLPTR_DEREFERENCE. See the documentation on Pulse latent issues.", + "null_argument": "This issue type indicates `nil` being passed as argument where a non-nil value expected.", + "null_argument_latent": "A latent NULL_ARGUMENT. See the documentation on Pulse latent issues.", + "null_dereference": "See NULLPTR_DEREFERENCE.", + "optional_empty_access": "Optional Empty Access warnings are reported when we try to retrieve the value of a folly::Optional when it is empty (i.e. folly::none).", + "optional_empty_access_latent": "A latent OPTIONAL_EMPTY_ACCESS. See the documentation on Pulse latent issues.", + "premature_nil_termination_argument": "This error type is reported in C and Objective-C. In many variadic methods, nil is used to signify the end of the list of input objects. This is similar to nil-termination of C strings. If one of the arguments that is not the last argument to the method is nil as well, Infer reports an error because that may lead to unexpected behavior.", + "pulse_cannot_instantiate_abstract_class": "Instantiating an abstract class will lead to Cannot instantiate abstract class error.", + "pulse_const_refable": "This issue is reported when a function parameter is a) passed by value and b) is not modified inside the function. Instead, parameter can be passed by const reference, i.e. converted to a const& so that no unnecessary copy is created at the callsite of the function.", + "pulse_dict_missing_key": "This issue is similar to PULSE_UNINITIALIZED_VALUE, but it is to warn reading a missing key of dictionary in Hack.", + "pulse_dynamic_type_mismatch": "This error is reported in Hack. It fires when we detect an operation that is incompatible with the dynamic type of its arguments.", + "pulse_readonly_shared_ptr_param": "This issue is reported when a shared pointer parameter is a) passed by value and b) is used only for reading, rather than lifetime extension. At the callsite, this might cause a potentially expensive unnecessary copy of the shared pointer, especially when many number of threads are sharing it. To avoid this, consider 1) passing the raw pointer instead and 2) use std::shared_ptr::get at callsites.", + "pulse_reference_stability": "The family of maps folly::F14ValueMap, folly::F14VectorMap, and by extension folly::F14FastMap differs slightly from std::unordered_map as it does not provide reference stability. When the map resizes such as when reserve is called or new elements are added, all existing references become invalid and should not be used.", + "pulse_resource_leak": "See RESOURCE_LEAK", + "pulse_transitive_access": "This issue tracks spurious accesses that are reachable from specific entry functions.", + "pulse_unawaited_awaitable": "Awaitable values created by calls to asynchronous methods should eventually be awaited along all codepaths (even if their value is unused). Hence the following is not OK", + "pulse_uninitialized_const": "This issue is similar to PULSE_UNINITIALIZED_VALUE, but it is to detect the uninitialized abstract const value in Hack.", + "pulse_uninitialized_value": "The code uses a variable that has not been initialized, leading to unpredictable or unintended results.", + "pulse_unnecessary_copy": "This is reported when Infer detects an unnecessary copy of an object via copy constructor where neither the source nor the copied variable are modified before the variable goes out of scope. Rather than the copy, a reference to the source object could be used to save memory.", + "pulse_unnecessary_copy_assignment": "See PULSE_UNNECESSARY_COPY.", + "pulse_unnecessary_copy_assignment_const": "See PULSE_UNNECESSARY_COPY.", + "pulse_unnecessary_copy_assignment_movable": "See PULSE_UNNECESSARY_COPY_MOVABLE.", + "pulse_unnecessary_copy_intermediate": "This is reported when Infer detects an unnecessary temporary copy of an intermediate object where copy is created to be passed down to a function unnecessarily. Instead, the intermediate object should either be moved into the callee or the type of the callee's parameter should be made const &.", + "pulse_unnecessary_copy_intermediate_const": "See PULSE_UNNECESSARY_COPY.", + "pulse_unnecessary_copy_movable": "This is reported when Infer detects an unnecessary copy into a field where", + "pulse_unnecessary_copy_optional": "This is reported when Infer detects an unnecessary copy of an object via optional value construction where the source is not modified before it goes out of scope. To avoid the copy, we can move the source object or change the callee's type.", + "pulse_unnecessary_copy_optional_const": "See PULSE_UNNECESSARY_COPY_OPTIONAL.", + "pulse_unnecessary_copy_return": "This is similar to PULSE_UNNECESSARY_COPY, but reported when a callee returns a copied value and it is not modified in its caller. We may be able to return const-ref typed value or try std::move to avoid the copy.", + "pure_function": "This issue type indicates pure functions. For instance, below functions would be marked as pure:", + "quandary_taint_error": "Generic taint error when nothing else fits.", + "regex_op_on_ui_thread": "A potentially costly operation on a regular expression occurs on the UI thread.", + "resource_leak": "Infer reports resource leaks in C, Objective-C and Java. In general, resources are entities such as files, sockets, connections, etc, that need to be closed after being used.", + "resource-leak-in-c": "This is an example of a resource leak in C code:", + "resource-leak-in-java": "For the remaining of this section, we will consider examples of resource leaks in Java code.", + "basics-and-standard-idiom": "Some objects in Java, the resources, are supposed to be closed when you stop using them, and failure to close is a resource leak. Resources include input streams, output streams, readers, writers, sockets, http connections, cursors, and json parsers.", + "nested_allocations": "When a resource allocation is included as an argument to a constructor, if the constructor fails it can leave an unreachable resource that no one can close.", + "allocation-of-jsonparser-and-cursor-resources": "Some resources are created inside libraries instead of by \"new\".", + "escaping-resources-and-exceptions": "Sometimes you want to return a resource to the outside, in which case you should not close it, but you still need to be careful of exceptions in case control skips past the return leaving no one to close. Here is a simple example of a positive use of escaping resources.", + "java-7s-try-with-resources": "(For use with Java 7 only)", + "retain_cycle": "A retain cycle is a situation when object A retains object B, and object B retains object A at the same time. Here is an example:", + "retain_cycle_no_weak_info": "A retain cycle is a situation when object A retains object B, and object B retains object A at the same time. Here is an example:", + "scope_leakage": "This issue type indicates that a class with scope annotation A stores a field with whose (dynamic) type (or one of its super types) is annotated with scope B such that a scope nesting restriction is violated. By \"stores\", we mean either directly or transitively.", + "sensitive_data_flow": "A flow of sensitive data was detected from a source.", + "shell_injection": "Environment variable or file data flowing to shell.", + "shell_injection_risk": "Code injection if the caller of the endpoint doesn't sanitize on its end.", + "sql_injection": "Untrusted and unescaped data flows to SQL.", + "sql_injection_risk": "Untrusted and unescaped data flows to SQL.", + "stack_variable_address_escape": "Reported when an address pointing into the stack of the current function will escape to its calling context. Such addresses will become invalid by the time the function actually returns so are potentially dangerous.", + "starvation": "This error is reported in Java, and specifically on Android. These reports are triggered when a method that runs on the UI thread may block, thus potentially leading to an Application Not Responding error.", + "static_initialization_order_fiasco": "This error is reported in C++. It fires when the initialization of a static variable A, accesses a static variable B from another translation unit (usually another .cpp file). There are no guarantees whether B has been already initialized or not at that point.", + "strict_mode_violation": "Android has a feature called strict mode, which if enabled, will flag the occasions where the main thread makes a call that results in disk I/O, waiting on a network socket, etc. The analysis catching starvation errors and deadlocks (the --starvation analysis) has the ability to statically detect such violations.", + "strong_self_not_checked": "This checks reports a potential issue when a block captures weakSelf (a weak pointer to self), then one assigns this pointer to a local variable strongSelf inside the block and uses this variable without checking first whether it is nil. The problem here is that the weak pointer could be nil at the time when the block is executed. So, the correct usage is to first check whether strongSelf is a valid pointer, and then use it.", + "taint_error": "A taint flow was detected from a source to a sink", + "thread_safety_violation": "This warning indicates a potential data race in Java. The analyser is called RacerD and this section gives brief but a mostly complete description of its features. See the RacerD page for more in-depth information and examples.", + "thread-safety-what-is-a-data-race": "Here a data race is a pair of accesses to the same member field such that:", + "thread-safety-potential-fixes": "Synchronizing the accesses (using the synchronized keyword, thread-exclusion such as atomic objects, volatile etc). Making an offending method private -- this will exclude it from being checked at the top level, though it will be checked if called by a public method which may itself, e.g., hold a lock when calling it. Putting the two accesses on the same thread, e.g., by using @MainThread or @ThreadConfined.", + "thread-safety-conditions-checked-before-reporting": "The class and method are not marked @ThreadSafe(enableChecks = false), and,", + "thread-safety-thread-annotations-recognized-by-racerd": "These class and method annotations imply the method is on the main thread: @MainThread, @UiThread", + "thread-safety-other-annotations-and-what-they-do": "These annotations can be found at com.facebook.infer.annotation.*.", + "topl_error": "A violation of a Topl property (user-specified). There is an execution path in the code that drives a Topl property from a start state to an error state.", + "topl_error_latent": "A latent TOPL_ERROR. See the documentation on Pulse latent issues.", + "untrusted_buffer_access": "Untrusted data of any kind flowing to buffer.", + "untrusted_deserialization": "User-controlled deserialization.", + "untrusted_deserialization_risk": "User-controlled deserialization", + "untrusted_environment_change_risk": "User-controlled environment mutation.", + "untrusted_file": "User-controlled file creation; may be vulnerable to path traversal and more.", + "untrusted_file_risk": "User-controlled file creation; may be vulnerable to path traversal and more.", + "untrusted_heap_allocation": "Untrusted data of any kind flowing to heap allocation. this can cause crashes or DOS.", + "untrusted_intent_creation": "Creating an Intent from user-controlled data.", + "untrusted_url_risk": "Untrusted flag, environment variable, or file data flowing to URL.", + "untrusted_variable_length_array": "Untrusted data of any kind flowing to stack buffer allocation. Trying to allocate a stack buffer that's too large will cause a stack overflow.", + "user_controlled_sql_risk": "Untrusted data flows to SQL (no injection risk).", + "use_after_delete": "An address that was invalidated by a call to delete in C++ is dereferenced.", + "use_after_delete_latent": "A latent USE_AFTER_DELETE. See the documentation on Pulse latent issues.", + "use_after_free": "An address that was invalidated by a call to free in C is dereferenced.", + "use_after_free_latent": "A latent USE_AFTER_FREE. See the documentation on Pulse latent issues.", + "use_after_lifetime": "The lifetime of an object has ended but that object is being accessed. For example, the address of a variable holding a C++ object is accessed after the variable has gone out of scope:", + "use_after_lifetime_latent": "A latent USE_AFTER_LIFETIME. See the documentation on Pulse latent issues.", + "vector_invalidation": "An address pointing into a C++ std::vector might have become invalid. This can happen when an address is taken into a vector, then the vector is mutated in a way that might invalidate the address, for example by adding elements to the vector, which might trigger a re-allocation of the entire vector contents (thereby invalidating the pointers into the previous location of the contents).", + "vector_invalidation_latent": "A latent VECTOR_INVALIDATION. See the documentation on Pulse latent issues.", + "weak_self_in_no_escape_block": "This check reports when weakSelf (a weak pointer to self) is used in a block, and this block is passed to a \"no escaping\" method. This means that the block passed to that method won't be leaving the current scope, this is marked with the annotation NS_NOESCAPE." +} \ No newline at end of file diff --git a/analyzer/codechecker_analyzer/analyzers/infer/result_handler.py b/analyzer/codechecker_analyzer/analyzers/infer/result_handler.py new file mode 100644 index 0000000000..d0fd59877c --- /dev/null +++ b/analyzer/codechecker_analyzer/analyzers/infer/result_handler.py @@ -0,0 +1,77 @@ +# ------------------------------------------------------------------------- +# +# Part of the CodeChecker project, under the Apache License v2.0 with +# LLVM Exceptions. See LICENSE for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +# ------------------------------------------------------------------------- +""" +Result handler for Infer. +""" +from typing import Optional +from pathlib import Path +import shutil + +from codechecker_report_converter.report.parser.base import AnalyzerInfo +from codechecker_report_converter.analyzers.infer.analyzer_result import \ + AnalyzerResult +from codechecker_report_converter.report import report_file +from codechecker_report_converter.report.hash import get_report_hash, HashType + +from codechecker_common.logger import get_logger +from codechecker_common.skiplist_handler import SkipListHandlers +from codechecker_common.review_status_handler import ReviewStatusHandler + +from ..result_handler_base import ResultHandler + +LOG = get_logger('analyzer.infer') + + +class InferResultHandler(ResultHandler): + """ + Create analyzer result file for Infer output. + """ + + def __init__(self, *args, **kwargs): + self.analyzer_info = AnalyzerInfo(name=AnalyzerResult.TOOL_NAME) + self.infer_analyzer_result = AnalyzerResult() + + super().__init__(*args, **kwargs) + + def postprocess_result( + self, + skip_handlers: Optional[SkipListHandlers], + rs_handler: Optional[ReviewStatusHandler] + ): + """ + Generate analyzer result output file which can be parsed and stored + into the database. + """ + LOG.debug_analyzer(self.analyzer_stdout) + + infer_out_folder = Path(self.workspace, "infer") + infer_dest_file_name = Path(infer_out_folder, + self.buildaction_hash, "report.json") + + reports = self.infer_analyzer_result.get_reports(infer_dest_file_name) + + hash_type = HashType.PATH_SENSITIVE + if self.report_hash_type == 'context-free-v2': + hash_type = HashType.CONTEXT_FREE + elif self.report_hash_type == 'diagnostic-message': + hash_type = HashType.DIAGNOSTIC_MESSAGE + + for report in reports: + if not report.checker_name.startswith("infer-"): + nicer_name = report.checker_name.lower().replace("_", "-") + report.checker_name = "infer-" + nicer_name + report.report_hash = get_report_hash(report, hash_type) + + if rs_handler: + reports = [r for r in reports if not rs_handler.should_ignore(r)] + + report_file.create( + self.analyzer_result_file, reports, self.checker_labels, + self.analyzer_info) + + shutil.rmtree(Path(self.workspace, "infer", self.buildaction_hash)) diff --git a/analyzer/codechecker_analyzer/cmd/analyze.py b/analyzer/codechecker_analyzer/cmd/analyze.py index 4ebf0b9ee9..42e9336ad4 100644 --- a/analyzer/codechecker_analyzer/cmd/analyze.py +++ b/analyzer/codechecker_analyzer/cmd/analyze.py @@ -183,6 +183,14 @@ def add_arguments_to_parser(parser): "Please consult the User guide on how a " "Skipfile should be laid out.") + skip_mode.add_argument('--drop-reports-from-skipped-files', + dest="drop_skipped_reports", + required=False, + action='store_true', + default=False, + help="Filter our reports from files that were " + "skipped from the analysis.") + skip_mode.add_argument('--file', nargs='+', dest="files", @@ -1097,8 +1105,13 @@ def main(args): LOG.error(f"Found no compilation commands in '{args.input}'") sys.exit(1) - # Process the skip list if present. + # Process the skip list if present. This will filter out analysis actions. skip_handlers = __get_skip_handlers(args, compile_commands) + # Post processin filters + filter_handlers = None + if ('drop_skipped_reports' in args and args.drop_skipped_reports): + filter_handlers = skip_handlers + rs_handler = review_status_handler.ReviewStatusHandler(args.output_path) try: @@ -1244,8 +1257,9 @@ def main(args): LOG.debug_analyzer("Compile commands forwarded for analysis: %d", compile_cmd_count.analyze) - analyzer.perform_analysis(args, skip_handlers, rs_handler, actions, - metadata_tool, compile_cmd_count) + analyzer.perform_analysis(args, skip_handlers, filter_handlers, + rs_handler, actions, metadata_tool, + compile_cmd_count) __update_skip_file(args) __update_review_status_config(args) diff --git a/analyzer/codechecker_analyzer/cmd/analyzers.py b/analyzer/codechecker_analyzer/cmd/analyzers.py index e235a08234..468ce0943f 100644 --- a/analyzer/codechecker_analyzer/cmd/analyzers.py +++ b/analyzer/codechecker_analyzer/cmd/analyzers.py @@ -145,6 +145,8 @@ def main(args): LOG.warning("'--dump-config cppcheck' is not supported.") elif args.dump_config == 'gcc': raise NotImplementedError('--dump-config') + elif args.dump_config == 'infer': + raise NotImplementedError('--dump-config') return diff --git a/analyzer/codechecker_analyzer/cmd/check.py b/analyzer/codechecker_analyzer/cmd/check.py index e654755f8f..871d1c6e1d 100644 --- a/analyzer/codechecker_analyzer/cmd/check.py +++ b/analyzer/codechecker_analyzer/cmd/check.py @@ -275,7 +275,7 @@ def add_arguments_to_parser(parser): "more information.\n" "USE WISELY AND AT YOUR OWN RISK!") - skip_mode = analyzer_opts.add_mutually_exclusive_group() + skip_mode = parser.add_argument_group("file filter arguments") skip_mode.add_argument('-i', '--ignore', '--skip', dest="skipfile", required=False, @@ -285,6 +285,14 @@ def add_arguments_to_parser(parser): "Please consult the User guide on how a " "Skipfile should be laid out.") + skip_mode.add_argument('--drop-reports-from-skipped-files', + dest="drop_skipped_reports", + required=False, + action='store_true', + default=False, + help="Filter our reports from files that were " + "skipped from the analysis.") + skip_mode.add_argument('--file', nargs='+', dest="files", @@ -881,6 +889,7 @@ def __update_if_key_exists(source, target, key): # after the call. args_to_update = ['quiet', 'skipfile', + 'drop_skipped_reports', 'files', 'analyzers', 'add_compiler_defaults', diff --git a/analyzer/tests/functional/analyze/test_analyze.py b/analyzer/tests/functional/analyze/test_analyze.py index 88e46ad47c..ecabdaec4f 100644 --- a/analyzer/tests/functional/analyze/test_analyze.py +++ b/analyzer/tests/functional/analyze/test_analyze.py @@ -1198,7 +1198,7 @@ def test_disable_all_checkers(self): out, _ = process.communicate() # Checkers of all 3 analyzers are disabled. - self.assertEqual(out.count("No checkers enabled for"), 4) + self.assertEqual(out.count("No checkers enabled for"), 5) def test_analyzer_and_checker_config(self): """Test analyzer configuration through command line flags.""" diff --git a/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py b/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py index b50a6b2b81..97a202eef7 100644 --- a/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py +++ b/analyzer/tests/functional/analyze_and_parse/test_analyze_and_parse.py @@ -224,7 +224,9 @@ def check_one_file(self, path, mode): "clang-tidy:", "clangsa:", "cppcheck:", - "gcc:"] + "gcc:", + "infer:", + "Found 1 source file to analyze in"] for line in output: # replace timestamps line = re.sub(r'\[\w+ \d{4}-\d{2}-\d{2} \d{2}:\d{2}\]', diff --git a/analyzer/tests/functional/analyze_and_parse/test_files/Makefile b/analyzer/tests/functional/analyze_and_parse/test_files/Makefile index 5a6d184ba1..3588216c4d 100644 --- a/analyzer/tests/functional/analyze_and_parse/test_files/Makefile +++ b/analyzer/tests/functional/analyze_and_parse/test_files/Makefile @@ -53,3 +53,5 @@ cppcheck_undef_include: $(CXX) -w cppcheck_include.cpp -I./includes/ -U HAVE_NULL_DEREFERENCE -o /dev/null gcc_simple: $(CXX) -w gcc_simple.cpp -o /dev/null -c +infer_simple: + $(CXX) -w infer_simple.cpp -o /dev/null -c \ No newline at end of file diff --git a/analyzer/tests/functional/analyze_and_parse/test_files/infer_simple.cpp b/analyzer/tests/functional/analyze_and_parse/test_files/infer_simple.cpp new file mode 100644 index 0000000000..4286748488 --- /dev/null +++ b/analyzer/tests/functional/analyze_and_parse/test_files/infer_simple.cpp @@ -0,0 +1,6 @@ +int main() { + int a = 123; + int b = a + 456; + + return 0; +} \ No newline at end of file diff --git a/analyzer/tests/functional/analyze_and_parse/test_files/infer_simple.output b/analyzer/tests/functional/analyze_and_parse/test_files/infer_simple.output new file mode 100644 index 0000000000..8475611d25 --- /dev/null +++ b/analyzer/tests/functional/analyze_and_parse/test_files/infer_simple.output @@ -0,0 +1,57 @@ +NORMAL#CodeChecker log --output $LOGFILE$ --build "make infer_simple" --quiet +NORMAL#CodeChecker analyze $LOGFILE$ --output $OUTPUT$ --analyzers infer --enable=infer-dead-store +NORMAL#CodeChecker parse $OUTPUT$ +CHECK#CodeChecker check --build "make infer_simple" --output $OUTPUT$ --quiet --analyzers infer --enable=infer-dead-store +----------------------------------------------- +[] - Starting build... +[] - Using CodeChecker ld-logger. +[] - Build finished successfully. +[] - Starting static analysis ... +[] - [1/1] infer analyzed infer_simple.cpp successfully. +[] - ----==== Summary ====---- +[] - Successfully analyzed +[] - infer: 1 +[] - Total analyzed compilation commands: 1 +[] - ----=================---- +[] - Analysis finished. +[] - To view results in the terminal use the "CodeChecker parse" command. +[] - To store results use the "CodeChecker store" command. +[] - See --help and the user guide for further options about parsing and storing the reports. +[] - ----=================---- +[HIGH] infer_simple.cpp:3:5: The value written to &b (type int) is never used. [infer-dead-store] + int b = a + 456; + ^ + +Found 1 defect(s) in infer_simple.cpp + + +----==== Severity Statistics ====---- +---------------------------- +Severity | Number of reports +---------------------------- +HIGH | 1 +---------------------------- +----=================---- + +----==== Checker Statistics ====---- +----------------------------------------------- +Checker name | Severity | Number of reports +----------------------------------------------- +infer-dead-store | HIGH | 1 +----------------------------------------------- +----=================---- + +----==== File Statistics ====---- +------------------------------------ +File name | Number of reports +------------------------------------ +infer_simple.cpp | 1 +------------------------------------ +----=================---- + +----======== Summary ========---- +--------------------------------------------- +Number of processed analyzer result files | 1 +Number of analyzer reports | 1 +--------------------------------------------- +----=================---- diff --git a/analyzer/tests/functional/skip/test_files/simple/skipfile_drop b/analyzer/tests/functional/skip/test_files/simple/skipfile_drop new file mode 100644 index 0000000000..6695fd8b22 --- /dev/null +++ b/analyzer/tests/functional/skip/test_files/simple/skipfile_drop @@ -0,0 +1 @@ +-*file_to_be_skipped.cpp diff --git a/analyzer/tests/functional/skip/test_skip.py b/analyzer/tests/functional/skip/test_skip.py index 79950be4ad..ba2d4c1c2f 100644 --- a/analyzer/tests/functional/skip/test_skip.py +++ b/analyzer/tests/functional/skip/test_skip.py @@ -136,7 +136,47 @@ def __run_parse(self, extra_options=None): def test_skip(self): """Analyze a project with a skip file.""" - self.__log_and_analyze_simple(["--ignore", "skipfile"]) + # we should see a report from skip.h + # we should not see a report from file_to_be_skipped.cpp + self.__log_and_analyze_simple(["--ignore", "skipfile_drop"]) + + # Check if file is skipped. + report_dir_files = os.listdir(self.report_dir) + for f in report_dir_files: + self.assertFalse("file_to_be_skipped.cpp" in f) + + # Check if report from the report file is removed. + report_dir_files = os.listdir(self.report_dir) + report_file_to_check = None + for f in report_dir_files: + if "skip_header.cpp" in f: + report_file_to_check = os.path.join(self.report_dir, f) + break + + self.assertIsNotNone(report_file_to_check, + "Report file should be generated.") + report_data = {} + with open(report_file_to_check, 'rb') as plist_file: + report_data = plistlib.load(plist_file) + files = report_data['files'] + + skipped_file_index = None + for i, f in enumerate(files): + if "skip.h" in f: + skipped_file_index = i + break + + self.assertNotEqual(skipped_file_index, None, + "Reports from headers should be kept" + " if the header is not on the skiplist") + + def test_drop_reports(self): + """Analyze a project with a skip file.""" + # we should not see a report from skip.h + # we should not see a report from file_to_be_skipped.cpp + + self.__log_and_analyze_simple(["--ignore", "skipfile", + "--drop-reports-from-skipped-files"]) # Check if file is skipped. report_dir_files = os.listdir(self.report_dir) diff --git a/config/labels/analyzers/infer.json b/config/labels/analyzers/infer.json new file mode 100644 index 0000000000..70db334d4e --- /dev/null +++ b/config/labels/analyzers/infer.json @@ -0,0 +1,1230 @@ +{ + "analyzer": "infer", + "labels": { + "infer-arbitrary-code-execution-under-lock": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#arbitrary_code_execution_under_lock", + "profile:unknown", + "severity:HIGH" + ], + "infer-array-out-of-bounds-l1": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-array-out-of-bounds-l2": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-array-out-of-bounds-l3": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-abduction-case-not-implemented": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-array-of-pointsto": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-assert-failure": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-arg": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_arg", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-arg-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_arg_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-generator": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_generator", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-generator-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_generator_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-key": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_key", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-key-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_key_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-map": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_map", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-map-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_map_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-record": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_record", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-record-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_record_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-return": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_return", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-return-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_return_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-biabduction-analysis-stops": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-biabduction-memory-leak": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#biabduction_memory_leak", + "profile:unknown", + "severity:HIGH" + ], + "infer-biabduction-retain-cycle": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#biabduction_retain_cycle", + "profile:unknown", + "severity:HIGH" + ], + "infer-block-parameter-not-null-checked": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#block_parameter_not_null_checked", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-buffer-overrun-l1": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#buffer_overrun_l1", + "profile:unknown", + "severity:HIGH" + ], + "infer-buffer-overrun-l2": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#buffer_overrun_l2", + "profile:unknown", + "severity:HIGH" + ], + "infer-buffer-overrun-l3": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#buffer_overrun_l3", + "profile:unknown", + "severity:HIGH" + ], + "infer-buffer-overrun-l4": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#buffer_overrun_l4", + "profile:unknown", + "severity:HIGH" + ], + "infer-buffer-overrun-l5": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#buffer_overrun_l5", + "profile:unknown", + "severity:HIGH" + ], + "infer-buffer-overrun-s2": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#buffer_overrun_s2", + "profile:unknown", + "severity:HIGH" + ], + "infer-buffer-overrun-u5": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#buffer_overrun_u5", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-footprint": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-captured-strong-self": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#captured_strong_self", + "profile:unknown", + "severity:HIGH" + ], + "infer-checkers-allocates-memory": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_allocates_memory", + "profile:unknown", + "severity:HIGH" + ], + "infer-checkers-annotation-reachability-error": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_annotation_reachability_error", + "profile:unknown", + "severity:HIGH" + ], + "infer-checkers-calls-expensive-method": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_calls_expensive_method", + "profile:unknown", + "severity:HIGH" + ], + "infer-checkers-expensive-overrides-unannotated": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_expensive_overrides_unannotated", + "profile:unknown", + "severity:HIGH" + ], + "infer-checkers-fragment-retains-view": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_fragment_retains_view", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-checkers-printf-args": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_printf_args", + "profile:unknown", + "severity:HIGH" + ], + "infer-class-cast-exception": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-condition-always-false": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-condition-always-true": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-config-impact": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#config_impact", + "profile:unknown", + "severity:STYLE" + ], + "infer-config-impact-strict": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#config_impact_strict", + "profile:unknown", + "severity:STYLE" + ], + "infer-config-usage": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#config_usage", + "profile:unknown", + "severity:LOW" + ], + "infer-constant-address-dereference": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#constant_address_dereference", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-constant-address-dereference-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#constant_address_dereference_latent", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-create-intent-from-uri": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#create_intent_from_uri", + "profile:unknown", + "severity:HIGH" + ], + "infer-cross-site-scripting": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#cross_site_scripting", + "profile:unknown", + "severity:HIGH" + ], + "infer-cxx-ref-captured-in-block": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#cxx_ref_captured_in_block", + "profile:unknown", + "severity:HIGH" + ], + "infer-cannot-star": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-dangling-pointer-dereference": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#dangling_pointer_dereference", + "profile:unknown", + "severity:HIGH" + ], + "infer-dangling-pointer-dereference-maybe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-datalog-fact": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#datalog_fact", + "profile:unknown", + "severity:LOW" + ], + "infer-data-flow-to-sink": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#data_flow_to_sink", + "profile:unknown", + "severity:STYLE" + ], + "infer-deadlock": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#deadlock", + "profile:unknown", + "severity:HIGH" + ], + "infer-dead-store": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#dead_store", + "profile:unknown", + "severity:HIGH" + ], + "infer-divide-by-zero": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#divide_by_zero", + "profile:unknown", + "severity:HIGH" + ], + "infer-do-not-report": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-empty-vector-access": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#empty_vector_access", + "profile:unknown", + "severity:HIGH" + ], + "infer-execution-time-complexity-increase": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#execution_time_complexity_increase", + "profile:unknown", + "severity:HIGH" + ], + "infer-execution-time-complexity-increase-ui-thread": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#execution_time_complexity_increase_ui_thread", + "profile:unknown", + "severity:HIGH" + ], + "infer-execution-time-unreachable-at-exit": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#execution_time_unreachable_at_exit", + "profile:unknown", + "severity:HIGH" + ], + "infer-expensive-execution-time": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#expensive_execution_time", + "profile:unknown", + "severity:HIGH" + ], + "infer-expensive-loop-invariant-call": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#expensive_loop_invariant_call", + "profile:unknown", + "severity:HIGH" + ], + "infer-exposed-insecure-intent-handling": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#exposed_insecure_intent_handling", + "profile:unknown", + "severity:HIGH" + ], + "infer-failure-exe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:LOW" + ], + "infer-guardedby-violation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#guardedby_violation", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-impure-function": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#impure_function", + "profile:unknown", + "severity:HIGH" + ], + "infer-inefficient-keyset-iterator": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#inefficient_keyset_iterator", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-inferbo-alloc-is-big": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#inferbo_alloc_is_big", + "profile:unknown", + "severity:HIGH" + ], + "infer-inferbo-alloc-is-negative": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#inferbo_alloc_is_negative", + "profile:unknown", + "severity:HIGH" + ], + "infer-inferbo-alloc-is-zero": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#inferbo_alloc_is_zero", + "profile:unknown", + "severity:HIGH" + ], + "infer-inferbo-alloc-may-be-big": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#inferbo_alloc_may_be_big", + "profile:unknown", + "severity:HIGH" + ], + "infer-inferbo-alloc-may-be-negative": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#inferbo_alloc_may_be_negative", + "profile:unknown", + "severity:HIGH" + ], + "infer-infinite-execution-time": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#infinite_execution_time", + "profile:unknown", + "severity:HIGH" + ], + "infer-inherently-dangerous-function": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-insecure-intent-handling": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#insecure_intent_handling", + "profile:unknown", + "severity:HIGH" + ], + "infer-integer-overflow-l1": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#integer_overflow_l1", + "profile:unknown", + "severity:HIGH" + ], + "infer-integer-overflow-l2": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#integer_overflow_l2", + "profile:unknown", + "severity:HIGH" + ], + "infer-integer-overflow-l5": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#integer_overflow_l5", + "profile:unknown", + "severity:HIGH" + ], + "infer-integer-overflow-u5": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#integer_overflow_u5", + "profile:unknown", + "severity:HIGH" + ], + "infer-interface-not-thread-safe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#interface_not_thread_safe", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-invalid-sil": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#invalid_sil", + "profile:unknown", + "severity:HIGH" + ], + "infer-invariant-call": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#invariant_call", + "profile:unknown", + "severity:HIGH" + ], + "infer-ipc-on-ui-thread": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#ipc_on_ui_thread", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-internal-error": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-javascript-injection": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#javascript_injection", + "profile:unknown", + "severity:HIGH" + ], + "infer-lab-resource-leak": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#lab_resource_leak", + "profile:unknown", + "severity:HIGH" + ], + "infer-lockless-violation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#lockless_violation", + "profile:unknown", + "severity:HIGH" + ], + "infer-lock-consistency-violation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#lock_consistency_violation", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-logging-private-data": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#logging_private_data", + "profile:unknown", + "severity:HIGH" + ], + "infer-leak-after-array-abstraction": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-leak-in-footprint": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-leak-unknown-origin": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-memory-leak-c": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#memory_leak_c", + "profile:unknown", + "severity:HIGH" + ], + "infer-memory-leak-cpp": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#memory_leak_cpp", + "profile:unknown", + "severity:HIGH" + ], + "infer-missing-required-prop": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#missing_required_prop", + "profile:unknown", + "severity:HIGH" + ], + "infer-mixed-self-weakself": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#mixed_self_weakself", + "profile:unknown", + "severity:HIGH" + ], + "infer-modifies-immutable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#modifies_immutable", + "profile:unknown", + "severity:HIGH" + ], + "infer-multiple-weakself": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#multiple_weakself", + "profile:unknown", + "severity:HIGH" + ], + "infer-mutual-recursion-cycle": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#mutual_recursion_cycle", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-missing-fld": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-nil-block-call": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nil_block_call", + "profile:unknown", + "severity:HIGH" + ], + "infer-nil-block-call-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nil_block_call_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-nil-insertion-into-collection": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nil_insertion_into_collection", + "profile:unknown", + "severity:HIGH" + ], + "infer-nil-insertion-into-collection-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nil_insertion_into_collection_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-nil-messaging-to-non-pod": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nil_messaging_to_non_pod", + "profile:unknown", + "severity:HIGH" + ], + "infer-nil-messaging-to-non-pod-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nil_messaging_to_non_pod_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-branch-in-try": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_branch_in_try", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-branch-in-try-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_branch_in_try_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-case-clause": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_case_clause", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-case-clause-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_case_clause_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-else-clause": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_else_clause", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-else-clause-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_else_clause_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-function-clause": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_function_clause", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-matching-function-clause-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_matching_function_clause_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-match-of-rhs": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_match_of_rhs", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-match-of-rhs-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_match_of_rhs_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-true-branch-in-if": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_true_branch_in_if", + "profile:unknown", + "severity:HIGH" + ], + "infer-no-true-branch-in-if-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#no_true_branch_in_if_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-nullptr-dereference": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nullptr_dereference", + "profile:unknown", + "severity:HIGH" + ], + "infer-nullptr-dereference-in-nullsafe-class": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nullptr_dereference_in_nullsafe_class", + "profile:unknown", + "severity:HIGH" + ], + "infer-nullptr-dereference-in-nullsafe-class-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nullptr_dereference_in_nullsafe_class_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-nullptr-dereference-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#nullptr_dereference_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-null-argument": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#null_argument", + "profile:unknown", + "severity:HIGH" + ], + "infer-null-argument-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#null_argument_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-null-dereference": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#null_dereference", + "profile:unknown", + "severity:HIGH" + ], + "infer-optional-empty-access": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#optional_empty_access", + "profile:unknown", + "severity:HIGH" + ], + "infer-optional-empty-access-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#optional_empty_access_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-precondition-not-found": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-precondition-not-met": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-premature-nil-termination-argument": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#premature_nil_termination_argument", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-pulse-cannot-instantiate-abstract-class": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_cannot_instantiate_abstract_class", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-const-refable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_const_refable", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-dict-missing-key": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_dict_missing_key", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-dynamic-type-mismatch": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_dynamic_type_mismatch", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-readonly-shared-ptr-param": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_readonly_shared_ptr_param", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-reference-stability": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_reference_stability", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-resource-leak": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_resource_leak", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-transitive-access": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_transitive_access", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unawaited-awaitable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unawaited_awaitable", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-uninitialized-const": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_uninitialized_const", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-uninitialized-value": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_uninitialized_value", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-assignment": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_assignment", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-assignment-const": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_assignment_const", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-assignment-movable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_assignment_movable", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-intermediate": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_intermediate", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-intermediate-const": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_intermediate_const", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-movable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_movable", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-optional": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_optional", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-optional-const": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_optional_const", + "profile:unknown", + "severity:HIGH" + ], + "infer-pulse-unnecessary-copy-return": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pulse_unnecessary_copy_return", + "profile:unknown", + "severity:HIGH" + ], + "infer-pure-function": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pure_function", + "profile:unknown", + "severity:HIGH" + ], + "infer-quandary-taint-error": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#quandary_taint_error", + "profile:unknown", + "severity:HIGH" + ], + "infer-regex-op-on-ui-thread": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#regex_op_on_ui_thread", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-resource-leak": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#resource_leak", + "profile:unknown", + "severity:HIGH" + ], + "infer-retain-cycle": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#retain_cycle", + "profile:unknown", + "severity:HIGH" + ], + "infer-retain-cycle-no-weak-info": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#retain_cycle_no_weak_info", + "profile:unknown", + "severity:HIGH" + ], + "infer-scope-leakage": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#scope_leakage", + "profile:unknown", + "severity:HIGH" + ], + "infer-sensitive-data-flow": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#sensitive_data_flow", + "profile:unknown", + "severity:STYLE" + ], + "infer-shell-injection": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#shell_injection", + "profile:unknown", + "severity:HIGH" + ], + "infer-shell-injection-risk": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#shell_injection_risk", + "profile:unknown", + "severity:HIGH" + ], + "infer-skip-function": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:LOW" + ], + "infer-sql-injection": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#sql_injection", + "profile:unknown", + "severity:HIGH" + ], + "infer-sql-injection-risk": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#sql_injection_risk", + "profile:unknown", + "severity:HIGH" + ], + "infer-stack-variable-address-escape": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#stack_variable_address_escape", + "profile:unknown", + "severity:HIGH" + ], + "infer-starvation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#starvation", + "profile:unknown", + "severity:HIGH" + ], + "infer-static-initialization-order-fiasco": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#static_initialization_order_fiasco", + "profile:unknown", + "severity:HIGH" + ], + "infer-strict-mode-violation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#strict_mode_violation", + "profile:unknown", + "severity:HIGH" + ], + "infer-strong-self-not-checked": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#strong_self_not_checked", + "profile:unknown", + "severity:HIGH" + ], + "infer-symexec-memory-error": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-taint-error": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#taint_error", + "profile:unknown", + "severity:HIGH" + ], + "infer-thread-safety-violation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#thread_safety_violation", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-topl-error": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#topl_error", + "profile:unknown", + "severity:HIGH" + ], + "infer-topl-error-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#topl_error_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-unreachable-code": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-buffer-access": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_buffer_access", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-deserialization": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_deserialization", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-deserialization-risk": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_deserialization_risk", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-environment-change-risk": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_environment_change_risk", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-file": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_file", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-file-risk": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_file_risk", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-heap-allocation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_heap_allocation", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-intent-creation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_intent_creation", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-url-risk": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_url_risk", + "profile:unknown", + "severity:HIGH" + ], + "infer-untrusted-variable-length-array": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#untrusted_variable_length_array", + "profile:unknown", + "severity:HIGH" + ], + "infer-user-controlled-sql-risk": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#user_controlled_sql_risk", + "profile:unknown", + "severity:HIGH" + ], + "infer-use-after-delete": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#use_after_delete", + "profile:unknown", + "severity:HIGH" + ], + "infer-use-after-delete-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#use_after_delete_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-use-after-free": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#use_after_free", + "profile:unknown", + "severity:HIGH" + ], + "infer-use-after-free-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#use_after_free_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-use-after-lifetime": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#use_after_lifetime", + "profile:unknown", + "severity:HIGH" + ], + "infer-use-after-lifetime-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#use_after_lifetime_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-vector-invalidation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#vector_invalidation", + "profile:unknown", + "severity:HIGH" + ], + "infer-vector-invalidation-latent": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#vector_invalidation_latent", + "profile:unknown", + "severity:HIGH" + ], + "infer-weak-self-in-no-escape-block": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#weak_self_in_no_escape_block", + "profile:unknown", + "severity:HIGH" + ], + "infer-wrong-argument-number": [ + "doc_url:https://fbinfer.com/docs/all-issue-types", + "profile:unknown", + "severity:HIGH" + ], + "infer-assign-pointer-MEDIUM": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#assign_pointer_MEDIUM", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-autoreleasepool-size-complexity-increase": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#autoreleasepool_size_complexity_increase", + "profile:unknown", + "severity:HIGH" + ], + "infer-autoreleasepool-size-complexity-increase-ui-thread": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#autoreleasepool_size_complexity_increase_ui_thread", + "profile:unknown", + "severity:HIGH" + ], + "infer-autoreleasepool-size-unreachable-at-exit": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#autoreleasepool_size_unreachable_at_exit", + "profile:unknown", + "severity:HIGH" + ], + "infer-bad-pointer-comparison": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#bad_pointer_comparison", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-checkers-annotation-reachability-HIGH": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_annotation_reachability_HIGH", + "profile:unknown", + "severity:HIGH" + ], + "infer-checkers-immutable-cast": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#checkers_immutable_cast", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-component-with-multiple-factory-methods": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#component_with_multiple_factory_methods", + "profile:unknown", + "severity:LOW" + ], + "infer-config-checks-between-markers": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#config_checks_between_markers", + "profile:unknown", + "severity:LOW" + ], + "infer-cxx-reference-captured-in-objc-block": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#cxx_reference_captured_in_objc_block", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-direct-atomic-property-access": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#direct_atomic_property_access", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-discouraged-weak-property-custom-setter": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#discouraged_weak_property_custom_setter", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-dotnet-resource-leak": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#dotnet_resource_leak", + "profile:unknown", + "severity:HIGH" + ], + "infer-eradicate-annotation-graph": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_annotation_graph", + "profile:unknown", + "severity:STYLE" + ], + "infer-eradicate-bad-nested-class-annotation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_bad_nested_class_annotation", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-condition-redundant": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_condition_redundant", + "profile:unknown", + "severity:LOW" + ], + "infer-eradicate-field-not-initialized": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_field_not_initialized", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-field-not-nullable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_field_not_nullable", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-field-over-annotated": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_field_over_annotated", + "profile:unknown", + "severity:LOW" + ], + "infer-eradicate-inconsistent-subclass-parameter-annotation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_inconsistent_subclass_parameter_annotation", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-inconsistent-subclass-return-annotation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_inconsistent_subclass_return_annotation", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-meta-class-can-be-nullsafe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_meta_class_can_be_nullsafe", + "profile:unknown", + "severity:LOW" + ], + "infer-eradicate-meta-class-is-nullsafe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_meta_class_is_nullsafe", + "profile:unknown", + "severity:STYLE" + ], + "infer-eradicate-meta-class-needs-improvement": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_meta_class_needs_improvement", + "profile:unknown", + "severity:STYLE" + ], + "infer-eradicate-nullable-dereference": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_nullable_dereference", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-parameter-not-nullable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_parameter_not_nullable", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-redundant-nested-class-annotation": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_redundant_nested_class_annotation", + "profile:unknown", + "severity:LOW" + ], + "infer-eradicate-return-not-nullable": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_return_not_nullable", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-eradicate-return-over-annotated": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_return_over_annotated", + "profile:unknown", + "severity:LOW" + ], + "infer-eradicate-unchecked-usage-in-nullsafe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_unchecked_usage_in_nullsafe", + "profile:unknown", + "severity:UNSPECIFIED" + ], + "infer-eradicate-unvetted-third-party-in-nullsafe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#eradicate_unvetted_third_party_in_nullsafe", + "profile:unknown", + "severity:UNSPECIFIED" + ], + "infer-expensive-autoreleasepool-size": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#expensive_autoreleasepool_size", + "profile:unknown", + "severity:HIGH" + ], + "infer-global-variable-initialized-with-function-or-method-call": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#global_variable_initialized_with_function_or_method_call", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-guardedby-violation-nullsafe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#guardedby_violation_nullsafe", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-infinite-autoreleasepool-size": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#infinite_autoreleasepool_size", + "profile:unknown", + "severity:HIGH" + ], + "infer-ivar-not-null-checked": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#ivar_not_null_checked", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-memory-leak": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#memory_leak", + "profile:unknown", + "severity:HIGH" + ], + "infer-mutable-local-variable-in-component-file": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#mutable_local_variable_in_component_file", + "profile:unknown", + "severity:LOW" + ], + "infer-parameter-not-null-checked": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#parameter_not_null_checked", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-pointer-to-const-objc-class": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#pointer_to_const_objc_class", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-quandary-taint-HIGH": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#quandary_taint_HIGH", + "profile:unknown", + "severity:HIGH" + ], + "infer-strong-delegate-MEDIUM": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#strong_delegate_MEDIUM", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-thread-safety-violation-nullsafe": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#thread_safety_violation_nullsafe", + "profile:unknown", + "severity:MEDIUM" + ], + "infer-topl-HIGH": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#topl_HIGH", + "profile:unknown", + "severity:HIGH" + ], + "infer-uninitialized-value": [ + "doc_url:https://fbinfer.com/docs/all-issue-types#uninitialized_value", + "profile:unknown", + "severity:HIGH" + ], + "infer-internal-HIGH": [ + "doc_url:https://fbinfer.com/docs/all-issue-types##", + "profile:unknown", + "severity:HIGH" + ], + "infer-symexec-memory-HIGH": [ + "doc_url:https://fbinfer.com/docs/all-issue-types##", + "profile:unknown", + "severity:HIGH" + ] + } +} \ No newline at end of file diff --git a/config/package_layout.json b/config/package_layout.json index 49579fe89b..d38cc59509 100644 --- a/config/package_layout.json +++ b/config/package_layout.json @@ -4,7 +4,8 @@ "clangsa": "clang", "clang-tidy": "clang-tidy", "cppcheck": "cppcheck", - "gcc": "g++" + "gcc": "g++", + "infer": "infer" }, "clang-apply-replacements": "clang-apply-replacements" }, diff --git a/docs/README.md b/docs/README.md index 69ebf2f77a..6b1d50ea81 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,7 +36,7 @@ macOS (OS X) development environment. # Main features ## Command line C/C++ Analysis - * Executes [_Clang-Tidy_](http://clang.llvm.org/extra/clang-tidy/), [_Clang Static Analyzer_](http://clang-analyzer.llvm.org/) with Cross-Translation Unit analysis, Statistical Analysis (when checkers are available), [_Cppcheck_](https://cppcheck.sourceforge.io/), and the [_GCC Static Analyzer_](https://gcc.gnu.org/wiki/StaticAnalyzer). + * Executes [_Clang-Tidy_](http://clang.llvm.org/extra/clang-tidy/), [_Clang Static Analyzer_](http://clang-analyzer.llvm.org/) with Cross-Translation Unit analysis, Statistical Analysis (when checkers are available), [_Cppcheck_](https://cppcheck.sourceforge.io/), [_GCC Static Analyzer_](https://gcc.gnu.org/wiki/StaticAnalyzer) and the [_Facebook Infer Analyzer_](https://fbinfer.com). * Creates the JSON compilation database by wiretapping any build process (e.g., `CodeChecker log -b "make"`). * Automatically analyzes GCC cross-compiled projects: detecting GCC or Clang compiler configuration and forming the corresponding clang analyzer invocations. * Incremental analysis: Only the changed files and its dependencies need to be reanalyzed. @@ -243,9 +243,13 @@ The following commands are used to bootstrap CodeChecker on Ubuntu 20.04 LTS: # come from package manager! # In case of Cppcheck, the minimal supported version is 1.80. # In case of gcc, the minimal supported version is 13.0.0. +# Infer: https://fbinfer.com/docs/getting-started sudo apt-get install clang clang-tidy cppcheck g++ build-essential curl gcc-multilib git python3-dev python3-venv python3-setuptools +# In case of venv_dev target and Ubuntu 23 Linux, install an additional library: +sudo apt-get install libpq-dev + # Install nodejs dependency for web. In case of Debian/Ubuntu you can use the # following commands. For more information see the official docs: # https://nodejs.org/en/download/package-manager/ diff --git a/docs/analyzer/checker_and_analyzer_configuration.md b/docs/analyzer/checker_and_analyzer_configuration.md index 637d513660..5359edb2f3 100644 --- a/docs/analyzer/checker_and_analyzer_configuration.md +++ b/docs/analyzer/checker_and_analyzer_configuration.md @@ -300,3 +300,29 @@ CodeChecker check -l ./compile_commands.json \ -d gcc-double-free \ # disable gcc-double-free -o ./reports ``` + +# Configuring the FB-Infer Analyzer + +As of CodeChecker 6.23, Codechecker can now execute the Facebook Infer Analyzer. +The minimum version of Infer we support is 1.1.0. + +## Analyzer Configuration + +Currently, we don't support configuring the Facebook Infer analyzer through +CodeChecker. The _overwhelming_ majority of these configurations are only +recommended for developers -- but we will keep an eye out if this ever changes. + +## Limitations + +Currently only static analysis can be executed. Meaning that it analyzes each +file separately and not the whole project as one. + +## Example invocation + +``` shell +CodeChecker check -l ./compile_commands.json \ + --analyzers infer \ # Run Infer analyzer only + -e infer \ # enable all checkers starting with "infer" + -d infer-expensive-loop-invariant-call \ # disable infer-expensive-loop-invariant-call + -o ./reports +``` \ No newline at end of file diff --git a/docs/analyzer/user_guide.md b/docs/analyzer/user_guide.md index b66a96e193..e8bba6ad95 100644 --- a/docs/analyzer/user_guide.md +++ b/docs/analyzer/user_guide.md @@ -1208,6 +1208,7 @@ the [_Clang Static Analyzer_](http://clang-analyzer.llvm.org), [_Clang-Tidy_](http://clang.llvm.org/extra/clang-tidy), [_Cppcheck_](http://cppcheck.sourceforge.net/) and [_GCC Static Analyzer_](https://gcc.gnu.org/wiki/StaticAnalyzer). +[_Facebook Infer Analyzer_](https://fbinfer.com/) `--analyzers` can be used to specify which analyzer tool should be used (by default, all supported are used). The tools are completely independent, so either can be omitted if not present as they are provided by different diff --git a/docs/supported_code_analyzers.md b/docs/supported_code_analyzers.md index 3ce580e5c9..b828b6122b 100644 --- a/docs/supported_code_analyzers.md +++ b/docs/supported_code_analyzers.md @@ -5,6 +5,7 @@ CodeChecker can execute the following static analyzer tools: - [Clang Static Analyzer](https://clang-analyzer.llvm.org/) - [Cppcheck](https://cppcheck.sourceforge.io/) - [GCC Static Analyzer](https://gcc.gnu.org/wiki/StaticAnalyzer) +- [Facebook Infer Analyzer](https://fbinfer.com/) We have created a separate [converter tool](/tools/report-converter) which can be used to convert the output of different source code analyzer tools to a diff --git a/docs/web/db_schema_guide.md b/docs/web/db_schema_guide.md index bb35946169..a5d341f319 100644 --- a/docs/web/db_schema_guide.md +++ b/docs/web/db_schema_guide.md @@ -39,7 +39,7 @@ according to your database configuration. ### **Step 3**: Use alembic to autogenerate migration scripts -`alembic --name config_db revision --autogenerate -m "Change description"` +`PYTHONPATH=/build/CodeChecker/lib/python3 alembic --name config_db revision --autogenerate -m "Change description"` ### **Step 4**: Check the generated scripts diff --git a/docs/web/permissions.md b/docs/web/permissions.md index 7152c301da..69a2683e1e 100644 --- a/docs/web/permissions.md +++ b/docs/web/permissions.md @@ -18,45 +18,40 @@ on the product level. Table of Contents ================= -* [The master superuser (root)](#the-master-superuser) -* [Managing permissions](#managing-permissions) -* [Permission concepts](#permission-concepts) - * [Default value](#default-value) - * [Permission inheritance](#permission-inheritance) - * [Permission manager](#permission-manager) -* [Available permissions](#available-permissions) - * [Server-wide (global) permissions](#global-permissions) - * [`SUPERUSER`](#superuser) - * [`PERMISSION_VIEW`](#permission-view) - * [Product-level permissions](#product-level-permissions) - * [`PRODUCT_ADMIN`](#product-admin) - * [`PRODUCT_ACCESS`](#product-access) - * [`PRODUCT_STORE`](#product-store) - * [`PRODUCT_VIEW`](#product-view) +- [Permission subsystem](#permission-subsystem) +- [Table of Contents](#table-of-contents) +- [The master superuser (root) ](#the-master-superuser-root-) +- [Managing permissions ](#managing-permissions-) +- [Permission concepts ](#permission-concepts-) + - [Default value ](#default-value-) + - [Permission inheritance ](#permission-inheritance-) + - [Permission manager ](#permission-manager-) +- [Available permissions ](#available-permissions-) + - [Server-wide (global) permissions ](#server-wide-global-permissions-) + - [`SUPERUSER` ](#superuser-) + - [`PERMISSION_VIEW`](#permission_view) + - [Product-level permissions ](#product-level-permissions-) + - [`PRODUCT_ADMIN` ](#product_admin-) + - [`PRODUCT_ACCESS` ](#product_access-) + - [`PRODUCT_STORE` ](#product_store-) + - [`PRODUCT_VIEW` ](#product_view-) # The master superuser (root) -Each CodeChecker server at its first start generates a master superuser -(*root*) access credential which it prints into its standard output: +At the first CodeChecker startup it is recommended that +you set up a single user with `SUPERUSER` permission. +Then with this user you will be able to configure additional permissions +for other users in the WEB GUI. +Let's say you want to give `SUPERUSER` permission to user `admin`. +Then set `super_user` field in the `server_config.json` configuration file: ```sh -[WARNING] Server started without 'root.user' present in CONFIG_DIRECTORY! -A NEW superuser credential was generated for the server. This information IS -SAVED, thus subsequent server starts WILL use these credentials. You WILL NOT -get to see the credentials again, so MAKE SURE YOU REMEMBER THIS LOGIN! ------------------------------------------------------------------ -The superuser's username is 'AAAAAA' with the password 'aaaa0000' ------------------------------------------------------------------ +"authentication": { + "enabled" : true, + "super_user" : "admin", +... ``` -These credentials can be deleted and new ones can be requested by starting -CodeChecker server with the `--reset-root` flag. The credentials are always -**randomly generated**. - -If the server has authentication enabled, the *root* user will **always have -access** despite of the configured authentication backends' decision, and -will automatically **have the `SUPERUSER` permission**. - # Managing permissions ![Global permission manager](images/permissions.png) @@ -184,4 +179,4 @@ delete existing analysis runs from the server. |---------|-----------------|-----------------| | Granted | `PRODUCT_ADMIN` | `PRODUCT_ADMIN`, `PRODUCT_STORE`, `PRODUCT_ACCESS` | -Users need the `PRODUCT_VIEW` permission to `view` analysis runs without modifying any properties of the runs. \ No newline at end of file +Users need the `PRODUCT_VIEW` permission to `view` analysis runs without modifying any properties of the runs. \ No newline at end of file diff --git a/docs/web/user_guide.md b/docs/web/user_guide.md index 846599b76a..c5ca9b9004 100644 --- a/docs/web/user_guide.md +++ b/docs/web/user_guide.md @@ -12,8 +12,8 @@ - [Configuring database and server settings location](#configuring-database-and-server-settings-location) - [Server Configuration (Authentication and Server Limits)](#server-configuration-authentication-and-server-limits) - [Database Configuration](#database-configuration) - - [Master superuser and authentication forcing](#master-superuser-and-authentication-forcing) - - [Enfore secure socket (TLS/SSL)](#enfore-secure-socket-ssl) + - [Initial super-user](#initial-super-user) + - [Enfore secure socket (TLS/SSL)](#enfore-secure-socket-tlsssl) - [Managing running servers](#managing-running-servers) - [Manage server database upgrades](#manage-server-database-upgrades) - [`store`](#store) @@ -150,7 +150,7 @@ usage: CodeChecker server [-h] [-w WORKSPACE] [-f CONFIG_DIRECTORY] [--sqlite SQLITE_FILE | --postgresql] [--dbaddress DBADDRESS] [--dbport DBPORT] [--dbusername DBUSERNAME] [--dbname DBNAME] - [--reset-root] [--force-authentication] + [--force-authentication] [-l | -r | -s | --stop-all] [--db-status STATUS | --db-upgrade-schema PRODUCT_TO_UPGRADE | --db-force-upgrade] [--verbose {info,debug,debug_analyzer}] @@ -309,26 +309,21 @@ project. **It is recommended to use only the Postgresql databse for production deployments!** -#### Master superuser and authentication forcing - -``` -root account arguments: - Servers automatically create a root user to access the server's - configuration via the clients. This user is created at first start and - saved in the CONFIG_DIRECTORY, and the credentials are printed to the - server's standard output. The plaintext credentials are NEVER accessible - again. - - --reset-root Force the server to recreate the master superuser - (root) account name and password. The previous - credentials will be invalidated, and the new ones will - be printed to the standard output. - --force-authentication - Force the server to run in authentication requiring - mode, despite the configuration value in - 'session_config.json'. This is needed if you need to - edit the product configuration of a server that would - not require authentication otherwise. +#### Initial super-user + +You can give a single user SUPER_USER permission +by setting the `super_user` field in the `authentication` +section of the `server_config.json`. +The user which is set here, must be an existing user. +For example it should be a user +with dictionary authentication method. + +``` + "authentication": { + "enabled" : true, + "super_user" : "admin", +... + ``` #### Enfore secure socket (TLS/SSL) diff --git a/tools/report-converter/codechecker_report_converter/analyzers/infer/analyzer_result.py b/tools/report-converter/codechecker_report_converter/analyzers/infer/analyzer_result.py index f351d91f3b..d4af69c04a 100644 --- a/tools/report-converter/codechecker_report_converter/analyzers/infer/analyzer_result.py +++ b/tools/report-converter/codechecker_report_converter/analyzers/infer/analyzer_result.py @@ -78,6 +78,10 @@ def __get_abs_path(self, source_path): if os.path.exists(full_path): return full_path + abs_path_corrected = os.path.join('/', source_path) + if os.path.exists(abs_path_corrected): + return abs_path_corrected + LOG.warning("No source file found: %s", source_path) return None diff --git a/web/api/products.thrift b/web/api/products.thrift index b6b3cf1bda..e13a3ed3e7 100644 --- a/web/api/products.thrift +++ b/web/api/products.thrift @@ -74,6 +74,7 @@ service codeCheckerProductService { // Get the list of product that matches the display name and endpoint // filters specified. + // PERMISSION: PRODUCT_VIEW Products getProducts(1: string productEndpointFilter, 2: string productNameFilter) throws (1: codechecker_api_shared.RequestFailed requestError), diff --git a/web/api/report_server.thrift b/web/api/report_server.thrift index 359372e28a..7c34652a31 100644 --- a/web/api/report_server.thrift +++ b/web/api/report_server.thrift @@ -688,7 +688,7 @@ service codeCheckerDBAccess { throws (1: codechecker_api_shared.RequestFailed requestError), // Return true if review status change is disabled. - // PERMISSION: PRODUCT_ACCESS or PRODUCT_STORE + // PERMISSION: PRODUCT_VIEW bool isReviewStatusChangeDisabled() throws (1: codechecker_api_shared.RequestFailed requestError), @@ -769,6 +769,7 @@ service codeCheckerDBAccess { // get the md documentation for a checker // DEPRECATED. Use getCheckerLabels() instead which contains checker // documentation URL. + // PERMISSION: PRODUCT_VIEW string getCheckerDoc(1: string checkerId) throws (1: codechecker_api_shared.RequestFailed requestError), diff --git a/web/server/codechecker_server/api/config_handler.py b/web/server/codechecker_server/api/config_handler.py index aeba492d02..a946f55f14 100644 --- a/web/server/codechecker_server/api/config_handler.py +++ b/web/server/codechecker_server/api/config_handler.py @@ -31,14 +31,23 @@ class ThriftConfigHandler: Manages Thrift requests regarding configuration. """ - def __init__(self, auth_session, config_session): + def __init__(self, auth_session, config_session, session_manager): self.__auth_session = auth_session self.__session = config_session + self.__session_manager = session_manager def __require_supermission(self): """ Checks if the current user isn't a SUPERUSER. """ + + # Anonymous access is only allowed if authentication is + # turned off + if self.__session_manager.is_enabled and not self.__auth_session: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.UNAUTHORIZED, + "You are not authorized to execute this action.") + if (not (self.__auth_session is None) and not self.__auth_session.is_root): raise codechecker_api_shared.ttypes.RequestFailed( @@ -69,7 +78,7 @@ def getNotificationBannerText(self): def setNotificationBannerText(self, notification_b64): """ Sets the notification banner remove_products_except. - Bevare: This method only works if the use is a SUPERUSER. + Beware: This method only works if the use is a SUPERUSER. """ self.__require_supermission() diff --git a/web/server/codechecker_server/api/product_server.py b/web/server/codechecker_server/api/product_server.py index 026f57aeff..f7cbaeadb6 100644 --- a/web/server/codechecker_server/api/product_server.py +++ b/web/server/codechecker_server/api/product_server.py @@ -67,8 +67,17 @@ def __require_permission(self, required, args=None): with DBSession(self.__session) as session: if args is None: args = dict(self.__permission_args) + + if 'config_db_session' not in args: args['config_db_session'] = session + # Anonymous access is only allowed if authentication is + # turned off + if self.__server.manager.is_enabled and not self.__auth_session: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.UNAUTHORIZED, + "You are not authorized to execute this action.") + if not any(permissions.require_permission( perm, args, self.__auth_session) for perm in required): @@ -247,6 +256,9 @@ def getProductConfiguration(self, product_id): Get the product configuration --- WITHOUT THE DB PASSWORD --- of the given product. """ + self.__require_permission([permissions.PRODUCT_VIEW], { + 'productID': product_id + }) with DBSession(self.__session) as session: product = session.query(Product).get(product_id) diff --git a/web/server/codechecker_server/api/report_server.py b/web/server/codechecker_server/api/report_server.py index f3e2a7a6b5..bb58998283 100644 --- a/web/server/codechecker_server/api/report_server.py +++ b/web/server/codechecker_server/api/report_server.py @@ -534,39 +534,31 @@ def get_source_component_file_query( def get_reports_by_bugpath_filter(session, file_filter_q) -> Set[int]: """ - This function returns a set of report IDs that are related to any file + This function returns a query for report IDs that are related to any file described by the query in the second parameter, either because their bug path goes through these files, or there is any bug note, etc. in these files. """ - def first_col_values(query): - """ - This function executes a query and returns the set of first columns' - values. - """ - return set(map(lambda x: x[0], query.all())) - - report_ids = set() - - q = session.query(Report.id) \ + q_report = session.query(Report.id) \ .join(File, File.id == Report.file_id) \ .filter(file_filter_q) - report_ids.update(first_col_values(q)) - - q = session.query(BugPathEvent.report_id) \ + q_bugpathevent = session.query(BugPathEvent.report_id) \ .join(File, File.id == BugPathEvent.file_id) \ .filter(file_filter_q) - report_ids.update(first_col_values(q)) + q_bugreportpoint = session.query(BugReportPoint.report_id) \ + .join(File, File.id == BugReportPoint.file_id) \ + .filter(file_filter_q) - q = session.query(ExtendedReportData.report_id) \ + q_extendedreportdata = session.query(ExtendedReportData.report_id) \ .join(File, File.id == ExtendedReportData.file_id) \ .filter(file_filter_q) - report_ids.update(first_col_values(q)) - - return report_ids + return q_report.union( + q_bugpathevent, + q_extendedreportdata, + q_bugreportpoint) def get_reports_by_components(session, component_names: List[str]) -> Set[int]: @@ -1447,6 +1439,13 @@ def __require_permission(self, required): args = dict(self.__permission_args) args['config_db_session'] = session + # Anonymous access is only allowed if authentication is + # turned off + if self._manager.is_enabled and not self._auth_session: + raise codechecker_api_shared.ttypes.RequestFailed( + codechecker_api_shared.ttypes.ErrorCode.UNAUTHORIZED, + "You are not authorized to execute this action.") + if not any(permissions.require_permission( perm, args, self._auth_session) for perm in required): @@ -2320,6 +2319,7 @@ def _setReviewStatus(self, session, report_hash, status, database transaction. This is needed because during storage a specific session object has to be used. """ + review_status = session.query(ReviewStatus).get(report_hash) if review_status is None: review_status = ReviewStatus() @@ -2421,6 +2421,8 @@ def isReviewStatusChangeDisabled(self): """ Return True if review status change is disabled. """ + self.__require_view() + with DBSession(self._config_database) as session: product = session.query(Product).get(self._product.id) return product.is_review_status_change_disabled @@ -2746,7 +2748,7 @@ def getCheckerDoc(self, _): Parameters: - checkerId """ - + self.__require_view() return "" @exc_to_thrift_reqfail @@ -2756,6 +2758,8 @@ def getCheckerLabels( checkers: List[ttypes.Checker] ) -> List[List[str]]: """ Return the list of labels to each checker. """ + self.__require_view() + labels = [] for checker in checkers: analyzer_name = None if not checker.analyzerName \ @@ -3569,6 +3573,8 @@ def getFailedFilesCount(self, run_ids): given run. If the run id list is empty the number of failed files will be counted for all of the runs. """ + self.__require_view() + # Unfortunately we can't distinct the failed file paths by using SQL # queries because the list of failed files for a run / analyzer are # stored in one column in a compressed way. For this reason we need to @@ -3611,6 +3617,8 @@ def getFailedFiles(self, run_ids): # ----------------------------------------------------------------------- @timeit def getPackageVersion(self): + self.__require_view() + return self.__package_version # ----------------------------------------------------------------------- diff --git a/web/server/codechecker_server/cmd/server.py b/web/server/codechecker_server/cmd/server.py index 33bbbd20f1..c7781250b8 100644 --- a/web/server/codechecker_server/cmd/server.py +++ b/web/server/codechecker_server/cmd/server.py @@ -212,17 +212,6 @@ def add_arguments_to_parser(parser): CONFIG_DIRECTORY, and the credentials are printed to the server's standard output. The plaintext credentials are NEVER accessible again.""") - root_account.add_argument('--reset-root', - dest="reset_root", - action='store_true', - default=argparse.SUPPRESS, - required=False, - help="Force the server to recreate the master " - "superuser (root) account name and " - "password. The previous credentials will " - "be invalidated, and the new ones will be " - "printed to the standard output.") - root_account.add_argument('--force-authentication', dest="force_auth", action='store_true', @@ -932,15 +921,6 @@ def server_init_start(args): not os.path.isdir(os.path.dirname(args.sqlite)): os.makedirs(os.path.dirname(args.sqlite)) - if 'reset_root' in args: - try: - os.remove(os.path.join(args.config_directory, 'root.user')) - LOG.info("Master superuser (root) credentials invalidated and " - "deleted. New ones will be generated...") - except OSError: - # File doesn't exist. - pass - if 'force_auth' in args: LOG.info("'--force-authentication' was passed as a command-line " "option. The server will ask for users to authenticate!") diff --git a/web/server/codechecker_server/routing.py b/web/server/codechecker_server/routing.py index 79ac8d0686..dfffe8eea8 100644 --- a/web/server/codechecker_server/routing.py +++ b/web/server/codechecker_server/routing.py @@ -71,6 +71,8 @@ def is_supported_version(version): version = version.lstrip('v') version_parts = version.split('.') + if len(version_parts) < 2: + return False # We don't care if accidentally the version tag contains a revision number. major, minor = int(version_parts[0]), int(version_parts[1]) @@ -115,24 +117,27 @@ def split_client_POST_request(path): """ # A standard POST request from an API client looks like: - # http://localhost:8001/[product-name]// + # http://localhost:8001/[product-name/]/ # where specifying the product name is optional. split_path = urlparse(path).path.split('/', 3) endpoint_part = split_path[1] - if is_valid_product_endpoint(split_path[1]): + if is_valid_product_endpoint(split_path[1]) and len(split_path) == 4: version_tag = split_path[2].lstrip('v') - remainder = split_path[3] + if not is_supported_version(version_tag): + return None, None, None + endpoint = split_path[3] + return endpoint_part, version_tag, endpoint - return endpoint_part, version_tag, remainder - elif split_path[1].startswith('v'): + elif split_path[1].startswith('v') and len(split_path) == 3: # Request came through without a valid product URL endpoint to # possibly the main server. version_tag = split_path[1].lstrip('v') - remainder = split_path[2] - - return None, version_tag, remainder + if not is_supported_version(version_tag): + return None, None, None + endpoint = split_path[2] + return None, version_tag, endpoint return None, None, None diff --git a/web/server/codechecker_server/server.py b/web/server/codechecker_server/server.py index 40bdf6db4d..2439dc4787 100644 --- a/web/server/codechecker_server/server.py +++ b/web/server/codechecker_server/server.py @@ -14,17 +14,14 @@ import atexit import datetime from functools import partial -from hashlib import sha256 from http.server import HTTPServer, SimpleHTTPRequestHandler import os import posixpath -from random import sample import shutil import signal import socket import ssl import sys -import stat from typing import List, Optional, Tuple import urllib @@ -68,8 +65,6 @@ Configuration as ORMConfiguration from .database.database import DBSession from .database.run_db_model import IDENTIFIER as RUN_META, Run, RunLock -from .tmp import get_tmp_dir_hash - LOG = get_logger('server') @@ -333,10 +328,21 @@ def do_POST(self): otrans = TTransport.TMemoryBuffer() oprot = output_protocol_factory.getProtocol(otrans) + product_endpoint, api_ver, request_endpoint = \ + routing.split_client_POST_request(self.path) + + if product_endpoint is None and api_ver is None and \ + request_endpoint is None: + self.send_thrift_exception("Invalid request endpoint path.", iprot, + oprot, otrans) + return + + # Only Authentication, Configuration, ServerInof + # endpoints are allowed for Anonymous users + # if authentication is required. if self.server.manager.is_enabled and \ - not self.path.endswith(('/Authentication', - '/Configuration', - '/ServerInfo')) and \ + request_endpoint not in \ + ['Authentication', 'Configuration', 'ServerInfo'] and \ not self.auth_session: # Bail out if the user is not authenticated... # This response has the possibility of melting down Thrift clients, @@ -352,12 +358,6 @@ def do_POST(self): # Authentication is handled, we may now respond to the user. try: - product_endpoint, api_ver, request_endpoint = \ - routing.split_client_POST_request(self.path) - if product_endpoint is None and api_ver is None and \ - request_endpoint is None: - raise ValueError("Invalid request endpoint path.") - product = None if product_endpoint: # The current request came through a product route, and not @@ -378,7 +378,8 @@ def do_POST(self): elif request_endpoint == 'Configuration': conf_handler = ConfigHandler_v6( self.auth_session, - self.server.config_session) + self.server.config_session, + self.server.manager) processor = ConfigAPI_v6.Processor(conf_handler) elif request_endpoint == 'ServerInfo': server_info_handler = ServerInfoHandler_v6(version) @@ -991,43 +992,6 @@ class CCSimpleHttpServerIPv6(CCSimpleHttpServer): address_family = socket.AF_INET6 -def __make_root_file(root_file): - """ - Generate a root username and password SHA. This hash is saved to the - given file path, and is also returned. - """ - - LOG.debug("Generating initial superuser (root) credentials...") - - username = ''.join(sample("ABCDEFGHIJKLMNOPQRSTUVWXYZ", 6)) - password = get_tmp_dir_hash()[:8] - - LOG.info("A NEW superuser credential was generated for the server. " - "This information IS SAVED, thus subsequent server starts " - "WILL use these credentials. You WILL NOT get to see " - "the credentials again, so MAKE SURE YOU REMEMBER THIS " - "LOGIN!") - - # Highlight the message a bit more, as the server owner configuring the - # server must know this root access initially. - credential_msg = f"The superuser's username is '{username}' with the " \ - f"password '{password}'" - LOG.info("-" * len(credential_msg)) - LOG.info(credential_msg) - LOG.info("-" * len(credential_msg)) - - sha = sha256((username + ':' + password).encode('utf-8')).hexdigest() - secret = f"{username}:{sha}" - with open(root_file, 'w', encoding="utf-8", errors="ignore") as f: - LOG.debug("Save root SHA256 '%s'", secret) - f.write(secret) - - # This file should be only readable by the process owner, and noone else. - os.chmod(root_file, stat.S_IRUSR) - - return secret - - def start_server(config_directory, package_data, port, config_sql_server, listen_address, force_auth, skip_db_cleanup: bool, context, check_env): @@ -1038,22 +1002,17 @@ def start_server(config_directory, package_data, port, config_sql_server, server_addr = (listen_address, port) + # The root user file is DEPRECATED AND IGNORED root_file = os.path.join(config_directory, 'root.user') - if not os.path.exists(root_file): - LOG.warning("Server started without 'root.user' present in " - "CONFIG_DIRECTORY!") - root_sha = __make_root_file(root_file) - else: - LOG.debug("Root file was found. Loading...") - try: - with open(root_file, 'r', encoding="utf-8", errors="ignore") as f: - root_sha = f.read() - LOG.debug("Root digest is '%s'", root_sha) - except IOError: - LOG.info("Cannot open root file '%s' even though it exists", - root_file) - root_sha = __make_root_file(root_file) - + if os.path.exists(root_file): + LOG.warning("The 'root.user' file: %s" + " is deprecated and ignored. If you want to" + " setup an initial user with SUPER_USER permission," + " configure the super_user field in the server_config.json" + " as described in the documentation." + " To get rid off this warning," + " simply delete the root.user file.", + root_file) # Check whether configuration file exists, create an example if not. server_cfg_file = os.path.join(config_directory, 'server_config.json') if not os.path.exists(server_cfg_file): @@ -1077,7 +1036,6 @@ def start_server(config_directory, package_data, port, config_sql_server, try: manager = session_manager.SessionManager( server_cfg_file, - root_sha, force_auth) except IOError as ioerr: LOG.debug(ioerr) @@ -1098,7 +1056,7 @@ def start_server(config_directory, package_data, port, config_sql_server, "Earlier logs might contain additional detailed " "reasoning.\n\t* %s", len(fails), "\n\t* ".join( - (f"'{ep}' ({reason})" for (ep, reason) in fails) + (f"'{ep}' ({reason})" for (ep, reason) in fails) )) else: LOG.debug("Skipping db_cleanup, as requested.") diff --git a/web/server/codechecker_server/session_manager.py b/web/server/codechecker_server/session_manager.py index 71c6e69fc7..4bcc6ace65 100644 --- a/web/server/codechecker_server/session_manager.py +++ b/web/server/codechecker_server/session_manager.py @@ -9,7 +9,6 @@ Handles the management of authentication sessions on the server's side. """ -import hashlib import json import os import re @@ -165,13 +164,12 @@ class SessionManager: CodeChecker server. """ - def __init__(self, configuration_file, root_sha, force_auth=False): + def __init__(self, configuration_file, force_auth=False): """ Initialise a new Session Manager on the server. :param configuration_file: The configuration file to read authentication backends from. - :param root_sha: The SHA-256 hash of the root user's authentication. :param force_auth: If True, the manager will be enabled even if the configuration file disables authentication. """ @@ -203,9 +201,6 @@ def __init__(self, configuration_file, root_sha, force_auth=False): self.__refresh_time = self.__auth_config['refresh_time'] \ if 'refresh_time' in self.__auth_config else None - # Save the root SHA into the configuration (but only in memory!) - self.__auth_config['method_root'] = root_sha - self.__regex_groups_enabled = False # Pre-compile the regular expressions of 'regex_groups' @@ -369,17 +364,16 @@ def get_realm(self): "error": self.__auth_config.get('realm_error') } + @property + def get_super_user(self): + return { + "super_user": self.__auth_config.get('super_user'), + } + @property def default_superuser_name(self) -> Optional[str]: """ Get default superuser name. """ - root = self.__auth_config['method_root'].split(":") - - # Previously the root file doesn't contain the user name. In this case - # we will return with no user name. - if len(root) <= 1: - return None - - return root[0] + return self.__auth_config['super_user'] def set_database_connection(self, connection): """ @@ -400,8 +394,7 @@ def __handle_validation(self, auth_string): This validation object contains two keys: username and groups. """ - validation = self.__try_auth_root(auth_string) \ - or self.__try_auth_dictionary(auth_string) \ + validation = self.__try_auth_dictionary(auth_string) \ or self.__try_auth_pam(auth_string) \ or self.__try_auth_ldap(auth_string) if not validation: @@ -422,22 +415,6 @@ def __is_method_enabled(self, method): 'method_' + method in self.__auth_config and \ self.__auth_config['method_' + method].get('enabled') - def __try_auth_root(self, auth_string): - """ - Try to authenticate the user against the root username:password's hash. - """ - user_name = SessionManager.get_user_name(auth_string) - sha = hashlib.sha256(auth_string.encode('utf8')).hexdigest() - - if f"{user_name}:{sha}" == self.__auth_config['method_root']: - return { - 'username': SessionManager.get_user_name(auth_string), - 'groups': [], - 'root': True - } - - return False - def __try_auth_token(self, auth_string): if not self.__database_connection: return None @@ -597,7 +574,10 @@ def get_db_auth_session_tokens(self, user_name): def __is_root_user(self, user_name): """ Return True if the given user has system permissions. """ - if self.__auth_config['method_root'].split(":")[0] == user_name: + if 'super_user' not in self.__auth_config: + return False + + if self.__auth_config['super_user'] == user_name: return True transaction = None diff --git a/web/server/tests/unit/test_request_routing.py b/web/server/tests/unit/test_request_routing.py index d15a2ef2ee..9a392b09a1 100644 --- a/web/server/tests/unit/test_request_routing.py +++ b/web/server/tests/unit/test_request_routing.py @@ -52,16 +52,12 @@ def test_post(self): # It is the server code's responsibility to give a 404 Not Found. self.assertEqual(post(''), (None, None, None)) self.assertEqual(post('CodeCheckerService'), (None, None, None)) - - # Raise an exception if URL is malformed, such as contains a - # product-endpoint-like component which is badly encoded version - # string. - with self.assertRaises(Exception): - post('v6.0') - post('/v6/CodeCheckerService') + self.assertEqual(post('v6.0'), (None, None, None)) + self.assertEqual(post('/v6.0/product/Authentication/Service'), + (None, None, None)) self.assertEqual(post('/v6.0/Authentication'), (None, '6.0', 'Authentication')) - self.assertEqual(post('/DummyProduct/v0.0/FoobarService'), - ('DummyProduct', '0.0', 'FoobarService')) + self.assertEqual(post('/DummyProduct/v6.0/FoobarService'), + ('DummyProduct', '6.0', 'FoobarService')) diff --git a/web/server/vue-cli/e2e/init.workspace.js b/web/server/vue-cli/e2e/init.workspace.js index 9ca21579f5..c1e43a5453 100644 --- a/web/server/vue-cli/e2e/init.workspace.js +++ b/web/server/vue-cli/e2e/init.workspace.js @@ -7,20 +7,19 @@ const WORKSPACE_DIR = path.join(CC_DIR, "workspace"); const SERVER_CONFIG = { authentication: { enabled : true, + "super_user" : "root", session_lifetime : 60000, refresh_time : 60, logins_until_cleanup : 30, method_dictionary: { enabled : true, - auths : [ "cc:admin" ], + auths : [ "cc:admin", + "root:S3cr3t" ], groups : {} } } }; -const ROOT_USER = - "root:2691b13e4c5eadd0adad38983e611b2caa19caaa3476ccf31cbcadddf65c321c"; - // Create workspace directory if it does not exists. if (!fs.existsSync(WORKSPACE_DIR)) { fs.mkdirSync(WORKSPACE_DIR); @@ -29,10 +28,4 @@ if (!fs.existsSync(WORKSPACE_DIR)) { // Create server configuration file and enable authentication. const serverConfigFile = path.join(WORKSPACE_DIR, "server_config.json"); const data = JSON.stringify(SERVER_CONFIG, null, " "); -fs.writeFileSync(serverConfigFile, data); - -// Generate initial root credentials. -// - username: root -// - password: S3cr3t -const rootUserFile = path.join(WORKSPACE_DIR, "root.user"); -fs.writeFileSync(rootUserFile, ROOT_USER); +fs.writeFileSync(serverConfigFile, data); \ No newline at end of file diff --git a/web/server/vue-cli/src/views/NewFeatures.vue b/web/server/vue-cli/src/views/NewFeatures.vue index 4130d13b73..8a98c713b5 100644 --- a/web/server/vue-cli/src/views/NewFeatures.vue +++ b/web/server/vue-cli/src/views/NewFeatures.vue @@ -2,6 +2,60 @@ + + + + + + + + + CodeChecker provides a new view in the "Analysis information tab" which lists all checkers that were enabled during analysis. + + + + + CodeChecker provides a new view to display all enabled checkers for a set of selected runs. Additionally, it also lists all guideline rules related to the given checker. For example, you can verify whether your code has any SEI Cert coding guideline violation. + The new table lists all checkers that were enabled in a set of selected analysis runs, shows the number of outstanding reports and the number of closed reports per enabled checker and the related coding guideline rules. + + + + + Thanks to a new optimization, the run storage duration can be up to 50% faster. + + + + + The static HTML generation is rewritten so it can handle much larger result set. + + + + + A new filter has been added to list outstanding and closed reports. An outstanding report is a report with detection status new, reopened, unresolved with review status unreviewed or confirmed. + + + + + +