Svace is an automatic defect detection tool for source code written in C, C++, C#, Go, Java, Kotlin, Python and Scala languages. The detection algorithms are based on static analysis. This software is copyrighted by Ivannikov Institute for System Programming of the Russian Academy of Sciences (ISP RAS).
Svace is a continuously evolving product based on long term research. It uses a number of unique state-of-the-art analysis technologies and adopts the front-end of several open compilers to support the latest language standards. Svace provides ease of integration with any build systems and allows to accurately capture the exact code being built and the details of its compilation. Svace analysis engine covers all program execution paths and considers function calls in particular. The analysis is fully parallel and scalable up to projects with tens of millions lines of code. Several third-party analyzers are integrated into Svace to widen the scope of detected issues. Analysis results are represented through detailed reports, where the traces provided for each reported issue support code navigation.
This User Guide includes the following main sections:
Typically, analyzing source code with Svace involves four stages:
Svace build monitoring allows to capture the finest details of the build process unavailable to other tools without this feature. In turn this lets the analyzer produce accurate and relevant intermediate representation of the analyzed source code. Finally, the main analysis engine utilize it to perform deep interprocedural and path-sensitive analysis able to uncover complex issues in the source code. Secondary analysis engines are also run on the same intermediate representation to quickly supplement them with a large number of more specific and simpler issues. The analysis results of all tools are combined and presented together in various formats including plain text, SARIF, XML and JSON.
History of previous analysis runs allows one to indicate (for a new run) which issues are new to this run and which issues detected on this run have been previously marked as incorrectly detected (false positives).
Svace is released in 3 separate distributions:
By request a distribution for macOS may be provided (x86_64, tested
on Mojave and newer; svace build
requires disabling System
Integrity Protection).
The analyzer has no external dependencies and may be used right after unpacking.
Svace supports the following languages versions (including earlier, unless stated otherwise):
go build
and go install
or bazel
javac
(including related,
e.g. Axiom JDK and Liberica JDK)gradle
, maven
or
kotlinc
List of supported C/C++ compilers:
List of supported target architectures for GCC/Clang-based compilers, not specified explicitly:
List of known compilers with no plans to support:
This section describes how to perform analysis through command line
interface, explaining the process of analysis in detail. The command
line interface is provided by the svace
program located in
the bin
subfolder of a Svace distribution. It supports a
number of commands (or tools) to perform various tasks. The main tools
are listed in the Command line tools
section. The full list can be shown by running the svace
program without arguments or the svace help
tool.
Svace stores all the data related to a project being analyzed in a
special project folder. This folder can be created using a svace init tool. It has the
.svace-dir
name by default and is created in the current
working directory.
Svace provides the svace build tool to
wrap a normal building command. This tool intercepts invocations of compilers supported by
Svace and transparently runs Svace internal compilers along with the
original compiler command. For example, if the analyzed project uses a
Make build system and is built using a make -f Makefile
command then to automatically generate intermediate files for analysis
of the project run svace build make -f Makefile
command.
For SBS make sure that sbs
is in PATH
. The
following command should now work from a source package directory:
svace build sbs -b
Similar procedure is for building a Tizen package using GBS. For
example, to build a libxml2
package:
svace build gbs build -A armv7l --binary-list libxml2
Use Aurora-specific Linux distributions of Svace. Note that Aurora SDK prior to version 5.0 and Sailfish SDK build environments are 32-bit.
Place Svace distribution in a folder for which sb2
doesn’t rewrite paths, e.g. $HOME
. Incorrectly placed
distribution may lead to build errors similar to these:
ERROR: ld.so: object 'libsvace.so' from LD_PRELOAD cannot be preloaded (cannot open shared object file): ignored.
Run svace build tool inside the
sb2
sandbox (i.e. sb2 svace build ...
instead
of svace build sb2 ...
). Otherwise, Svace components
working outside the sandbox won’t be able to properly handle paths to
files inside the sandbox. Incorrectly run command may lead to the
following build interception error:
svace build: warning: Hash server reported some errors.
Add a --prepend-preload-lib
option to
svace build
command.
Svace supports building of Go projects on Linux that use Bazel build system or bazelisk wrapper:
Run the following commands before the build (otherwise not all compilations may be intercepted because of caching):
bazel clean --expunge # clean up cache
bazel shutdown # stop Bazel server processes
# or use '--batch' flag of subsequent 'bazel build' command
Add a --go-interception-mode compile
option to
svace build
command.
Add a --spawn_strategy=local
option to
bazel build
. More details here.
To analyze only Go source code, add options
--disable-language all
and
--enable-language go
to svace build
or
svace analyze
commands. In this way only Go compilations
are intercepted/analyzed.
For example, if the build command of your project is
bazel build //...
, you may use svace build
command like this:
svace build --disable-language all --enable-language go --go-interception-mode compile bazel --batch build --spawn_strategy=local //...
By default, warnings from a balanced list of warning types with high reliability and severity are reported. The svace warning tool can list these and other available warning types and control whether they are enabled or not for subsequent analyses.
Svace supports configuration options that may affect the analysis behaviour. Default option values are suitable for most scenarios, but it is possible to tune specific parameters. Changing their values is done using the svace config tool:
svace config THREAD_NUMBER 4
In the example above the THREAD_NUMBER
configuration
option, that controls the number of parallel threads used for analysis,
is set to 4. By default, the THREAD_NUMBER
option has a
special value max
that allows analyzer to use as many
threads as the number of processor cores. Other possible values are
positive numbers to set specific analysis threads number and
auto
to select threads number depending on the current
processor load.
In most cases analysis is the fastest when THREAD_NUMBER
is max
. At the same time each thread uses additional memory
so more threads will use more memory and when the amount of RAM is
limited it is better to decrease this option value.
Use the svace analyze command line tool. The analysis results are available afterwards in the project folder in various formats and can be viewed with a text editor, imported directly into a defect management tool or into a history storage.
A Svace distribution additionally includes Clang Static Analyzer and SpotBugs. Also, a number of lightweight detectors are implemented in Svace UAST engine for searching of issues patterns common for all supported languages. By default, all those tools are run before the main analysis, using intermediate data produced during the build phase as input. Defect reports produced by those tools are included into the final analysis results.
In order to control history of source code issues introductions and fixes found during project development the analysis results can be imported into history storage to be shown and processed afterwards. History storage contains the list of warnings ever found in the project, and snapshots of the relevant source code. It also recognizes some issues detected on a new analysis run as instances of previously detected issues (even if the source code and line numbers changed between the analyses). If an issue was marked as a ‘false positive’ after some analysis run, it will remain marked so in new analysis results. The history storage functionality is provided by the internal Svace history storage or by a standalone Svacer history server. The latter is advised as it provides more features and is better supported.
Analysis results may be viewed through a web-browser using either a builtin server (when using internal Svace history storage, see svace server) or a standalone Svacer history server.
Each issue detected by Svace is associated with the following information:
Undecided
, Confirmed
,
Won't fix
, False positive
or
Unclear
).The analysis results are presented as a tree. The top level lists warnings by type, the second level lists the warnings themselves.
Each warning record shows location in the source code and warning
message that may include names of the relevant variables, functions,
classes, etc. For each warning, subnodes in the tree may indicate
multiple source locations playing different roles in the warning (for
example, for DEREF_AFTER_NULL, one
location shows where the pointer is dereferenced, and the other location
shows where it was compared to NULL
). Together they form a
warning trace that helps in understanding the program flow from the
defect origin to its manifestation point, or provides other useful
information related to the warning.
When using a web-server warning evaluation status can be set by selecting appropriate value from the drop-down list, and is indicated by the color of the issue. There are the following options:
The command line tools are accessed through a wrapper tool
svace
located in the bin
subfolder of a Svace
distribution (it is recommended to add this folder to the
PATH
environment variable, so that svace
is
accessible as a command). Command line options for svace
are as follows:
usage: svace <command> [args]
svace --version [--quiet | --verbose]
svace [--help]
This is the command-line interface for Svace static analysis tool.
Type 'svace help <command>' to get help on a specific command.
options:
--help Show this help message and exit.
-V, --version Show version information and exit. If used together
with '--quiet', only version number in X.Y.Z format is
printed. If used together with '--verbose', source
code revision is printed too.
-v, --verbose Increase output verbosity.
-q, --quiet Reduce output verbosity.
commands:
main:
analyze Run Svace static analyzer to search for defects in
source code, given the data gathered during 'svace
build'.
build Capture invocations of supported compilers performed
by a given command and generate intermediate data for
a subsequent analysis.
config Manage general configuration settings.
init Create a Svace project directory.
server Configure or run Svace server.
warning Control which warning types are enabled during
analysis.
miscellaneous:
admin Manage object pool in a project directory.
compare-svres Compare warnings from two .svres files ignoring
checker names and generate comparison stats.
context-spec Manage context in which particular function will be
analyzed.
export-build Export a build object into an external directory.
filter-svres Extract the issues enabled in given warning settings
file from a given .svres.
history Interact with a warning database.
ignore-test Check svace.ignore filter.
inactive-comments Find warning suppression comments that appear to be
obsolete.
match-svres Match two svres files and show identifiers of the
matched warnings from the first svres file.
merge-build Merge multiple build objects into a single build
object.
merge-svres Merge multiple .svres files with analysis results for
different projects into a single file.
remote Execute command on remote analysis server.
review-rebase Rebase warning review from one svres file to other.
server-config Manage server configuration settings.
show Show results in a Svace server (localhost:8060).
spec Manages specifications for third-party library
functions,including user-provided.
split-svres Split a multi-project .svres file into separate files
with analysis results for individual projects.
svres2json Converter from Svace results format to JSON format and
vice versa.
svres2sarif Converter from Svace results format to Sarif 2.1
format.
warning-selector Pick particular warnings and write them to an svres
file.
experimental:
suppress Run stand-alone suppression tool.
usage: svace init [options] [DIR]
Create an empty Svace project directory or reinitialize an existing one. By
default, this command creates a '.svace-dir' subdirectory in DIR (but see
--bare). If DIR is not specified, the value of SVACE_DIR environment variable
(or, if unset, the current directory) is used instead. If the project
directory already exists, this command can reinitialize its permissions
according to the options without changing the stored data. Note that only
permissions of the directory itself can be changed. Permissions of
subdirectories should be changed manually if desired.
options:
--bare Initialize the specified directory as a Svace project directory
instead of creating '.svace-dir' subdirectory in it.
--shared Make the project directory group-writable and set the set-
group-id bit on it if needed. This allows users belonging to
the current user's primary group to share this project
directory.
-q, --quiet Print only error messages (to stderr).
--help Show this help message and exit.
This tool launches a specified custom build command, intercepts invocations of supported compilers and uses Svace internal compilers to produce intermediate representation of the same source code that is being built by the intercepted command using the same options and environment.
Command line options are as follows:
usage: svace build [options] [--] <build-command>
Run <build-command> and capture all invocations of supported compilers during
its execution. Create intermediate representation files using information from
the intercepted compiler invocations.
<build-command> can include options and arguments. To capture multiple
commands, wrap the commands in a single script.
Typical usage:
svace build make -j4 all
svace build gradle --no-daemon build
svace build msbuild /t:Rebuild MySolution.sln
positional arguments:
build-command
options:
-h, --help show this help message and exit
--svace-dir SVACE_DIR
Specify a Svace directory, overriding current
directory and SVACE_DIR environment variable.
--init Initialize the selected Svace project directory before
doing anything else. This is a shortcut for avoiding
manual 'svace init' if all you need is its default
behavior.
--clear-build-dir Remove redundant contents of the build directory after
build.
--enable-language LANG, --disable-language LANG
Enable or disable compilation interception for the
specified programming language. Valid values are: all,
csharp, cxx, go, kotlin, python, scala, visualbasic.
By default, all supported languages are enabled.
-L LANGS, --languages LANGS
Enable compilation interception for languages in the
specified comma-separated list and disable it for
other languages. Supported languages are: csharp, cxx,
go, kotlin, python, scala, visualbasic. The special
value 'all' enables all supported languages (which is
the default).
--enable-uast-language UAST_LANG, --disable-uast-language UAST_LANG
Enable or disable UAST IR generation for the specified
programming language. Valid values are: all, go,
kotlin, python. See also '--uast-languages'.
--uast-languages UAST_LANGS
Enable UAST IR generation for languages in the
specified comma-separated list and disable it for
other languages. Supported languages are: go, kotlin,
python. The special value 'all' enables all supported
languages. If this option is not set, it defaults to
the subset of enabled languages supported by UAST
analysis.
--python DIRECTORY or FILE.py
Specify the path to a directory with Python source
code or to a single Python source file to process for
subsequent analysis.
--enable-mypy, --disable-mypy
Enable or disable Python analysis with mypy static
analyzer. Note that mypy is not bundled with Svace, so
it can be used only if found in your PATH and mypy
version is at least 0.790.
--enable-python-ir-gen, --disable-python-ir-gen
Enable or disable generation of Python IR used for
analysis by Svace. This option is for advanced users
only.
--too-high-kotlin-version-mode {fail,warn,ignore}
Specify what to do if an intercepted Kotlin
compilation uses a language version that is higher
than the maximum version supported by Svace. If set to
'fail' (the default), report a fatal error and don't
produce a build object. Otherwise, attempt to process
such compilations as though they were using the
maximum Kotlin version supported by Svace, and, if set
to 'warn', report a warning.
--debug Enable verbose logging useful for debugging and making
bug reports.
--enable-dxr, --disable-dxr
Build source reference index used for source code
navigation (via history API). By default, it is
enabled.
--enable-csa, --disable-csa
Run Svace checkers implemented in Clang Static
Analyzer during the build phase. By default, this
feature is disabled.
--enable-csa-deps, --disable-csa-deps
Prepare everything needed for running Clang Static
Analyzer during the analysis phase. By default, this
feature is enabled.
--enable-spotbugs, --disable-spotbugs
Run SpotBugs checkers for intercepted Java
compilations during the build phase. By default, this
feature is disabled.
--disable-java-agent Don't inject Java agent into intercepted JVM
processes. If this option is used, compilations
performed via compiler APIs (e.g., javac API) are not
intercepted. Since Svace Java agent requires Java 8 or
newer and causes build failure if it's injected into
an older JVM, this option can be useful if build
process involves older Java and compiler API
interception is not needed.
--enable-link-fixup, --disable-link-fixup
Attempt to fix missing links in source markup data by
heuristically linking similar symbols. This feature is
disabled by default.
--hash-server-memory HASH_SERVER_MEMORY
Specify maximum size of Java heap (in MB) that Svace
hash server may use (default: 2048).
--spotbugs-memory SPOTBUGS_MEMORY
Specify maximum size of Java heap (in MB) that
SpotBugs may use (default: 2048).
--kotlinc-memory KOTLINC_MEMORY
Specify maximum size of Java heap that Svace Kotlin
compiler may use (default: 'auto'). The value of this
option may be an integer, an integer percentage (in
the range from '1%' to '1000%'), or 'auto'. If set to
an integer, Svace will use the specified size (in MB)
for all compilations. Otherwise, Svace will attempt to
mirror the memory usage profile of the intercepted
build by detecting the maximum heap size used by each
compilation and scaling it by the specified percentage
(or an internal factor if 'auto'). If, for some
compilation, the heap size detection fails, Svace
won't set the maximum heap size for its compiler when
processing this compilation (so a JVM default will be
used instead).
--captured-nothing-status STATUS
If the build command completed successfully and no
serious Svace failures were detected, but no
compilations were successfully captured, exit with the
specified status (255 by default).
--low-ready-units-ratio PERCENTAGE
If the ratio of ready units to all units (as shown
with --verbose) is less than the specified integer
percentage for any programming language with at least
one intercepted compilation, print a warning and exit
with the status specified with --low-ready-units-
status option (but still generate all data necessary
for analysis). This option doesn't apply if no units
were captured at all (see --captured-nothing-status).
The default value is 0 (i.e. no capture ratio is
considered low).
--low-ready-units-status STATUS
Set the exit status for --low-ready-units-ratio option
(254 by default).
-v, --verbose Report more detailed statistics after build.
experimental options:
-t TARGET, --target TARGET
Specify the target platform that Clang should use for
bitcode generation. Valid values are: auto, arm,
aarch64, x86, x64, hexagon, mips, mipsel, mips64,
mips64el, powerpc, powerpc64, powerpc64le, riscv32,
riscv64, sparc, sparc64. Values other than 'auto'
override automatic target detection. This option is
intended for debugging only.
--disable-bitcode-gen
Don't run Clang to generate bitcode files. Note that
disabling bitcode generation makes it impossible to
run Svace checkers on the resulting build object. It
is useful if you only want to get results from tools
run at the build time (e.g., warnings from CSA or
metrics). Turning on this option disables default
C/C++ metrics generation.
--disable-pch-gen Don't create an internal precompiled header (PCH) when
creation of a PCH by a supported compiler is detected.
Internal PCHs are used by Svace to speed up bitcode
generation for subsequent compilations, but might lead
to incorrect bitcode in some corner cases. In
particular, if an intercepted compiler creates a PCH
but rejects it in subsequent compilations (for
example, due to macro or option mismatch), falling
back to usage of normal headers, Svace might still use
an unsuitable internal PCH due to different mismatch
detection rules in Clang. PCH generation is currently
supported only for Clang and (partially) GCC.
--enable-dxr-collect Run a helper process that aims to reduce load on the
filesystem when building source reference index is
enabled (see --enable-dxr).
--enable-ptrace, --disable-ptrace
Use ptrace-based interception for statically-linked
executables. By default, Svace uses LD_PRELOAD-based
interception, which works only for dynamically linked
executables. But sometimes compiler toolchains are
statically linked, or there are other statically
linked processes that break LD_PRELOAD-based
interception (for example, by removing LD_PRELOAD
environment variable). This option enables a hybrid
mode: Svace will start in LD_PRELOAD mode, but will
switch to ptrace-based mode each time it detects a
launch of a statically linked executable, and will
switch back if that executable runs a dynamically
linked executable.
--enable-ptrace-all Use ptrace-based interception for all executables and
disable LD_PRELOAD-based interception. See --enable-
ptrace for more details.
--prepend-preload-lib, --append-preload-lib
Usually, the Svace library for function call
interception should be appended to LD_PRELOAD
environment variable, ensuring that Svace
modifications to the execution context are not
overridden by other LD_PRELOAD-libraries. However, if
such libraries modify arguments of created processes
in a way that makes them unrecognizable to the Svace
code that detects "interesting" processes, the Svace
library should be prepended instead, ensuring that it
sees the same arguments that were passed by the
application. By default, the Svace library is
prepended if Scratchbox environment is detected, and
is appended otherwise.
--clean-ld-env-vars Remove Svace-specific parts of LD_* environment
variables before the application's main() entry point
is called. This may help if the application complains
about them or is confused by unexpected values. This
option is meaningful only in LD_PRELOAD mode.
--disable-path-sens-csa
Disable all path sensitive checkers of Clang Static
Analyzer.
--disable-comp Disable all compiler addons
--disable-misc Disable all non-compiler addons
--clang-opts CLANG_OPTS
Add specified options to the Clang and Clang Static
Analyzer run by Svace. The options should be separated
by ';'.
--spotbugs-opts SPOTBUGS_OPTS
Add specified options to the SpotBugs run by Svace.
The options should be separated by ';'.
--enable-jar-decompilation, --disable-jar-decompilation
Enable or disable decompilation of used JAR libraries.
By default, it is disabled.
--base-dir CSHARP_BASE_DIR
Base directory to use relative warning paths in C#
source code analysis.
--enable-csa-stat Generate '*.json.times' files containing the running
time of CSA checkers (in ms) in the build directory.
--keep-intermediate-source
If --enable-csa-deps is specified, do not remove
partially preprocessed '.source' files from the build
directory after archiving them. This option is
intended for easier debugging.
--hash-ar-content Calculate archive hash based only on names of files in
the archive, excluding timestamp, UID, GID and
content. Use in case of undeterministic mode of ar or
ranlib and in case of prebuilt archives with prebuilt
object files (that may differ because of another
platform etc.). DO NOT USE in case of rebuilding the
same archive more than once in the same build (e.g.
debug and release mode).
--preserve-build-order
Store compilation data in the same order as it's
received by Svace hash server. If the build order is
deterministic, but the build data is different between
runs, this may be useful to ensure that the data is
always processed in the build order by build object
consumers. By default, the build order is not
preserved, and build artifacts are usually sorted by
content hash before storage.
--disable-hash-server
Disable Svace hash server, making it impossible to use
the build data on the analysis phase. This option is
for advanced users only.
--enable-huge-source-workaround
Clang can't handle translation units larger than 2 GB
after preprocessing because it uses signed 32-bit
integer type internally for identifying source
locations. Sometimes such huge TUs occur in practice
because the same autogenerated header file is included
multiple times without guards. In this case source
locations are allocated for each inclusion, which may
cause an overflow resulting in a hang or crash. This
option makes it so source locations are allocated only
once per header file, which may help avoid the
overflow in this case. This option is incompatible
with '--enable-csa-deps'.
--enable-mount-ns-workaround
If the build process enters a mount namespace and then
creates new mount points under non-shared mount
points, those new mount points are not visibile in the
parent mount namespace, preventing top-level Svace
processes from accessing affected paths. This option
enables a workaround. Currently, it works only if the
build process doesn't also enter a PID namespace.
--chroot-interception-failure-mode {fail,warn,ignore}
Specify how to react if Svace fails to set up
interception upon encountering a transition into a
chroot jail. If set to 'fail', terminate the affected
process immediately and report a fatal error.
Otherwise, disable interception for the affected
process and its descendants and, if set to 'warn',
also report a warning when 'svace build' completes.
This may be useful in cases when chroot() is used for
purposes other than running compilations in a chroot
jail, making interception failure irrelevant. The
default value is 'fail'.
--go-interception-mode {go-build,compile}
Specify Go interception mode. Supported modes are: go-
build, compile. The default is 'go-build' mode. In
this mode only compilations performed with 'go build'
are supported. The (experimental) 'compile' mode is
build-system-agnostic, so it supports build systems
which don't use 'go build' too (e.g. Bazel). The
'compile' mode implies --enable-ptrace. See --enable-
ptrace for more details.
--enable-goa, --disable-goa
Enable or disable Go AST analysis during svace build.
--enable-go-ir-gen, --disable-go-ir-gen
Enable or disable generation of Go IR used for
analysis by Svace. This option is for advanced users
only.
--enable-go-vendor-gen, --disable-go-vendor-gen
Enable or disable generation of Go IR for dependencies
from vendor/modules.txt used for analysis by Svace.
deprecated options (may be removed in a future release):
--clear-bitcode-dir An alias for --clear-build-dir.
Environment variables:
SVACE_DIR Overrides the default value for --svace-dir
SVACE_ENABLE_CSA_STAT
Overrides the default value for --enable-csa-stat.
Must be set to 'yes' or 'no'.
Usage: svace config [OPTIONS] [<key> [<value>]]
svace config [OPTIONS] --unset <key>
Manage configuration settings. In the first variant (without --unset), if
only <key> is specified, get current value assigned to that key (--parents
must be specified to take system-wide and user-specific configuration into
account). If both <key> and <value> are specified, set <key> to <value>. For
boolean keys, possible values are 'true' and 'false'. Some keys that control
internal (development) settings are 'hidden' and don't show by default, but
can be listed with --show-hidden.
Common config options (shared by 'warning' and 'config' tools):
--global : Work with user-specific configuration
~/.svace/<config-file>, rather than the local
configuration $SVACE_DIR/<config-file>
--system : Work with system-wide configuration
/path/to/distro/config/<config-file>, rather than the
local configuration $SVACE_DIR/<config-file>
-f, --file FILE : Use specified config file instead of the one implied by
options --global, --system, or current svace dir.
If FILE is '-', read from stdin and write to stdout
(implies --quiet).
--unset : Remove the entry for specified key from config file (so
that the default value or value specified in parent
config files will be used instead).
--unset-all : Clear the config file.
-l, --list : List the entries specified in config file(s).
-a, --all : List entries for all keys, including those not specified
in config files. Don't list hidden keys, unless
--show-hidden is specified. Implies --parents.
--show-hidden : When listing keys with --all or --list, show hidden keys
as well.
--json : Output in json format.
-i, --info : For each listed key, print a short description.
-p, --parents : When reading from config file, take into account the
settings specified in parent files. By default, apply
system-wide, user-specific and then Svace directory
or explicitly specified settings to form the list of
modified settings and their values.
If --global is specified, apply only system-wide and
user-specific settings; if --system is specified,
apply only system-wide settings.
--svace-dir PATH : Specify a Svace directory, overriding current directory
and $SVACE_DIR environment variable.
-q, --quiet : Only print error messages (to stderr) and specifically
requested output (to stdout).
--get-val : Only print the value of specified key (without 'key = ').
--plugin-dir : Add directory to search svace-api plugins in.
Use this option multiple times to add multiple directories.
Usage: svace warning [OPTIONS] [<warn-type> [<state>]]
svace warning [OPTIONS] --unset <warn-type>
Manage warning settings (which warning types are enabled/disabled). In the
first variant (without --unset), if only <warn-type> (key) is specified, get
current state (value) of that warning type (--parents must be specified to
take system-wide and user-specific configuration into account). If both
<warn-type> and <state> are specified, set <warn-type> to <state>. Possible
values for <state> are 'true', 'false' and 'hidden' (disables a warning type
and hides it from output of '--all').
Special warning name 'all' can be used to enable/disable all warning types
at once, except for warning types hidden by default.
Use special warning name 'hidden' to enable/disable warning types hidden by
default.
Special warning names that start with 'CWE' can be used to show all warnings
corresponding to given CWE identifier.
Common config options (shared by 'warning' and 'config' tools):
--global : Work with user-specific configuration
~/.svace/<config-file>, rather than the local
configuration $SVACE_DIR/<config-file>
--system : Work with system-wide configuration
/path/to/distro/config/<config-file>, rather than the
local configuration $SVACE_DIR/<config-file>
-f, --file FILE : Use specified config file instead of the one implied by
options --global, --system, or current svace dir.
If FILE is '-', read from stdin and write to stdout
(implies --quiet).
--unset : Remove the entry for specified key from config file (so
that the default value or value specified in parent
config files will be used instead).
--unset-all : Clear the config file.
-l, --list : List the entries specified in config file(s).
-a, --all : List entries for all keys, including those not specified
in config files. Don't list hidden keys, unless
--show-hidden is specified. Implies --parents.
--show-hidden : When listing keys with --all or --list, show hidden keys
as well.
--json : Output in json format.
-i, --info : For each listed key, print a short description.
-p, --parents : When reading from config file, take into account the
settings specified in parent files. By default, apply
system-wide, user-specific and then Svace directory
or explicitly specified settings to form the list of
modified settings and their values.
If --global is specified, apply only system-wide and
user-specific settings; if --system is specified,
apply only system-wide settings.
--svace-dir PATH : Specify a Svace directory, overriding current directory
and $SVACE_DIR environment variable.
-q, --quiet : Only print error messages (to stderr) and specifically
requested output (to stdout).
--get-val : Only print the value of specified key (without 'key = ').
--plugin-dir : Add directory to search svace-api plugins in.
Use this option multiple times to add multiple directories.
Warning tool specific options:
--preset NAME : Work with specific preset of options
Warning type properties (displayed when option --info is given;
see User Guide for more details):
Situation : What kind of situation is indicated by the issue.
Possible values:
Quality
Security
CodingStyle
Suppressed
Duplicate
Performance
Portability
Severity : Potential danger represented by the issue, if it's correctly
detected. Possible values:
Critical
Major
Normal
Minor
Undefined
Reliability: How often the issues of this type are detected correctly
(in different projects). Possible values:
VeryHigh
High
Average
Low
VeryLow
Unknown
Language : For which languages warnings of this type are detected.
Possible values:
CXX
JAVA
KOTLIN
SCALA
CSHARP
GO
PYTHON
JAVASCRIPT
VISUALBASIC
PLSQL
NONE
INTERNAL
Detection tools: Where the checker for the issue is implemented.
Possible values:
SvEng
SvaceCppSpecific
UAST
CSA
SpotBugs
Goa
Mypy
Roslyn
SvaceIR_API
Kotlinc
Group : A group which this warning type belongs to.
Examples:
# Get the state of warning type DEREF_AFTER_NULL, taking parent configuration
# into account:
svace warning -p DEREF_AFTER_NULL
# Disable DEREF_AFTER_NULL for all analysis invocations by current user:
svace warning --global DEREF_AFTER_NULL false
# Disable DEREF_AFTER_NULL for java language:
svace warning JAVA.DEREF_AFTER_NULL false
# Disable all warning for language CSHARP:
svace warning CSHARP false
# Disable autofixes for all warnings (but do not change warnings themselfs):
svace warning AUTOFIX false
# List all critical warnings:
svace warning CRITICAL
# List all warnings with autofixes:
svace warning AUTOFIX
# List all warnings of buffer overflow group:
svace warning TAINTED_SECTION
or
svace warning TAINTED
# List all warnings by wildcard (only * is allowed):
svace warning DEREF_*_NULL*
# List several categories:
svace warning DEREF_OF_NULL,DEREF_AFTER_NULL
# Intersect several categories:
svace warning --and CRITICAL,JAVA
# List all warning settings specified for current user, including a short
# description for each warning type:
svace warning -lpi --global
# Dump all information about the configuration that would be used during
# analysis:
svace warning -lap --show-hidden
# Show all warnings corresponding to CWE190:
svace warning CWE190
This is the tool for running analysis. It takes as input the data
produced by the svace build
tool
and produces analysis results files. Command line options are as
follows:
usage: svace analyze [options]
Analyze the selected project directory with Svace.
By default, a project directory based on the current working directory
is selected, and analysis is performed for the last build object produced by
'svace build', which is specified in file <project-dir>/bitcode/build-object.
Use '--svace-dir' to specify a different project directory and '--build'
to specify a different build object.
options:
-h, --help show this help message and exit
--svace-dir DIR Specify a Svace directory, overriding current
directory and SVACE_DIR environment variable.
-b HASH, --build HASH
A build object to be analyzed. By default, the last
build object produced by 'svace build' is used.
-n NAME, --name NAME Associate given name with the analyzed project (used
for naming files with results and some other files).
By default, the name of the project directory is used.
--memory NUM_MB Specify maximum amount of RAM (in MB or in %) that
Svace may use for analysis. More precisely, this
option controls the maximum size of JVM heap. If set
to 'auto' (the default), Svace attempts to detect the
maximum usable JVM heap size at the time of analysis
start and uses the value close to the detected
maximum. If set to a percentage (e.g. '70%'), Svace
uses the specified percentage of total memory in the
analysis environment.
--preset NAME Use the requested warnings preset during the analysis.
--import-csa, --skip-import-csa
Import Clang Static Analyzer results generated at the
build phase. Has no effect if CSA is run at the
analysis phase (see --skip-csa-analysis).
--import-spotbugs, --skip-import-spotbugs
Import analysis results of SpotBugs generated at the
build phase. Has no effect if SpotBugs is run at the
analysis phase (see --skip-spotbugs-analysis).
--enable-language LANG, --disable-language LANG
Enable or disable analysis for the specified
programming language. Valid values are: all, csharp,
cxx, go, kotlin, python, scala, visualbasic. Enabling
a language also enables working with the relevant
tools (e.g., enabling C/C++ turns on analysis with
Clang Static Analyzer). Default values set by this
option are overridden by --import-<tool>, --skip-
import-<tool> and --skip-<lang_or_tool>-analysis
options. By default, analysis of all supported
languages is enabled.
-L LANGS, --languages LANGS
Enable analysis for languages in the specified comma-
separated list and disable it for other languages.
Supported languages are: csharp, cxx, go, kotlin,
python, scala, visualbasic. The special value 'all'
enables all supported languages (which is the
default).
--enable-uast-language UAST_LANG, --disable-uast-language UAST_LANG
Enable or disable UAST analysis for the specified
programming language. Valid values are: all, go,
kotlin, python. See also '--uast-languages'.
--uast-languages UAST_LANGS
Enable UAST analysis for languages in the specified
comma-separated list and disable it for other
languages. Supported languages are: go, kotlin,
python. The special value 'all' enables all supported
languages. If this option is not set, it defaults to
the subset of enabled languages supported by UAST
analysis.
--target TARGET Select the target architecture of C/C++ code to
analyze. If it's set to 'all' (the default), each
target is analyzed in turn independently of other
targets. Valid values are: all, arm, aarch64, x86,
x64, hexagon, mips, mipsel, mips64, mips64el, powerpc,
powerpc64, powerpc64le, riscv32, riscv64, sparc,
sparc64.
--skip-import-goa Don't import analysis results of Goa generated at the
build phase.
--skip-uast-analysis Don't run uast analysis.
--skip-c-analysis Don't analyze C/C++ code with Svace engine. Note that
Clang Static Analyzer is not affected by this option.
--skip-sveng-analysis
Don't run Svace engine analysis.
--skip-csa-analysis Don't analyze C/C++ code with Clang Static Analyzer.
You may still see CSA results if it was enabled at the
build phase and its results were imported.
--skip-spotbugs-analysis
Don't analyze Java code with SpotBugs. You may still
see SpotBugs results if it was enabled at the build
phase and its results were imported.
--csa-opts OPTS Add specified options to the Clang Static Analyzer run
by Svace. The options should be separated by
semicolons (';'). To specify a literal semicolon,
prepend a backslash ('\') to it. To specify a sequence
of literal backslashes before a literal semicolon,
repeat each of the literal backslashes twice.
--set-config KEY=VALUE
Set the value of config option KEY to VALUE for this
analysis run only. Takes precedence over config files.
--set-warn WARN=(true|false)
Enable or disable warning type WARN for this analysis
run only. Takes precedence over config files.
-w FILE, --warnings FILE
Load warning settings (information about
enabled/disabled warning types) from FILE instead of
warn-settings.txt from Svace project directory. Note
that defaults from system and global settings are
still applied as usual.
--ignore-file FILE Load regular expressions from FILE for suppress
warnings by path considering. These adding along with
systems and local ignore files.
--settings FILE Load Svace analyze settings from FILE instead of
settings.txt from Svace project directory. Note that
defaults from system and global settings are still
applied as usual.
-q, --quiet Show error messages only.
-v, --verbose Show detailed information about analysis and its
status.
--log-level LOG_LEVEL
Log level (both console and file). May have values:
quiet, brief, default and verbose.
--console-log-level CONSOLE_LOG_LEVEL
Console log level. May have values: quiet, brief,
default and verbose.
--file-log-level FILE_LOG_LEVEL
Log level for files. May have values: quiet, brief,
default and verbose. File-logs with upper level will
be disabled. For example, if file log has default
level then it won't be created for quite and brief
levels.
--version Show Svace engine version information and exit. If
used together with '--quiet', only version number in
X.Y.Z format is printed. The Svace engine version is
normally the same as the version of the Svace
distribution as shown by 'svace --version'.
experimental options:
-t HASH, --task HASH A task object to be analyzed.
--raw-files Analyze files with intermediate representation in the
build directory ($SVACE_DIR/bitcode by default)
instead of analyzing a build object. This option may
be useful for debugging, but normally shouldn't be
used.
--build-call-graph-only
In this mode Svace will make call graph for analyze
project. Any analysis will be skipped.
--with-cache Use cache for analysis intermediate results. Enabling
cache may increased analysis speed of modified
project. Require additional disk space.
--collect-summary During analysis summary for functions will be stored.
They may be using if parameter '--use-summary' is
specified. Require additional disk space.
--use-summary Analysis will be run in special mode: if function body
can not be found then function summary will be taken
from cache. For cache filling parameter '--collect-
summary' is requiring.
--analyze-storage-dir SVACE_ANALYZE_STORAGE_DIR
Specify a folder for storing analysis data that are
shared between analysis. Analysis with cache, fast
analysis and incremental analyses use this folder. By
default analysis uses folder $SVACE_DIR/analyze-
storage.
--build-dir DIR Specify a build directory produced by 'svace build' to
use for analysis. The default is $SVACE_DIR/bitcode.
Valid only together with --raw-files.
-s DIR, --source DIR Search for source code in DIR. Project directory is
used by default (as specified, even if the actual
Svace directory is its subdirectory .svace-dir). Valid
only together with --raw-files.
-o DIR, --output DIR Save analysis results in DIR. By default, the results
are saved in $SVACE_DIR/analyze-res. The analysis
result files are $PRJ_NAME.svres and $PRJ_NAME.txt,
where $PRJ_NAME is the name of the analyzed project.
Valid only together with --raw-files.
--fix Run autofix during analysis.
--autofix-backend {svace}
Autofix backend to use. The default is 'svace'.
--emit-autofix-input Emit autofix input as a separate file (for debug
purposes).
--analyze-csharp-solutions-separately
Analyze all captured C# solutions separately.
--plugin-dir DIR Add directory to search svace-api plugins in. Use this
option multiple times to add multiple directories.
This tool manages and starts Svace in server mode listening for remote analysis requests or providing web-interface for integrated analysis results history server.
usage: svace server [--server-dir DIR] <subcommand> [args]
options:
--server-dir DIR Specify a Svace server directory. By default, the current
directory is used.
--help Show this help message and exit.
subcommands:
subcommand
init Create a Svace server directory.
start Start the history access server.
single-start Start the history access server in single-project mode.
reboot Reboot Svace remote server.
admin Manage a Svace server.
show-api-docs Display documentation for Svace history API.
Main command line tools are listed in the Command line tools chapter. This section contains other command line tools that may be occasionally useful.
Command server-config
is similar to the config
command and is used to
setup configuration parameters for starting a remote analysis server.
svace history list [--svace-dir <project-dir>] [--branch <branch>]
[--class <warning type>] [--status <warning status>]
svace history update [--svace-dir <project-dir>] [--branch <branch>]
--warning <warning ID> [--user <user name>]
[-status <warning status>] [--comment <comment>]
svace history import [--svace-dir <project-dir>] [--branch <branch>]
(--task <task ID> | --build <build ID>)
--results <results ID> [--name <snapshot name>]
[--user <user name>]
svace history branch (list |
clone --target <branch name> [--source <branch name>] |
delete --branch <branch name> [--force] |
sync <branch name> <branch name>)
[--svace-dir <project-dir>] [--branch <branch>]
svace history merge-svres svres-id...
svace history split-build build-id
Interact with a warning database. Allows to import new analysis results,
list analysis results and modify warning states from the command line.
Common options:
--svace-dir PATH : Svace project directory to work with.
If not set, current working directory will be used.
--branch NAME : History branch to work with.
If not set, default ('master') branch will be used.
Operations and their options:
list : Show list of actual warnings in a history branch.
These warnings can be filtered.
--class NAME : Display only warnings of selected class.
Can be used several times, showing multiple warning
classes. If not set, all warnings are shown.
--status STATUS : Display only warnings having selected status.
Can be used several times, showing warnings of
multiple statuses. If not set, all warnings are shown.
--format FORMAT : Format output for each warning according to
a format string. Supported format specifiers:
%h: Warning ID (hash)
%c: Warning class
%s: Warning status
%m: Warning message
%l: Warning location
%u: User comment
%A: User-defined attributes
--current-snapshot VAL : If VAL is false, display only warnings that
didn't appear on the current (last) snapshot
(but were detected on one of the old snapshots).
If it's true, display only warnings detected on the
current snapshot (this is the default).
--previous-snapshot VAL : If VAL is false, display only warnings that
didn't appear on the previous snapshot
(but were detected either on one of the old snapshots,
or on the current snapshot).
If it's true, display only warnings detected on the
previous snapshot.
update : Modify a warning in the history storage.
--user NAME : Username related to this warning modification.
If not set, current user name will be used.
--warning ID : The 32-symbol ID for warning which must be updated.
--status STATUS : New status for the selected warning.
May be one of 'bug', 'scope', 'goal', 'unclear' or
'default' (case-insensitive).
Other statuses are interpreted as 'default'.
--comment STRING : New comment string for the selected warning.
import : Import analysis results into history storage.
When neither build/task nor results object are specified,
import the last analysis results.
--user NAME : Username related to this warning modification.
If not set, current user name will be used.
--results ID : The 32-symbol ID for the imported analysis results.
--build ID : The 32-symbol ID for the build which was analyzed
to produce imported analysis results.
--task ID : The 32-symbol ID for the task which was analyzed
to produce imported analysis results.
--name NAME : Snapshot name related to these analysis results.
import-data : Internal tool mostly for debugging.
Allows to import analysis results and source code snapshots,
into svace storage system.
--type : 'svres', 'build', 'source-build' or 'empty-build'.
'svres' allows you to import analysis results in svres format,
'empty-build' produces build-object with no data in it,
'build' imports build object previously exported using
Svace 'export-build' command, 'source-build' allows you to import
some directory as a source code snapshot. The resulting build-object
will obviously contain no bitcode, so it won't be analyzable;
however it can be useful for importing data into history system.
--path : path to file (if importing 'svres') or directory (if importing
a source code snapshot or build object) you want to import.
--orig-path : used for 'source-build' only, and is optional. Indicates
the path where the imported source tree should be available at.
E.g.: you have file /home/user/project/file.c, you use:
--path /home/user/project/, --orig-path /var/build
the file will be imported and used by history system
like it was located at /var/build/file.c
If you are under *nix OS and you want to emulate build-object
generated by windows system, change orig-path this way:
'C:\Some\Dir\' => '/c_/Some/Dir/', and also check next flag.
--with-dxr : used for 'source-build' only, and is optional. Allows to provide
a directory with source code markup in csv format. Svace requires
file names in the same format svace generates dxr files itself. Also
Svace requires empty .csv.stamp files along with .csv data files.
--merge-metrics : used for 'build' only, and is optional. If set to 'true' merges
imported build object metrics with the current ones.
--move : used for 'build' only, and is optional. If set to 'true' moves
imported files instead of copying them. This allows to speed up
the import but the export folder will be cleared after this.
branch : Manage branches. Must be followed by one of
the following sub-commands:
list : List all branches in the project directory
(including remote branches).
clone : Create a new branch and optionally makes its state
equal to another branch.
--target NAME : Branch name to be created.
--source NAME : Branch to be cloned.
If not set, created branch will have no content.
delete : Delete specified branch.
--branch NAME : Name of the branch to be deleted.
force : Allows to delete 'master' branch.
(svace history branch delete force --branch master)
sync NAME NAME : Sync warning states from two given branches.
merge-svres : Takes multiple analysis results and merges them all
in one large analysis results.
split-build : Takes build object and splits it into multiple build objects
where each resulting build object has only some target-related data.
Examples:
# List warnings of class MEMORY_LEAK with Goal and Default statuses:
svace history list --class MEMORY_LEAK --status Default --status Goal
# List warnings of class MEMORY_LEAK that disappeared on the last snapshot:
svace history list --class MEMORY_LEAK --current-snapshot false \
--previous-snapshot true
# Change status of a selected warning:
svace history update --user developer1 --warning 123..132 --status Bug \
--comment 'Array index isn't checked before access'
# Import existing analysis results into history database:
svace history import --user developer1 --build 123..231 --results 323..321'
# Clone existing branch into a new one:
svace history branch clone --source master --target backports_2.0
svres2json [options] FILENAME
svres2json is a converter from Svace results format to JSON format and vice versa.
Options:
FILENAME : path to the file to be converted
-h, -help, --help : display this message
-o, --out OUT_FILENAME : path to the file where converted results will be stored. If this option
is not used, stdout will be used for this purpose
--compact : dump traces in compact format
-b, --backward : turn the backward mode on. In this mode an input file must be in JSON
format
svres2sarif [options] FILENAME
svres2json is a converter from Svace results format to Sarif 2.1 format.
Options:
FILENAME : path to the file to be converted
-h, -help, --help : display this message
-o, --out OUT_FILENAME : path to the file where converted results will be stored. If this option
is not used, stdout will be used for this purpose
usage: svace spec <subcommand> [args]
Manages specifications for third-party library functions,including user-
provided.
options:
--help Show this help message and exit.
subcommands:
subcommand
add Compile specifications from source files and add them to the
specified location.
list Show existing specifications in the specified location.
remove Remove existing specifications from the specified location.
show Show the source file of a specification.
This section demonstrates the process of using the analyzer with an example (analysis of proftpd-1.3.3).
Let’s assume that Svace is located in ~/svace/
, on an
Ubuntu system and all proftpd build dependency packages are already
installed.
A copy of the proftpd-1.3.3 distributive can be downloaded from the
Internet at ftp://ftp.proftpd.org/distrib/source/proftpd-1.3.3.tar.bz2.
Extract the archive in ~/projects/proftpd-1.3.3/
.
The package can be built using configure/make. To obtain LLVM bitcode
files for analysis by svace, svace project folder must be initialized
first and then building commands need to be wrapped in svace build
command as follows:
cd ~/projects/proftpd-1.3.3
./configure
~/svace/bin/svace init
~/svace/bin/svace build make
If the operation completes successfully, a text like the following
will be displayed by build
:
Now Svace will preprocess captured data.
Processing source code markup data...
[**********************************************************************] 100%
Assembled build object: [BUILD]6715d4e4c908f8e62a54f174796282c48ce2d3a5
83 C/C++ units are ready.
As a result, the folder ~/projects/proftpd-1.3.3/.svace-dir/bitcode/ should now contain files with intermediate representation, for example the file
~/projects/proftpd-1.3.3/.svace-dir/bitcode/auth.e7d9dbb866bdbd6c2456bf8f3212d727.bc
Further analysis will only require files within Svace project folder (~/projects/proftpd-1.3.3/.svace-dir), so the other byproducts of the building process can be safely removed.
Assuming that folder ~/projects/proftpd-1.3.3 contains the svace project folder with intermediate representation files obtained on the previous step, analysis can be launched with the following command:
~/svace/bin/svace analyze --svace-dir ~/projects/proftpd-1.3.3
As a result (it should take a couple of minutes), two files will be created:
~/projects/proftpd-1.3.3/.svace-dir/analyze-res/proftpd-1.3.3.txt
~/projects/proftpd-1.3.3/.svace-dir/analyze-res/proftpd-1.3.3.svres
First file should contain (for example) the following warning of type DEREF_OF_NULL:
* DEREF_OF_NULL: Pointer 'symhold' that can have only NULL value (checked at line 152), is dereferenced at line 153.
dereference at mod_ls.c:153
null at mod_ls.c:152
source line: "*symhold = show_symlinks_hold;"
Corresponding source file fragment (from ~/projects/proftpd-1.3.3/modules/mod_ls.c:152):
if (!symhold)
*symhold = show_symlinks_hold;
For example, consider the warning NULL_AFTER_DEREF/proftpd-1.3.3/auth.c:1227. Below is the corresponding excerpt from the plain text file ~/projects/proftpd-1.3.3/.svace-dir/analyze-res/proftpd-1.3.3.txt:
* NULL_AFTER_DEREF: Pointer 'login_name' which was dereferenced at line 1222 is compared to NULL value at line 1227.
null check at auth.c:1227
dereference at auth.c:1222
source line: "if ((!login_name || !anon_c) &&"
The relevant part of the source code is as follows:
1222: if (*login_name &&
&&
auth_alias_only *auth_alias_only == TRUE)
*login_name = NULL;
1227: if ((!login_name || !anon_c) &&
) {
anon_name*anon_name = NULL;
}
On line 1222, variable login_name
is dereferenced. If
login_name
is NULL
, this will be a null
pointer dereference. On line 1227, this pointer is compared to
NULL
, which suggests that it indeed can be NULL on line
1222. If it can’t, then the check on line 1227 is redundant.
Some warning reports are less useful than others, and because of that
Svace may suppress some warnings based on different conditions.
Information about suppressed warnings is added to the
.svace-dir/analyze-res/suppress
subfolder in the project
folder after the analysis.
Analysis is performed on the whole code used during the build process
including third-party dependencies and system components that aren’t
actually part of the project. To suppress warning reports for these
files add the regular expressions for these files or whole directories
to the .svace-dir/svace.ignore
file in the project folder.
Patterns for known system components, such as standard compiler header
files, are added by default to this file during project folder creation.
Note that these patterns control only the suppression of the warning
reports for the corresponding files; their content is still analyzed to
perform interprocedural analysis of the code that calls functions from
them.
It is sometimes useful to ignore certain warnings for a specific line
or block of code without affecting the entire analysis process. One
popular way to do this is to mark the related source code lines with
comments in a predefined format. The most common example is a simple
NOLINT
comment. But many static analysis and related tools
use their own formats. Besides NOLINT
Svace supports
several popular warning suppression comment formats out of the box:
NOSONAR
from SonarQube, a tool for continuous inspection of
code quality; svacer_review
from Svacer, a standalone
analysis history management server; noqa
used to ignore
PEP8 warnings in Python. All warning reports issued on the source lines
with any of these comments can be suppressed if this feature is
enabled.
Examples of supported comments:
//NOSONAR
// NOLINT
// NOLINTNEXTLINE
//nolint
//nolint:unused,deadcode
//svacer_review: -
//svacer_review: -INTEGER_OVERFLOW
//svacer_review: -INTEGER_OVERFLOW|-BUFFER_OVERFLOW
# noqa: E731,E123
#noqa
Svace parses arguments of comments in the Svacer format
svacer_review
to suppress only warning types listed there.
Arguments of all other types are ignored and such comments suppress
reports of any warning types. E.g. the
nolint:unused,deadcode
comment is interpreted the same way
as nolint
and the noqa: E731,E123
comment is
interpreted the same way as noqa
.
By default this functionality is disabled and can be enabled using
the SUPPRESS_BY_COMMENT
configuration option. By default warning
suppression comments in all formats are processed but specific formats
may be disabled separately using the corresponding configuration
option:
SUPPRESS_BY_COMMENT.SONAR
- for
NOSONAR
;SUPPRESS_BY_COMMENT.LINT
- for nolint
and
nolintnextline
;SUPPRESS_BY_COMMENT.SVACER
- for
svacer_review
;SUPPRESS_BY_COMMENT.NOQA
- for noqa
;Additionally, it is possible to process warning suppression in a
custom format by assigning a SUPPRESS_BY_COMMENT.TEMPLATE
configuration option with a suppression format regular expression. If
the comment text in the source code matches this regular expression all
warning reports for the corresponding source code line are
suppressed.
When Svace is used regularly to analyze the same project it is possible to cache intermediate analysis results for the unchanged code parts to save time and resources for subsequent analysis runs. This takes some disk space to store the cache but can reduce analysis time significantly.
To use this feature build the project with svace build
as usual but add the svace analyze
--with-cache
parameter (or enable the
PERSISTENT_MODE
configuration
option). When this parameter is present the analyzer stores into the
project folder the intermediate results and marks their dependencies at
the end of the analysis. To use the cache during the next analysis use
the same parameter --with-cache
in the
svace analyze
command. Svace then checks the dependencies
of the intermediate source code representation files in the new build
object being analyzed, and if they match those from the cache, retrieves
the analysis results from the cache and uses them instead of running it
again. To achieve the best cache utilization it’s advised to reproduce
as many build environment details as possible. Especially, the folder
where the project is built may affect the resulting intermediate files
and it is best to rebuild the project for the next analysis in the same
folder.
By default the cache is stored in the project folder in the
.svace-dir/analyze-storage
subfolder and is expanded with
new entries after each analysis with cache. Its initial size after the
first analysis is comparable to the size of the intermediate source code
representation files of a single build object and grows with each
subsequent analysis with cache of a new build object depending on the
volume of changes in the analyzed source code. When Svace is integrated
into the development process in such a way that a new project folder is
created for each analysis, it is necessary to designate an external
cache storage folder using a svace analyze
--analyze-storage-dir <SVACE_ANALYZE_STORAGE_DIR>
command line parameter, where
<SVACE_ANALYZE_STORAGE_DIR>
is a path to the folder
where the analysis cache should be stored. This option can also be used
to share the cache between different projects that contain common source
code files. If the cache size becomes too large it can be purged by
removing the cache storage folder entirely. It will be recreated during
the next analysis with cache. The cache folder should also be removed
when upgrading the analyzer to the new version.
The same cache is used for all analysis engines but may provide
different impact for each of them for a particular project. There are
several configuration options that control
specific analysis engines cache usage. Using these options the cache can
be disabled selectively for some of them:
PERSISTENT_MODE.CSA
for Clang Static Analyzer,
PERSISTENT_MODE.SPOTBUGS
for SpotBugs,
PERSISTENT_MODE.MODULE_PARSING
and
PERSISTENT_MODE.FUNC_ANALYSIS
for Svace.
Most projects depend in some way on third-party code, e.g. libraries.
Interprocedural analysis may be much more precise when the analyzer has
knowledge of what the libraries’ code does but most often they are built
separately and it’s not available to the analysis. To overcome this
problem it is possible to analyze part of the code, particularly
third-party libraries, and store the intermediate analysis results in
the form of function summaries to be reused during the analysis of the
other parts of the project. Such summaries contain information about the
notable side effects from the calls of the respective functions. In
order to store such summaries build the library(-ies) with svace build
as usual and add the svace analyze
--collect-summary
parameter when running its analysis. The
summaries are stored in the project folder and can be used for
subsequent analysis of the project dependent on the library by using a
svace analyze
--use-summary
parameter when running its analysis.
In many situations it’s more convenient to run svace build
and svace analyze
steps independently
on different systems. For example, a developer’s PC is able to build a
large project but lacks the required resources to perform the analysis.
To support such scenarios in a simpler way it is possible to designate
one computer as a remote analysis server to perform the analyses and
provide it with analysis tasks over the network from other computers.
Remote analysis server supports multiple projects with own analysis
configurations and user authorization.
The server uses a special server folder that contains its
configuration and data. Note that it is different from the Svace project folder
.svace-dir
. A server folder can be initialized by running a
svace server init
command in an empty folder. The server is
initialized with default settings without any users or projects set up
and they have to be added before its usage. To simplify server
initialization special svace server admin create-admin
or
svace server admin create-defaults
commands can be used.
The former command creates an admin
user with
admin
role allowing to manage the whole server. And the
latter command additionally adds a regular user main
with a
role main
that allows to view and modify any project on the
server, and a project group main
with a
default
project. Managing projects, project groups, users,
roles and permissions is described below.
The following commands sequence demonstrates creating a remote analysis server folder and its basic configuration suitable for performing remote analysis:
mkdir server-dir
cd server-dir
svace server init
svace server admin create-defaults
Above commands produce output similar to the following, showing the initialization process:
Creating admin...
Enter user's password or press enter to use autogenerated password (without quotes): "!?qVt_1J'R%X"
Enter password:
Nothing entered: will use auto-generated password from above.
Creating role 'main'...
[INFO] Role created.
Creating user 'main'...
Enter user's password or press enter to use autogenerated password (without quotes): "jv8HC~hm4nf$"
Enter password:
Nothing entered: will use auto-generated password from above.
[INFO] User created.
Adding role 'main' to user 'main'...
[INFO] Role main added for user main
Creating project group 'main'...
[INFO] Project group created.
Creating project 'default'...
[INFO] No database for svace project directory; initializing new one
[INFO] Project directory initialized @server-dir/dirs/default
[INFO] Project created.
Adding modify permissions for role 'main'...
[INFO] Permission modify on SERVER granted for role main
The following server settings can be changed using a svace server-config
command:
SERVER_PORT
- a port used by the server to listen for
remote analysis clients connections (55135 by default).WEB_SERVER_PORT
- a port used by the server to listen
for user connections using a web-browser (8060 by default).SIMULTANEOUS_ANALYSIS_LIMIT
- a number of simultaneous
analyses the server is allowed to run. By default 3 analyses are allowed
to run in parallel and each analysis task takes one slot unless it’s
assigned a specific weight with a svace remote analyze
--weight
parameter. If not enough simultaneous analysis
slots are available the analysis task is delayed until previous analysis
tasks finish.ALLOW_SIMULTANEOUS_ANALYSIS_PER_PATH
– whether parallel
analyses are allowed using the same project folder. If it is set to
‘false’ then only one simultaneous remote analysis per path is allowed.
The ‘true’ value of this option is ignored for server single-start
mode.REJECT_CLIENT_CONFIGS
- when this server configuration
setting is enabled client analysis settings including enabled warnings
configuration don’t have an effect on the remote analysis. By default
the client settings are applied. By enabling this configuration setting
it is possible to enforce remote analysis settings to those predefined
on the server by its administrator.JOIN_CLIENT_CONFIGS
- when this server configuration
setting is enabled client enabled warnings configuration are merged with
the server configuration so that a warning
type is enabled if it is enabled either on the server or on the
client. By default no configuration merging is made. By enabling this
configuration setting it is possible to supplement required remote
analysis settings with the optional client settings.DELETE_ANALYZE_DIR_ALL
- when this server configuration
setting is enabled the results analysis folder is removed after remote
analysis has been finished to preserve server disk space. By default it
is not removed.DELETE_ANALYZE_DIR_SUCCESS
- when this server
configuration setting is enabled the results analysis folder is removed
after remote analysis has been finished successfully. This allows to
preserve server disk space but still diagnose the causes of analysis
errors when remote analysis fails. By default the analysis results
folder is not removed in all cases.CREATE_PROJECT_IF_NOT_EXIST
- when this server
configuration setting is enabled and there is no project with the
requested name it is created. By default an error about wrong project
name is thrown in this case.The following client configuration option affects remote analysis:
ZIP_ANALYZE_RES
- if enabled Svace compresses whole
analysis results folder contents to transfer it to the client; by
default it is disabled and only analysis results are transferred.If some option should be changed for all projects on server - it can
be done by command svace config
with parameter
--system
or --global
.
For example:
svace config --sytem SUPPRESS_BY_COMMENT true
Server config option REJECT_CLIENT_CONFIGS
should be
enabled (command svace server=config
).
Running remote analysis requires specifying a project on the client.
Each project is represented by a project folder contained inside of the
server folder and can have own analysis settings and caches. Remote analysis project is
created automatically when a remote analysis of a previously
non-existing project is requested. In this case it is assigned to the
main
project group created during the server setup if the
user starting the remote analysis has permissions to do so. To
control the group which it belongs to the project can be created
beforehand using a
svace server admin create --project PROJECT --group GROUP
command during server configuration, where PROJECT
represents the name of the project being created and GROUP
- the name of the group which it should belong to. The following server
configuration command
svace server admin create --project-group GROUP
creates a
new project group with the name GROUP
. It is possible to
register an already existing external project folder to be used for the
project instead of creating it in the server folder. To do this use the
--path
option to specify the location of this project
folder DIR
in the project creation command
svace server admin create --project PROJECT --group GROUP --path DIR
.
To show the list of all projects available on the server with their
project folder locations the
svace server admin list --projects
command can be used. To
show the list of available project groups there is the
svace server admin list --project-groups
command. Command
svace server admin project-group GROUP --list-projects
shows the projects that belong to the specified group
GROUP
. Removing a project PROJECT
is done
using the svace server admin remove --project PROJECT
command. Similarly, removing a project group GROUP
is done
using the svace server admin remove --project-group GROUP
command.
In the following example a new project group with the name
foo
is created followed by creation of a new project
bar
and registering an external project baz
,
both in this group:
svace server admin create --project-group foo
svace server admin create --project bar --group foo
svace server admin create --project baz --group foo --path /home/user/baz/.svace-dir
The remote analysis server works with multiple users using a role-based access control model. Running remote analysis requires user authentication with an account having sufficient privileges for the specified project.
On each remote analysis server, an unlimited number of users can be created, each of whom can have an arbitrary set of roles. Different users can have the same role. Each role describes a set of permissions for accessing individual projects, all projects in certain groups, or all projects on the server. If a role has specific permissions for a group of projects, then all users with that role have those permissions for all projects in that group. If a role has specific permissions for the server, then all users with that role have those permissions for all projects on that server. The final user permissions are determined by the sum of permissions from all the roles held by the user.
There are three types of permissions - read
,
modify
, and admin
. Each subsequent type of
permission includes the properties of the previous one. Currently, the
remote analysis server only checks the modify
permission
for the specified user to run an analysis (thus, modify
or
admin
permission for the project is required). Other types
of permissions are used to organize access to the capabilities of the
built-in history server.
Creating a new user is done using the
svace server admin create --user USER
command, where
USER
is the desired username. When creating a user, you
will need to enter their password (or accept the password suggested by
the server). By default, the user does not have any roles. To change a
user’s password, use the
svace server admin update --user USER
command, where
USER
is the name of an existing user for whom you want to
change the password. You can view a list of existing users using the
svace server admin list --users
command. Deleting a user
account is done with the
svace server admin remove --user USER
command, where
USER
is the name of the existing user you want to
delete.
To create a new role, one should use the command
svace server admin create --role ROLE
, where
ROLE
is the desired role name. By default, the new role
doesn’t have any permissions. To grant to a role a specific permission
for a project, you need to execute the
svace server admin grant-permission PERMISSION --role ROLE --project PROJECT
command. In this command, ROLE
is the name of an existing
role to which you want to grant permission, PERMISSION
is
the corresponding permission (read
, modify
or
admin
), and PROJECT
is the name of the
existing project for which the role is granted the permission.
Similarly, to grant to a role a specific permission for an entire
project group, you should use the
svace server admin grant-permission PERMISSION --role ROLE --project-group GROUP
command, where GROUP
is the name of an existing project
group for which the role is granted the permission and the other
parameters are the same as in the previous command. If you don’t specify
any of the --project
or the --project-group
parameters in the
svace server admin grant-permission PERMISSION --role ROLE
command, then the corresponding permission PERMISSION
will
be granted to the role ROLE
server-wide. To view the
current permissions of a role, you can use the command
svace server admin role ROLE --list-permissions
, where
ROLE
is the name of an existing role for which you want to
see permissions. To remove all permissions granted to a role
ROLE
for a project PROJECT
, you should run the
command
svace server admin revoke-permission --role ROLE --project PROJECT
.
Similarly, to remove all permissions granted to a role ROLE
for a project group GROUP
, you should use the command
svace server admin revoke-permission --role ROLE --project-group GROUP
.
The command
svace server admin revoke-permission --role ROLE
removes
server-wide permissions granted to the role ROLE
. You can
display a list of existing roles on the server using the command
svace server admin list --roles
. To delete a role, use the
command svace server admin remove --role ROLE
, where
ROLE
is the name of the existing role you want to
delete.
To assign a user a new role, you need to execute the command
svace server admin user USER --add-role ROLE
, where
USER
is the name of an existing user you want to assign the
role with the name ROLE
, which should be created
previously. Conversely, to remove the role ROLE
from the
user USER
, you should execute the command
svace server admin user USER --remove-role ROLE
with the
corresponding parameters. The command
svace server admin user USER --list-roles
is used to view
the current roles assigned to the user USER
, and the
svace server admin user USER --list-permissions
command is
used to view the actual permissions that the user has on projects,
project groups or the entire server. The command
svace server admin role ROLE --list-users
is used to view
users who have a specific role, where ROLE
is the name of
the role for which you want to determine the list of users who possess
it.
The example commands below create a new user jenkins
and
allow it to run remote analysis using only the bar
project
using a ci_remote_analysis
role:
svace server admin create --user jenkins
svace server admin create --role ci_remote_analysis
svace server admin user jenkins --add-role ci_remote_analysis
svace server admin grant-permission modify --role ci_remote_analysis --project bar
The server is started with the svace server start
command and can be stopped anytime by killing the server process,
e.g. by pressing Ctrl+C
in the terminal where it is has
been started. The following message is displayed when the server has
been started successfully and is ready to process analysis requests from
the clients:
Remote analysis server started at port 55135
Note: the port can be configured using the SERVER_PORT
server
configuration setting.
While running the server provides the possibility to access the
analyzer user guide text, information about the server status, and the
built-in history server (if the corresponding configuration setting is
enabled) using a web browser. To do this, you need to access the
appropriate port of the running server via the HTTP protocol. For
example, if the server is running on a machine with the IP address
192.168.1.10
, to obtain brief information about the status
of active analyses running remotely, you need to open the page
http://192.168.1.10:8060/status
. The port can be configured
using the WEB_SERVER_PORT
server
configuration setting.
To request remote analysis use the following command on the client,
where SERVER_ADDRESS
represents the address of the running
remote analysis server:
svace remote --host SERVER_ADDRESS analyze
When executing this command the client establishes connection with the remote analysis server and transfers all the data needed for analysis. The server then performs the analysis and returns the results to the client, which places them in the same way as if the analysis has been performed locally.
If the remote analysis server was configured to
use a custom port then it can be specified via the
svace remote --port PORT
option, where PORT
represents that port number. User credentials can be
specified using the --login USER
and
--password PASSWORD
options. If these options aren’t
specified the client requests them from the input stream. The project for the remote analysis is
specified with the --path PROJECT
option. If it is not
present then the name of the current working directory is taken as the
project name.
When the remote analysis is started successfully a message containing an analysis task hash like the following is printed on the client’s output stream:
Starting remote analysis task 'c5466046fe87bbc828de45daf2b1c1d9f33396c4' on remote server.
If necessary, this hash can be used to prematurely terminate the
remote analysis. This can be done by the user who initiated the remote
analysis, as well as by a user with admin
rights on the
project with which this analysis was launched. To do this, use the
following command, where TASK_ID
represents the analysis
task:
svace remote kill TASK_ID
The svace remote kill
command can also be used without
arguments, in which case the latest remote analysis launched by the
client is stopped. Analysis task identifiers of the currently executing
remote analyses can also be found on the remote analysis server status
page (see the last paragraph of the Starting and
stopping remote analysis server chapter).
Remote analysis server can also be started in a quick mode, where
user authentication is not required and analysis is only supported using
a single project folder. To start this mode simply initialize the
project folder using the svace init
command
and execute the svace server single-start
command. To
configure server settings you can use the
svace server-config
command as described in the Remote analysis
server configuration settings chapter.
Along with the remote analysis feature the server can provide a
feature for storing and viewing analysis results. The integrated
analysis results history server has a simple interface and limited
capabilities compared to the standalone Svacer
history server. To enable the integrated history server simply set
the ALLOW_WEB_HISTORY_ACCESS
setting to true
in the remote
analysis server configuration settings. After starting the remote
analysis server the integrated history server will be accessible via the
HTTP protocol through a web browser. For example, if the server is
running on a machine with the IP address 192.168.1.10
, the
integrated analysis results history server can be accessed by opening
the page http://192.168.1.10:8060
in a web browser and
following the “Integrated history web interface” link. The port can be
configured using the WEB_SERVER_PORT
server
configuration setting.
The integrated analysis results history server supports the same user and permission
management and project management
as remote analysis server. To access the server features the user has to
authenticate first. In order to view analysis results for specific
project the user needs at least read
permission for that
project, the modify
permission allows to review the
reported warnings by setting their status (one of
Confirmed
, Won't fix
,
False positive
, Unclear
of the default
Undecided
) and/or add a comment to the warning report.
The authenticated user has only access to those projects, that he has any permissions on. The project to work with, its branch and analysis results snapshot can be selected using the drop-down menus in the upper part of the server web-interface. When the needed analysis results snapshot is selected a list of warning types found in this snapshot is displayed on the left part of the page. The number of found warnings of a specific warning type is shown in parentheses after the corresponding warning type name. Left clicking on an item in this list displays all warnings from the selected analysis results snapshot of the corresponding type below. Clicking on an item in the warnings list makes the selected warning active and shows information about this warning below: warning message, location of the defect, trace and its review status along with added user comments. When a new warning report becomes active as well as when you click on the location links of the defect or elements of the trace in the active warning information panel, the source code of the corresponding location is displayed in the central part of the page. The source code panel supports IDE-like navigation: when you click with the left mouse button on an identifier in the source code (for example, a variable or a function name), all occurrences of that identifier are highlighted. Left-clicking on the highlighted identifier displays a context menu for that identifier. Using this menu, you can navigate to the main declaration or definition of the identifier as well as to the locations where it is used in the analyzed source code.
Warning types classify individual warnings (issues) emitted by Svace. Each warning type specifies the kind of defect being sought, typical situations in which such issues are found, and possible sources of false positives. Some warning types have subtypes, allowing more fine-grained classification of the detected issues.
Each warning type is characterized by several properties, which can
be inspected by running the warning tool
with option --info
. These properties can be helpful when
deciding which warning types to inspect and when interpreting the
results classified under various warning types.
Severity is an estimate for the potential danger represented by issues of the warning type that are detected correctly. Possible values of this property are Critical, Major, Normal, Minor and Undefined.
Reliability is an estimate for how often the issues of a given type are detected correctly. The estimate is based on results obtained on many projects, and actual reliability of results on a given project may significantly deviate from the average. Possible values are VeryHigh, High, Average, Low, VeryLow and Unknown. When there are only a few issues detected for a warning type, it may be worthwhile inspecting even warning types rated Low or VeryLow, but for warning types that are frequently detected, focusing on results of VeryHigh, High and Average reliability is more important.
In addition to normal warnings that indicate a defect, there are warnings with different aims, which are described with this property. Suppressed and Duplicate warning types normally shouldn’t be used, but are included for completeness.
Most Svace checkers are implemented in the main analysis framework, which is indicated by SvaceMain detection tool property of the warning type. A few of the checkers that detect simpler situations are implemented in Clang, and have SvaceClang as their detection tool. Issues imported from SpotBugs are marked as having SpotBugs as the detection tool (and have warning type prefix FB.).
Some warnings have common subtypes, that are indicated by adding a suffix after a dot to the warning type. For example, the MEMORY_LEAK warning type has a suppressed subtype MEMORY_LEAK.MIGHT.
This subtype indicates that the checker knows of ways in which the warning might be false, and of ways in which the warning might be true, but can’t prove it either way. This is a “weaker” warning subtype, and sometimes the results falling under it should be skipped, but sometimes the detected issues turn out to be correct. Since the checker can’t prove that the issues are correct, even under the heuristic assumptions used to find them, reliability of these warning types is generally lower and can’t be significantly improved.
This subtype specifies that the runtime error or memory corruption happens inside a user-written procedure called from the code that causes the problem. In other words, the problem is caused by incorrect (unsafe) invocation of a procedure.
This subtype indicates that memory locations involved in these warning reports have global scope.
This subtype indicates that severity of warning may be less than for major type.
This subtype indicates that a warning is reported may require very strict error policy to analyzed source code.
This subtype indicates that a warning is reported by an extended version of the checker.
This subtype indicates that a warning is reported for an exception path.
This subtype indicates that a warning is related to a value returned from a function.
This subtype indicates that a warning is related to a library function.
This subtype indicates that the issue is reported for a specific loop iteration.
This subtype indicates that the issue is reported for a specific condition branch.
This subtype indicates that a warning is reported in test code.
This subtype has the meaning of .PROC in C# null dereference checkers.
This subtype of null dereference checkers indicates that the value is dereferenced in the same expression where it is created.
This subtype indicates that the issue is related to using a value in a call as an argument when corresponding function parameter has specific annotation.
A null pointer dereference error occurs when a program attempts to
dereference a memory address or access an object reference that has a
null value (NULL
in C, nullptr
in C++,
null
in Java, etc). Such dereferencing is invalid because
the null pointer (null reference) does not point to any valid memory
location (to any valid object). When a null pointer dereference error
occurs in a program, written in a manual memory management language, it
typically results in a segmentation fault or access violation, causing
the program to terminate abruptly. For programs in automated memory
management languages in this case an exception is usually raised that
can lead to unexpected behavior. Here and below a pointer dereference
means literal pointer dereference for languages where it is applicable
and object reference or another corresponding notion for languages
without pointers.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
Visual Basic .NET | Quality | Critical | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Kotlin | Quality | Normal | High | Yes |
Python | Quality | Normal | High | Yes |
C# | Quality | Critical | High | Yes |
This checker finds situations where a pointer is dereferenced, but can only have a null value, and so the operation of dereference can never be run without causing a runtime error. A special case where the null value is explicitly assigned is categorized as DEREF_OF_NULL.CONST.
More generally, checkers in the DEREF_OF_NULL
group find
situations where a dereferenced pointer can be assigned a null value on
one of the possible execution paths.
if (dvfs->get_avail_governor)
return -ENOTSUP;
return dvfs->get_avail_governor(res_name, avail_governor);
In the example above an accidentally inverted condition meant to
return an error status in case the field
dvfs->get_avail_governor
is NULL
but
instead in this case the execution continues and leads to a null pointer
dereference during the call in the return
statement.
fun example(s: String?) {
if (s == null) {
derefAnyway(s)
}
}
fun derefAnyway(s: String?) {
print(s!!.length)
}
fun possibleFix(s: String?) {
print(s?.length)
}
Function example
illustrates the defect: variable
s
of nullable type is compared with null directly and is
dereferenced by derefAnyway
function call.
Function possibleFix
illustrates a possible fix: use
null safe operator ?.
and remove redundant
derefAnyway
call.
The example given is too synthetic, because it’s quite difficult to
create Kotlin code where Svace will report a DEREF_OF_NULL warning, Kotlin compiler will
try to prevent you from writing such code by reporting warnings and
errors. For example, Kotlin compiler will report a compilation error for
exampleOfCompilationError
function below.
fun exampleOfCompilationError(s: String?) {
if (s == null) {
s!!.length // kotlin compiler report a compilation error here
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Go | Quality | Critical | Average | Yes |
Kotlin | Quality | Normal | Average | Yes |
Python | Quality | Normal | Average | Yes |
Related CWEs: CWE476.
This checker is similar to the DEREF_OF_NULL checker but reports cases when a pointer is assigned a null value under a condition and is then dereferenced under another condition which is not mutually exclusive with the first one.
import java.io.File
fun handleCollection(collection: Collection<Any>?) {
for (elem in collection!!) {
// ...
}
}
fun handleCollectionCorrect(collection: Collection<Any>?) {
collection?.forEach { elem ->
// ...
}
}
fun example(f: File) {
val files = f.listFiles()?.asList()
handleCollection(files)
}
fun possibleFix(f: File) {
val files = f.listFiles()?.asList()
handleCollectionCorrect(files)
}
Function example
illustrates the defect:
files
may be null, but it will be dereferenced inside
handleCollection
function call.
Function possibleFix
illustrates a possible fix: use
safe implementation handleCollectionCorrect
to iterate over
collection. Safe call of forEach
is used inside
handleCollectionCorrect
function.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Go | Quality | Critical | High | Yes |
Kotlin | Quality | Normal | High | Yes |
Python | Quality | Normal | High | Yes |
Related CWEs: CWE476.
This checker finds situations where a pointer is dereferenced, but can only have a null value because it was explicitly assigned a null value.
class Holder(str: String) {
var nullable: Int? = null
}
fun example() {
val h = Holder("Create and forget to init 'nullable'")
h.nullable!!.dec()
}
fun possibleFix() {
val h = Holder("Create and forget to init 'nullable'")
h.nullable?.dec()
}
Function example
illustrates the defect:
dec
is called for value which may be null.
Function possibleFix
illustrates a possible fix: use
safe call.
Sometimes, the pointer is assigned a non-null value as a side effect of a function call. If the analysis is unable to see such a side effect (normally, side effects are analyzed), this warning may be emitted incorrectly.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Average | Yes |
Go | Quality | Major | Average | Yes |
Kotlin | Quality | Normal | Average | Yes |
Python | Quality | Normal | Average | Yes |
Related CWEs: CWE476.
This checker finds situations where a pointer is assigned a null value and is subsequently dereferenced. In the situations reported by the checker the dereferenced pointer is not necessarily null but the pointer that was assigned a null value is always dereferenced.
void ep_xfer_timeout(void *ptr)
{
ep_xfer_info_t *xfer_info = NULL; // Assigned a null value
int ep_num = 0;
dctl_data_t dctl = {.d32 = 0 };
gintsts_data_t gintsts = {.d32 = 0 };
gintmsk_data_t gintmsk = {.d32 = 0 };
if (ptr)
xfer_info = (ep_xfer_info_t *) ptr;
if (!xfer_info->ep) { // FLAW: Dereferenced a potentially null value
DWC_ERROR("xfer_info->ep = %p\n", xfer_info->ep);
return;
}
data class Wrapper(val value: Int)
fun example(w: Wrapper?): Int {
val mayBeNull = w?.value
return mayBeNull!!
}
fun possibleFix(w: Wrapper?): Int? {
val mayBeNull = w?.value
return mayBeNull
}
Function example
illustrates the defect:
mayBeNull
may have null value and is cast to non-nullable
type by !!
operator.
Function possibleFix
illustrates a possible fix: return
nullable type.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | High | Yes |
C/C++ | Quality | Normal | High | Yes |
Go | Quality | Normal | High | Yes |
Kotlin | Quality | Normal | High | Yes |
This checker detects issues where a null value is passed as a pointer argument to a function, where it is dereferenced under some conditions depending on external data (i.e. that can’t be guaranteed to always be false when the pointer is null).
static char *
partial_die_full_name (struct partial_die_info *pdi,
struct dwarf2_cu *cu)
{
...
die = follow_die_ref (NULL, &attr, &ref_cu); // FLAW: Null value passed as 1st parameter
...
static struct die_info *
follow_die_ref (struct die_info *src_die, const struct attribute *attr,
struct dwarf2_cu **ref_cu)
{
...
if (!die)
error (_("Dwarf Error: Cannot find DIE at %s referenced from DIE "
"at %s [in module %s]"),
sect_offset_str (sect_off), sect_offset_str (src_die->sect_off), // First parameter is dereferenced here
objfile_name (cu->per_cu->dwarf2_per_objfile->objfile));
...
In the example above a NULL
pointer value is passed to
the follow_die_ref
function as its first parameter
src_die
and this parameter is dereferenced if the value of
an unrelated die
local variable is not equal to
NULL
.
data class SimpleData(val value: Int)
fun example(): Int {
return helper(null, null)
}
fun helper(a: SimpleData?, b: SimpleData?): Int {
if (a != null) {
return a.value + 2
} else {
return b!!.value + 2
}
}
fun exampleFix(): Int {
return helperFix(null, null)
}
fun helperFix(a: SimpleData?, b: SimpleData?): Int {
a?.let { return a.value + 2 }
b?.let { return b.value + 2 }
throw IllegalStateException()
}
Function example
illustrates the defect:
helper
function is called with both null
arguments and its second argument is cast to non-nullable type by
!!
operator inside helper
function.
Function helperFix
illustrates a possible fix: use null
safe operator ?.
.
Sometimes it is logically impossible for the condition under which the pointer is dereferenced in the called function to become true in the context of the call where a null value is assigned to that pointer. The checker emits a warning even if this possibility isn’t ruled out which leads to false positive reports in such cases.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Average | Yes |
Go | Quality | Major | Average | Yes |
Related CWEs: CWE476.
This checker detects issues where a null value is passed as a pointer argument to a function, where it is dereferenced under some condition and analysis is able to prove that this condition can be true when the pointer is null.
#include <stddef.h>
extern void use(int);
void deref_if(int *p, int x) {
if (x > 1) {
x -= *p;
}
use(x);
}
void example(int x) {
deref_if(NULL, x);
}
void example_fixed(int x) {
deref_if(NULL, x > 1 ? 1 : x);
}
Function example
illustrates the defect:
NULL
pointer value is passed to deref_if
function, which dereferences it, if the passed value of x
is greater than 1.
Function example_fixed
illustrates a possible fix.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
C/C++ | Quality | Normal | Unknown | No |
Kotlin | Quality | Minor | Unknown | No |
Related CWEs: CWE476.
This checker finds potential null
pointer dereferences that are less reliable than those found by the
DEREF_OF_NULL checker. For example, this
warning type is reported in Java code when an object with a
nullable
annotation is referenced without a null check. For
Kotlin it is reported when a nullable type object is referenced using
the non-null assertion operator !!
also without a null
check.
fun getLen(s: String?) = s!!.length
fun getLenCorrect(s: String?) = s?.length ?: throw IllegalStateException()
fun getLenOf(s: Any?) = (s as String).length
fun getLenOfCorrect(s: Any?) = (s as? String)?.length ?: throw IllegalStateException()
Functions getLen
and getLenOf
illustrate
the defect: s
may have null value and is cast to
non-nullable type by !!
and as
operators
respectively.
Functions getLenCorrect
and getLenOfCorrect
illustrate possible fixes: use null safe ?.
and
as?
operators.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
Go | Quality | Normal | Unknown | Yes |
Kotlin | Quality | Normal | Unknown | Yes |
Python | Quality | Normal | Unknown | Yes |
This checker finds situations where a pointer returned by a user-written function that returns a null value on some execution paths, is dereferenced without a check for being null.
If instead of a direct
reference an assert-like function such as Java’s
Objects.requireNonNull
or Kotlin’s not-null assertion
operator !!
is used a DEREF_OF_NULL.RET.ASSERT
warning subtype is reported.
If instead of a direct
reference a potentially null value is used in a call as an argument and
corresponding parameter has a @NotNull
or
@NonNull
annotation then a
DEREF_OF_NULL.RET.ANNOT
warning subtype is reported.
#define USE_PREFIX 1
#define MAX_SIZE 1024
char* buf[MAX_SIZE];
char* get_login(const char* url, int flag) {
if (flag == USE_PREFIX && url[0] != '%' && url[1] != '%')
return NULL; // Null value may be returned
return fill_from_bd(buf, MAX_SIZE);
}
void use(const char* url) {
char* login = get_login(url, USE_PREFIX);
int len = strlen(login); // FLAW: potentially null value is used without a check
// ...
}
data class Wrapper(val value: Int)
class Helper(val wr: Wrapper?) {
fun getMaybeNull() = wr?.value
}
fun deref(n: Int?) = n!!.times(3)
fun example(h: Helper): Int {
val num = h.getMaybeNull()
return deref(num)
}
fun derefCorrect(n: Int?) = n?.times(3)
fun possibleFix(h: Helper): Int? {
val num = h.getMaybeNull()
return derefCorrect(num)
}
Function example
illustrates the defect: variable
num
value may be null after a getMaybeNull
call and it is dereferenced by the call of a user defined function
deref
. Function possibleFix
illustrates a
possible fix: use safe implementation derefCorrect
. Safe
call of the times
method is used inside the
derefCorrect
function.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
Visual Basic .NET | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Major | Unknown | Yes |
Kotlin | Quality | Normal | Unknown | Yes |
C# | Quality | Normal | Unknown | Yes |
This checker finds situations where a pointer returned by a library function is dereferenced without being checked for a null value.
Following is a partial list of library functions that are recognized by this checker as being able to return a null value for reasons not under user’s control:
Examples from the C standard library:
fdopen
,fopen
,popen
,getenv
,opendir
,dlopen
,dlsym
,freopen
,tempnam
,tmpfile
,tmpnam
,ptsname
,getutent
,getutid
,getutline
,pututline
,getcwd
,getwd
.Examples from the Java libraries:
java.io.File.listRoots()
java.io.File.getParent()
java.io.File.getParentFile()
java.io.File.listFiles().tryLock()
java.net.DatagramSocket.getLocalAddress()
java.nio.channels.FileChannel
javax.crypto.Cipher.update(byte[])
javax.crypto.Cipher.update(byte[], int, int)
android.os.Bundle.getBundle(String)
void example() {
char *s = getenv("RANDFILE");
if (s[0] == '\0') {
// ...
}
}
void possible_fix() {
char *s = getenv("RANDFILE");
if (s != NULL && s[0] == '\0') {
// ...
}
}
Function example
illustrates the defect: if the
RANDFILE
environment variable is not set the
getenv
function returns a NULL
value which is
dereferenced by accessing the first character of the returned string
s
.
Function possible_fix
illustrates a possible fix: check
the result of the getenv
call for being NULL
before accessing the first character of the returned string
s
.
import java.io.File
fun example(f: File) = f.parentFile.listFiles()
fun possibleFix(f: File) = f.parentFile?.listFiles()
Function example
illustrates the defect: getter of a
parentFile
property may return null, which is referenced by
a listFiles
call.
Function possibleFix
illustrates a possible fix: use a
safe call of the listFiles
function.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Quality | Normal | Average | Yes |
C# | Quality | Normal | Average | Yes |
This is a subtype of DEREF_OF_NULL.RET.LIB warning, and it is reported when a pointer returned by a library function may have null value and is dereferenced not explicitly, but within a function call.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Unknown | Yes |
C/C++ | Quality | Minor | Unknown | Yes |
Kotlin | Quality | Minor | Unknown | Yes |
This is a subtype of a DEREF_OF_NULL.RET.LIB warning type that is reported when a pointer returned by a library function may have null value, but this might happen in quite untypical usage conditions, so can be considered as an issue of minor probability.
Examples from the C standard library:
localtime
,Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | High | No |
This C/C++-specific checker finds situations where a pointer returned
by a memory allocation function that returns a NULL
value
in error cases, such as malloc
, is dereferenced without
being checked for a NULL
value.
Note: when the
amount of allocated memory is a small constant, the probability of error
cases becomes much lower and these situations are reported with a
DEREF_OF_NULL.RET.ALLOC.MINOR
warning subtype. The
threshold can be set by a
DEREF_OF_NULL_RET_ALLOC.SMALL_ARGUMENT
configuration option and has a default value of
5000.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Major | High | Yes |
Go | Quality | Major | High | Yes |
Kotlin | Quality | Major | High | Yes |
Related CWEs: CWE476.
This checker is a statistical version of DEREF_OF_NULL.RET that also reports when a potentially null pointer returned by a function is dereferenced without checking for being null. A report of this warning type is emitted when a pointer returned by a call to a function is dereferenced without a null check while for a number of other calls to the same function it is dereferenced only after a proper null check.
#include <stddef.h>
extern int *get_ptr(int);
void example() {
int *p1 = get_ptr(1);
if (p1)
*p1 = 3;
int *p2 = get_ptr(2);
if (p2)
*p2 = 1;
int *p3 = get_ptr(3);
if (p3)
*p3 = 4;
int *p4 = get_ptr(4);
if (p4)
*p4 = 1;
int *p5 = get_ptr(5);
//if (p5)
*p5 = 5;
int *p6 = get_ptr(6);
if (p6)
*p6 = 9;
int *p7 = get_ptr(7);
if (p7)
*p7 = 2;
int *p8 = get_ptr(8);
if (p8)
*p8 = 6;
}
Function example
illustrates the defect: each time
function get_ptr
is called, the returned value is checked
for not being NULL
before its dereference, except for
variable p5
.
Possible fix is to uncomment the commented if
operator,
which checks the value of the p5
variable before its
dereference too.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | High | Yes |
Related CWEs: CWE476.
This checker reports an issue when a pointer value is the result of a
dynamic_cast
C++ operator, it may be null and it is
dereferenced without an appropriate check.
class A {
public:
virtual int a() { return 0; }
};
class B : public A {
public:
virtual int a() { return 1; }
virtual int b() { return 0; }
};
int example(A *a) {
B *b = dynamic_cast<B*>(a);
return b->b();
}
int example_fixed(A *a) {
B *b = dynamic_cast<B*>(a);
if (!b)
return -1;
return b->b();
}
Function example
illustrates the defect: pointer
b
is the result of a dynamic_cast
operator,
and it is dereferenced without any check.
Function example_fixed
illustrates a possible fix.
Sometimes, a complex program logic might guarantee that
dynamic_cast
can be used safely without any additional
checks, while the analysis is not able to prove it.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
Kotlin | Quality | Normal | Unknown | No |
This Java/Kotlin-specific checker reports an issue when a null value
is used in a call as an argument and corresponding parameter has a
@NotNull
or @NonNull
annotation.
class Example {
public void test_helper(@NotNull String s) {
}
public void test(String s) {
if (s == null)
test_helper(s); // DEREF_OF_NULL.ANNOT
else
test_helper(s); // No warning, variable s is not null!
}
}
Method test
illustrates this defect - the value of
variable s
is checked for being null
and then
passed as an argument to the method test_helper
call whose
parameter has a NotNull
annotation. In the
else
clause of the same if
statement, where
the value of variable s
is definitely not
null
, using it as an argument of the call to the same
test_helper
method doesn’t cause this warning to be
reported.
The issue can be fixed by removing the NotNull
annotation on the parameter of test_helper
method in this
case.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | No |
Kotlin | Quality | Normal | Low | No |
This checker detects situations where literal NULL
value
or a variable initialized by literal NULL
value is used as
an argument in a function call and thus passed to a parameter which has
a NotNull
or NonNull
annotation.
class Example {
public void test_helper(@NotNull String s) {
}
public void test1(int i) {
String s = null;
test_helper(s); // DEREF_OF_NULL.ANNOT.CONST
}
public void test2() {
test_helper(null); // DEREF_OF_NULL.ANNOT.CONST
}
}
Methods test1
and test2
exemplify the two
possible ways for this defect to occur. In the former case, variable
s
is initialized by literal NULL
value and
then passed to the parameter of method test_helper
which
has a NotNull
annotation. In the latter case, literal
NULL
is passed directly to the same parameter of
test_helper
method.
Possible fix in both cases is to remove the NotNull
annotation on the parameter of test_helper
method.
Due to known issue, this checker can produce false positive findings in Kotlin projects where a call to typeOf function from Kotlin reflection API is used as an argument in a function call and passed to a parameter with a non-nullable type.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Very high | No |
Kotlin | Quality | Minor | Very high | No |
This checker finds issues where a value which bears a
Nullable
annotation is used as an argument in a function
call and thus passed to a parameter with NotNull
or
NonNull
annotation.
class Example {
@Nullable private String s
public void test_helper(@NotNull String s) {
}
public void test1(@Nullable String s) {
test_helper(s); // DEREF_OF_NULL.ANNOT.STRICT
}
public void test2() {
test_helper(this.s); // DEREF_OF_NULL.ANNOT.STRICT
}
}
The defect is shown in methods test1
and
test2
. In the former, a parameter with
Nullable
annotation is passed to parameter with
NotNull
annotation in a call to method
test_helper
. In the latter, a field declared with
Nullable
annotation is similarly passed to the same
parameter of method test_helper
.
Possible fixes include removal of either annotation or both of them,
or placing calls to test_helper
method under
if
statements that check that the value passed to the
parameter with NotNull
annotation is in fact not
NULL
.
Due to known issue, false positive warnings of this type can be
issued where a value which bears Nullable
annotation is
used as an argument in a call to another function of specific form.
Namely, the return value of this function should have
NonNull
or NotNull
annotation while the
parameter receiving the value with Nullable
annotation
should also have Nullable
annotation. Moreover, the
function’s code should return the value of that parameter, while it’s
free to perform any operations on that parameter prior to that.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
Kotlin | Quality | Normal | Unknown | No |
This checker finds situations where NULL
value is passed
to a function and later inside that function, under conditions
independent of this value, it is used as an argument in a call to
another function, whereby it is passed to a parameter which has
NotNull
or NonNull
annotation.
class Example {
public void test_helper(@NotNull String s) {
}
public void test(String s1, String s2) {
if (s1 != null)
test_helper(s1);
else
test_helper(s2);
}
public void foo() {
test(null, null); // DEREF_OF_NULL.ANNOT.COND
}
}
The defect is illustrated by method foo
-
NULL
value passed in the call to test
method
to parameter s2
may later be used in the call to
test_helper
method and thus be passed to a parameter with
NotNull
annotation, depending on the value passed to
parameter s1
of test
method.
The issue can be fixed by putting the call to
test_helper
method under condition that checks that the
value passed to that call is not NULL
. Alternatively, the
NotNull
annotation on the parameter of
test_helper
method can be removed.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | No |
Kotlin | Quality | Normal | Average | No |
This checker produces warnings if some variable is first assigned
NULL
value and later on it is used as an argument in a
function call and thus passed to a parameter with NotNull
or NonNull
annotation. Note that the value of the variable
may be changed along some execution paths leading to the call site; the
important point is that the paths that leave the NULL
value
intact must lead to the call site as well.
class Example {
private String str;
public void test_helper(@NotNull String s) {
}
public void test(boolean cond) {
String s = null;
if (cond)
s = "string";
test_helper(s); // DEREF_OF_NULL.ANNOT.ASSIGN
}
public NotNullAnnot(boolean cond) {
if (cond)
str = "string";
test_helper(str); // DEREF_OF_NULL.ANNOT.ASSIGN
}
}
Method test
and the constructor of Example class
exemplify the defect. In the former case, a local variable
s
is first assigned a NULL
value which may be
changed depending on the parameter cond
and then is used in
a call to method test_helper
whereby it is passed to
parameter s
which has NotNull
annotation. In
the latter case, uninitialized field str
which has default
NULL
value may be changed depending on the constructor
parameter cond
and then similarly is passed to the same
parameter of method test_helper
.
Possible fix is to put the call to test_helper
method
under if
statement checking that the value being passed to
it is not NULL
. Alternatively, the NotNull
annotation on the parameter of test_helper
method can be
removed.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
Kotlin | Quality | Normal | Unknown | No |
This checker detects situations where a value that may be
NULL
under some condition is used as an argument in a
function call and thus passed to a parameter with NotNull
or NonNull
annotation under another condition which is not
mutually exclusive with the first condition.
class Example {
private int x;
public void test_helper(@NotNull String s) {
}
public void test1(Object o) {
String s = null;
if (o instanceof String)
s = (String) o;
if (x > 0)
test_helper(s); // DEREF_OF_NULL.ANNOT.EX
}
public void test2(Object o, boolean b) {
String s = null;
if (o instanceof String)
s = (String) o;
else
b = false;
if (b)
test_helper(s); // No warning here, as b is false when s is null
}
}
The defect is shown in methods test1
and
test2
. In the first case, the value of local variable
s
is NULL
under condition which is totally
independent of the condition under which that variable is used in a call
to test_helper
method and passed to the parameter which has
NotNull
annotation. Therefore, the warning is emitted in
this case. By contrast, in the second case the condition under which the
value of s
is NULL
and condition under which
s
is passed to the same parameter of
test_helper
method are mutually exclusive, thus the issue
is not detected.
Possible fix in the first case is either to remove the
NotNull
annotation on the parameter of
test_helper
method or augment the condition under which
test_helper
method is called so that it also checks that
the value of s
at the call site is not
NULL
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
Kotlin | Quality | Major | Unknown | No |
This checker reports issues where a NULL
value is used
as an argument in a function call under certain conditions and thus
passed to a parameter with NotNull
or NonNull
annotation.
class Example {
public void func(@NotNull String s) {
}
void passIfTrue(String str, int f1) {
if (f1 > 0)
func(str);
}
void test(int f1, int f2) {
String str = null;
passIfTrue(str, f1);
}
}
Method test
exemplifies the defect - variable
str
is assigned NULL
value and then used in a
call to method passIfTrue
whereby, if the other argument
passed in the same call is positive, that NULL
value is
passed to the parameter of method func
which bears
NotNull
annotation.
Possible fixes include removal of NotNull
or
NonNull
annotation on the parameter of func
method, extending the condition in passIfTrue
method to
ensure the argument used in the call to func
method is not
NULL
or placing the call to passIfTrue
method
under condition that excludes using NULL
value as the first
argument and a positive value as the second argument of that call.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
Visual Basic .NET | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | High | Yes |
Go | Quality | Critical | High | Yes |
Kotlin | Quality | Normal | High | Yes |
Python | Quality | Normal | High | Yes |
C# | Quality | Major | Average | Yes |
This checker finds situations where first, a pointer is compared to
NULL
(which indicates that it could have a
NULL
value), and then it is dereferenced
(unconditionally).
A subtype DEREF_AFTER_NULL.LOOP
is emitted when the conditional expression comparing the pointer to
NULL
is part of a loop.
A (suppressed) DEREF_AFTER_NULL.MACRO
subtype of this warning is emitted if it is suspected that the warning
is a false positive, caused by the conditional expressions being
implemented within a macro. Such checks can be too general and not
always reflect possible value range of index variables.
A less reliable DEREF_AFTER_NULL.RET
subtype of this warning type is emitted if the checked for null pointer
value is returned from a function before the dereference.
C# warnings can have one
of these subtypes: .ARGUMENT and .ARGUMENT.INSTANT. There is no DEREF_AFTER_NULL.EX for C# because
DEREF_AFTER_NULL
is already path-sensitive.
int test(char* str1, char* str2, int len) {
if (!str1) {
len = 0;
} else {
len = strlen(str1);
}
return strcmp(str1, str2);
}
If str1
can be NULL
, so that the
conditional isn’t trivial, then call of strcmp
will lead to
a null pointer dereference.
data class Client(val name: String, val age: Int)
fun example(arg: Client?) {
val age = arg?.age
println("Client '${arg!!.name}' is $age years old")
}
fun possibleFix(arg: Client?) {
val age = arg?.age
age?.let{ println("Client '${arg.name}' is $it years old") }
}
Function example
illustrates the defect: Variable
age
is initialized from the arg
parameter
using a safe call operator, handling the case when the parameter is
null, but afterwards the arg
parameter is again
dereferenced using a not-null assertion operator to get the field
name
value without a null check this time.
Function possibleFix
illustrates a possible fix by
ensuring non-null value of parameter arg
before getting the
value of its filed name
using a scope function
let
.
A common cause of this checker false positives is when the execution paths taken when a variable is null contain program termination before its dereference but the analyzer doesn’t have enough data to detect it. For example if calling external functions that abort the program:
If instead of an unknown my_exit
, a standard program
termination function exit
is called, no false warnings is
emitted.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Go | Quality | Critical | Average | Yes |
Kotlin | Quality | Normal | Average | Yes |
Python | Quality | Normal | Average | Yes |
Related CWEs: CWE476.
This checker is a version of the DEREF_AFTER_NULL with path sensitivity. Unlike DEREF_AFTER_NULL, it produces a warning when a variable can be dereferenced after comparison against a null value and there exist a combination of the input parameters and an execution path where both of these events can happen.
import java.text.DateFormat
import java.util.*
fun example(date: String?): Date {
date?.let{ println("parsing: '$date'") }
val df = DateFormat.getDateInstance(DateFormat.LONG, Locale.KOREA)
return df.parse(date)
}
fun possibleFix(date: String): Date {
println("parsing: '$date'")
val df = DateFormat.getDateInstance(DateFormat.LONG, Locale.KOREA)
return df.parse(date) // svace: not_emitted DEREF_AFTER_NULL*
}
Function example
illustrates the defect:
date
may be null according to safe call of let
block, but it has been dereferenced onward by passing as first parameter
into parse
method.
Function possibleFix
illustrates a possible fix: change
function contract. First argument of possibleFix
function
is non-nullable.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | No |
C/C++ | Quality | Major | High | No |
This checker detects issues where a pointer or an object reference is compared against a null value (which indicates that it may be null) and then passed to a function as a parameter, where it is conditionally dereferenced.
Note: the checker does not use smt-solver. It uses heuristics to define, where function may dereference passed null pointer.
extern int get_some_data();
void deref_under_some_condition(int *p) {
if (get_some_data() > 0) {
*p = -1;
}
}
void example(int *p) {
if (p) {
*p = 123;
}
deref_under_some_condition(p);
}
void example_fixed(int *p) {
if (p) {
*p = 123;
}
if (p) {
deref_under_some_condition(p);
}
}
Function example
illustrates the defect: variable
p
is compared to NULL
and then passed to
function deref_under_some_condition
which could dereference
it under a condition, that depends on some uncontrolled integer value
(returned by function get_some_data
).
Function example_fixed
illustrates a possible fix.
In some cases the checker might lack or miss information that the condition of the dereference within the called function can’t be true in the context where the function is called, in which case the null dereference can actually never happen during this call but the null check still may be useful for calls from the other contexts.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Average | Yes |
Go | Quality | Major | Average | Yes |
This checker detects issues where a pointer or an object reference is compared against a null value (which indicates that it may be null) and then passed to a function as a parameter, where it is conditionally dereferenced.
void deref_if_x_is_positive(int *p, int x) {
if (x > 0) {
*p = x;
}
}
void example(int *p, int x) {
if (p) {
*p = 123;
}
deref_if_x_is_positive(p, x);
}
void example_fixed(int *p, int x) {
if (p) {
*p = 123;
}
if (p) {
deref_if_x_is_positive(p, x);
}
}
Function example
illustrates the defect: pointer
p
is compared to NULL
and then passed to
function deref_if_x_is_positive
which could dereference it,
if condition x > 0
is true.
Function example_fixed
illustrates a possible fix.
In some cases the checker might lack or miss information that the condition of the dereference within the called function can’t be true in the context where the function is called, in which case the null dereference can actually never happen during this call but the null check still may be useful for calls from the other contexts.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Average | Yes |
C/C++ | Quality | Normal | Average | Yes |
Related CWEs: CWE476.
This checker detects issues where a pointer or an object reference is compared against a null value (which indicates that it may be null) inside of a loop condition and then it is dereferenced after the loop.
struct Item {
int id;
int value;
struct Item *next;
};
int example(Item *start, int id) {
Item *item = start;
while (item && item->id != id) {
item = item->next;
}
return item->value;
}
int example_fixed(Item *start, int id) {
Item *item = start;
while (item && item->id != id) {
item = item->next;
}
if (!item)
return 0;
return item->value;
}
Function example
illustrates the defect: variable
item
is compared to NULL
in a
while
loop condition and then dereferenced after the
loop.
Function example_fixed
illustrates a possible fix.
Occasionally when loop condition is composed of several subconditions the assumption that the comparison implies a null value might not be correct for the path where the warning is reported due to some additional implicit preconditions.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Suppressed | Major | Average | Yes |
C/C++ | Suppressed | Major | Average | Yes |
Go | Suppressed | Major | Average | Yes |
Related CWEs: CWE476.
This checker detects issues where a pointer or an object reference is compared against a null value (which indicates that it may be null), and then dereferenced on a path where the program execution might be terminated, but the analysis is not able to ensure whether it avoids null dereference or not.
#include <stdlib.h>
extern int get_error_level(int error_id);
void handle_error(int error_id) {
if (get_error_level(error_id) > 1) {
exit(1);
}
}
void example(int *p) {
if (!p) {
handle_error(313);
}
*p = -1;
}
void example_fixed(int *p) {
if (!p) {
handle_error(313);
} else {
*p = -1;
}
}
Function example
illustrates the defect: variable
p
is compared to NULL
and then dereferenced,
while on a path, where it is null function handle_error
is
called, which might terminate the program execution.
Function example_fixed
illustrates a possible fix.
Sometimes, the program logic guarantees that the called function will
always terminate the program execution, when the pointer is
NULL
, but the analysis is not able to prove it.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Kotlin | Suppressed | Normal | Unknown | No |
A subtype of DEREF_AFTER_NULL, where a null check happens inside of an inlined code.
data class Person(val id: Int, val name: String)
fun example(id: Int, persons: List<Person>): String {
return persons.find { it.id == id }!!.name
}
fun possibleFix(id: Int, persons: List<Person>): String? {
return persons.find { it.id == id }?.name
}
Function example
illustrates the defect: higher-order
function find
may return null, if there was no entry with
the given id
, so !!
operator will fail with an
exception.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
Visual Basic .NET | Quality | Major | Average | Yes |
C/C++ | Quality | Major | High | Yes |
Kotlin | Quality | Normal | High | Yes |
Python | Quality | Normal | High | Yes |
C# | Quality | Major | Average | Yes |
The checker finds situations where first, a pointer or an object reference is dereferenced, and then it is compared against a null value (which indicates that it may be null). This means that either the null check is too late and the potential null dereference has already happened or it’s not needed at all since it’s always false.
A (suppressed) NULL_AFTER_DEREF.MACRO
subtype of this warning is emitted if it is suspected that the warning
is a false positive, caused by the conditional expressions being
implemented within a macro. Such checks can be too general and not
always reflect possible value ranges of index variables.
struct bank {
char* name;
char* pool;
};
void test(struct bank* c, char* name) {
strcpy(name, "<Global>");
if (name)
c->name = pstrdup(c->pool, name);
}
If name
can be NULL
, so that the
conditional isn’t trivial, then call of strcpy
will lead to
a null pointer dereference.
import java.text.DateFormat
import java.util.Date
import java.util.Locale
fun example(date: String?): Boolean {
val df = DateFormat.getDateInstance(DateFormat.LONG, Locale.KOREA)
val ret = df.parse(date)
date?.let{ println("'$date' has been parsed") } // svace: emitted NULL_AFTER_DEREF
return ret.after(Date(2020, 12, 10))
}
fun possibleFix(date: String?): Boolean {
val ret = date?.let {
val df = DateFormat.getDateInstance(DateFormat.LONG, Locale.KOREA)
df.parse(date).also { println("'$date' has been parsed") }
}
return ret?.after(Date(2020, 12, 10)) ?: false
}
Function example
illustrates the defect:
date
may be null according to safe call of let
block but it has been dereferenced earlier by passing as first parameter
into parse
method.
Function possibleFix
illustrates a possible fix: wrap
more code into let
block.
In some cases the dereference location in the warning report points to a function that returns the pointer. Such warnings reflect an observation that the function that produced the pointer unconditionally dereferences it before the return and so the returned pointer can’t be null. This means that the null check in the callee is redundant. In most cases this happens due to incorrect assumptions about the called function return value (e.g. the check is added “just in case”) but at the same time there are various situations when such check can be beneficial and shouldn’t be removed.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | CodingStyle | Minor | Average | No |
C/C++ | CodingStyle | Minor | Average | No |
The checker finds situations where first, a pointer or an object reference is obtained as a result of a function call, and then is compared against a null value (which indicates that it is considered to possibly be null), but it has already been dereferenced in the function that produced it. This means that the null check in the callee is redundant. In most cases this happens due to incorrect assumptions about the called function return value (e.g. the check is added “just in case”) but at the same time there are various situations when such check can be beneficial and shouldn’t be removed.
char* get_buf(void) {
char* buf = (char*) malloc(100);
buf[0] = 'q';
return buf;
}
void foo(void) {
char* res = get_buf();
if (res) // NULL_AFTER_DEREF.RET: `res` can't be NULL.
res[1] = 'w';
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Kotlin | Quality | Normal | High | Yes |
This is a Kotlin-specific checker similar to NULL_AFTER_DEREF which issues warnings if a variable has the non-null assertion operator (!!) applied to it prior to being compared to null.
Function test
shows the defect - parameter
s
of nullable type has the not-null assertion operator (!!)
applied. If the value of s
(as passed by caller) is null,
NullPointerException will be thrown and execution won’t proceed to the
next line. Thus, the null-check via null-safe member access operator
(?.) on the next line is unnecessary.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Kotlin | Quality | Normal | High | Yes |
This Kotlin-specific checker detects situations where a variable has the unsafe cast operator (as operator with non-nullable target type) applied to it prior to being compared to null.
Function test
exemplifies the defect - parameter
s
of nullable type has the unsafe cast operator applied. If
the value of s
(as passed by caller) is null,
ClassCastException will be thrown and execution won’t proceed to the
next line. Thus, the null-check via null-safe member access operator
(?.) on the next line is unnecessary.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Average | Yes |
Since address of a local variable in C/C++ can’t be null comparing it a with a null value is redundant.
int foo() {
int x = 0;
if (&x) // COMPARE_LOCAL_ADDR, maybe it should be `if (x)`.
return 0;
return 1;
}
Tainted input checkers find situations where a value obtained from external sources (network, files, environment variables) is used in an operation sensitive to incorrect or malicious parameters. The externally controlled value is called tainted, the location where tainted value is obtained is called source, and the location where tainted value is dangerously used is called sink. Checkers in this group consider tainted values of integer or C string types. TAINTED_INT.LOOP considers loop bounds as sinks.
Sources for TAINTED_INT:
int getch(void);
int _getch(void);
int getchar(void);
int atoi(const char* arg);
long atol(const char* arg);
long long atoll(const char* arg);
long strtol(const char *restrict nptr, char **restrict endptr, int base);
long long strtoll(const char *restrict nptr, char **restrict endptr, int base);
unsigned long strtoul(const char *restrict nptr, char **restrict endptr, int base);
unsigned long long strtoull(const char *restrict nptr, char **restrict endptr, int base);
elements of tainted integer arrays
Sinks for TAINTED_INT:
char *fgets(char *s, int num, FILE *stream);
void* memset(void * ptr, int value, size_t num);
ssize_t pwrite(int d, const void *buf, size_t nbytes, off_t offset);
ssize_t write(int d, const void *buf, size_t nbytes);
void *calloc(size_t num, size_t size);
void *malloc(size_t size);
void *xmalloc(unsigned long size);
void *realloc(void *ptr, size_t size);
void *xrealloc (void *ptr, size_t size);
Sources for TAINTED_PTR:
ssize_t recv(int s, void *buf, size_t len, int flags);
ssize_t recvfrom(int s, void *buf, size_t len, int flags, struct sockaddr *from, socklen_t *fromlen);
char *fgets(char *s, int num, FILE *stream);
size_t fread(void *ptr, size_t size, size_t nitems, FILE *stream);
char *gets(char *s);
ssize_t getline(char **lineptr, size_t *n, FILE *stream);
char *getenv(const char* key);
ssize_t read(int d, void *buf, size_t nbytes);
(...) scanf
Sinks for TAINTED_PTR:
FILE *fopen(const char *filename, const char *mode);
FILE *freopen(const char *filename, const char *mode, FILE *stream);
FILE *popen(const char *command, const char *mode);
char* strcat(char *s, const char * append);
char* strcpy(char *dst, const char *src);
char* stpcpy(char *dst, const char *src);
Format string sinks:
void err(int eval, const char *fmt, ...);
void verr(int eval, const char *fmt, va_list args);
void errx(int eval, const char *fmt, ...);
void verrx(int eval, const char *fmt, va_list args);
void warn(const char *fmt, ...) ;
void vwarn(const char *fmt, va_list args);
void warnx(const char *fmt, ...);
void vwarnx(const char *fmt, va_list args);
void error(int status, int errnum, const char *fmt, ...);
int fprintf(FILE *stream, const char *format, ...);
int fscanf(FILE *stream, const char *format, ...);
int printf(const char *format, ...);
int scanf(const char *format, ...);
int sprintf(char *s, const char *format, ...) ;
int snprintf(char *str, size_t size, const char *format, ...);
int asprintf(char **ret, const char *format, ...);
int sscanf(const char *s, const char *format, ...);
int vscanf(const char *format, va_list ap);
int vsscanf(const char *str, const char *format, va_list ap);
int vfscanf(FILE *stream, const char *format, va_list ap);
int vfprintf(FILE *stream, const char *format, va_list ap);
int vprintf(const char *format, va_list ap);
int vsprintf(char *s, const char *format, va_list ap);
int vsnprintf(char *str, size_t size, const char *format, va_list ap);
int vasprintf(char **ret, const char *format, va_list ap);
void setproctitle(const char *fmt, ...);
void syslog(int priority, const char *message, ...);
void Tcl_Panic(const char* format, ...);
void panic(const char* format, ...)
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Critical | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Scala | Quality | Critical | Average | Yes |
Go | Quality | Critical | Average | Yes |
Python | Quality | Critical | Average | Yes |
Related CWEs: CWE121, CWE122, CWE129, CWE194, CWE195, CWE20, CWE400, CWE606, CWE789.
The checker TAINTED_INT finds situations where an integer value received from external source (from a file or from the network; such value is called tainted value) is used in an operation where uncontrolled value may cause problems, such as allocation of memory of given size, or reading the given number of bytes from a file.
There is a .MIGHT variant of this warning (TAINTED_INT.MIGHT).
This warning has the following subtypes:
isdigit
).size_t size;
char* res;
void test() {
char* env = getenv("QQQ");
size = strtoul(env, NULL, 10);
// Allocating a buffer of unbounded number of bytes.
res = (char*) malloc(size);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Suppressed | Major | Average | Yes |
C/C++ | Suppressed | Major | Average | Yes |
Scala | Suppressed | Major | Average | Yes |
Go | Suppressed | Major | Average | Yes |
Related CWEs: CWE121, CWE122, CWE129, CWE194, CWE195, CWE20, CWE400, CWE606, CWE789.
.MIGHT variant of TAINTED_INT, which finds situations where tainted value is used in an unsafe operation, where value is tainted only on some execution paths.
void test(char* str, int flag) {
int count;
if (flag) {
count = atoi(str);
} else {
count = -1;
}
usleep(count);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
C/C++ | Quality | Major | Unknown | No |
Scala | Quality | Major | Unknown | No |
Go | Quality | Major | Unknown | No |
Related CWEs: CWE121, CWE122, CWE129, CWE194, CWE195, CWE20, CWE400, CWE606, CWE789.
.COND variant of TAINTED_INT, which finds situations where tainted value is used in an unsafe operation, where tainted value is passed to function, which uses it only on some execution paths.
package main
import (
"flag"
"strconv"
)
func allocIf(size, mode int) {
if mode > 10 {
return
}
allocSlice(size)
}
func allocSlice(size int)[]string {
var s = make([]string, size)
return s
}
func main() {
sizeArg := flag.String("size", "0", "")
modeArg := flag.String("size", "0", "")
flag.Parse()
size, _ := strconv.Atoi(*sizeArg)
mode, _ := strconv.Atoi(*modeArg)
allocIf(size, mode) //TAINTED_INT.COND
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
C/C++ | Quality | Major | Unknown | No |
Scala | Quality | Major | Unknown | No |
Go | Quality | Major | Unknown | No |
Related CWEs: CWE121, CWE122, CWE129, CWE194, CWE195, CWE20, CWE400, CWE606, CWE789.
It is variant of TAINTED_INT with features of both .COND and .MIGHT types. The detector finds situations where tainted value is used in an unsafe operation, and it is used in an operation where uncontrolled value may cause problems. The value is tainted only on some paths, and it is uses also only on some paths.
package main
import (
"flag"
"strconv"
)
func allocIf(size, mode int) {
if mode > 10 {
return
}
allocSlice(size)
}
func allocSlice(size int)[]string {
var s = make([]string, size)
return s
}
func main() {
sizeArg := flag.String("size", "0", "")
modeArg := flag.String("size", "0", "")
flag.Parse()
size, _ := strconv.Atoi(*sizeArg)
mode, _ := strconv.Atoi(*modeArg)
if mode < 20 {
//now for some pathes size is not controlled by user
size = 10
}
allocIf(size, mode) //TAINTED_INT.MIGHT.COND
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | Yes |
Related CWEs: CWE121, CWE122, CWE129, CWE20, CWE606.
A subtype of TAINTED_INT, where a tainted
value is used in character type functions from header
<ctype.h>
(isdigit
,
isspace
, etc.). ISO C requires that ctype functions work
for unsigned char values and for EOF
. Other values may lead
to undefined behavior, but many library implementations also support
negative signed char and some other values. This warning is suppressed
if tainted integer is known to have values only in interval [-128;
255].
char ch = '0';
int val;
void int_type() {
scanf("%d", &val);
if (isdigit(val)) // Here the program may crash; TAINTED_INT.CTYPE is emitted.
ch = (char) val;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Suppressed | Major | Unknown | No |
Related CWEs: CWE121, CWE122, CWE129, CWE20, CWE606.
.MIGHT variant of TAINTED_INT.CTYPE, where value is tainted only on some execution paths.
char ch = '0';
void int_type(int val) {
if (val < 0) {
scanf("%d", &val);
}
if (isdigit(val)) // Here the program may crash if val < 0; TAINTED_INT.CTYPE.MIGHT is emitted.
ch = (char) val;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Critical | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Scala | Quality | Critical | High | Yes |
Related CWEs: CWE121, CWE122, CWE129, CWE20, CWE400, CWE606.
A subtype of TAINTED_INT, where the tainted value is used as a bound in a loop, and thus may cause the program to hang.
There is a .MIGHT variant of this warning (TAINTED_INT.LOOP.MIGHT).
size_t size;
char* res;
void test() {
int i;
char* env = getenv("QQQ");
size = strtoul(env, NULL, 10);
for (i = 0; i < size; i++) {
// ...
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Suppressed | Critical | Average | Yes |
C/C++ | Suppressed | Critical | Average | Yes |
Scala | Suppressed | Critical | Average | Yes |
Related CWEs: CWE121, CWE122, CWE129, CWE20, CWE400, CWE606.
.MIGHT variant of TAINTED_INT.LOOP, which find situations where loop bound is tainted only on some execution paths.
size_t size;
char* res;
void test(bool flag) {
int i;
if (flag) {
char* env = getenv("QQQ");
size = strtoul(env, NULL, 10);
} else {
size = 0;
}
for (i = 0; i < size; i++) {
// ...
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Average | Yes |
Scala | Quality | Major | Average | Yes |
A subtype of TAINTED_INT, where the tainted value is used as a loop step, and thus may cause the loop to be infinite.
There is a .MIGHT variant of this warning (TAINTED_INT.INFINITE_LOOP.MIGHT).
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Average | Yes |
Scala | Quality | Major | Average | Yes |
.MIGHT variant of TAINTED_INT.INFINITE_LOOP, which find situations where loop step is tainted only on some execution paths.
void test(bool flag) {
int step;
if (flag) {
step = getchar();
} else {
step = 1;
}
for (char i = 0; i < 100; i += step) {
// ...
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Scala | Quality | Critical | Average | Yes |
Go | Quality | Critical | Average | Yes |
Kotlin | Quality | Critical | Average | Yes |
Python | Quality | Critical | Average | Yes |
Related CWEs: CWE121, CWE122, CWE124, CWE126, CWE127, CWE129, CWE194, CWE195, CWE20, CWE606.
The checker TAINTED_ARRAY_INDEX finds situations where an integer value received from external source (from a file or from the network) is used as an index in accessing an array, without ensuring that it’s within bounds. This warning is a subtype of TAINTED_INT.
There is a .MIGHT variant of this warning (TAINTED_ARRAY_INDEX.MIGHT).
void array_index() {
int buf[256];
int index = getchar();
if (index < 256) {
// Index may still be negative!
buf[index] = 7;
}
}
fun example(number: String) {
val num: Int = number.toInt()
val x: IntArray = intArrayOf(1, 2, 3)
if (num < 3) {
print(x[num])
}
}
fun possibleFix(number: String) {
val num: Int = number.toInt()
val x: IntArray = intArrayOf(1, 2, 3)
if (num >=0 && num < 3) {
print(x[num])
}
}
Function example
illustrates the defect:
num
is converted from string value. Only the right bound of
num
is checked and num
may be still negative
when it’s used as array index.
Function possibleFix
illustrates a possible fix: check
the left bound of num
as well.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Suppressed | Major | Unknown | Yes |
C/C++ | Suppressed | Major | Unknown | Yes |
Scala | Suppressed | Major | Unknown | Yes |
Python | Suppressed | Major | Unknown | Yes |
Related CWEs: CWE121, CWE122, CWE124, CWE127, CWE129, CWE194, CWE195, CWE20, CWE606.
.MIGHT version of TAINTED_ARRAY_INDEX, which checks if out-of-bounds array access could happen on some execution paths.
#include <cstdio>
void array_index(bool flag) {
int buf[256];
int index = getchar();
if (flag && index < 256) {
// Index may still be negative!
buf[index] = 7;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Scala | Quality | Critical | Average | Yes |
Go | Quality | Critical | Average | No |
Python | Quality | Critical | Average | Yes |
Related CWEs: CWE121, CWE122, CWE126, CWE129, CWE606.
Extended version of TAINTED_ARRAY_INDEX, which checks if out-of-bounds array access could happen on some execution paths. Very similar to TAINTED_ARRAY_INDEX.MIGHT, but can find more difficult cases.
int bounded_getchar(int bound) {
int res = getchar();
return res > bound ? bound : res;
}
void array_index_error(int bound) {
int buf[256];
// If bound > 256, index may be equal to 256
int index = bounded_getchar(bound > 256 ? 256 : bound);
if (index >= 0) {
// Index may still be equal to 256
buf[index] = 7;
}
}
void possible_fix(int bound) {
int buf[256];
// Index is not greater than 255
int index = bounded_getchar(bound > 255 ? 255 : bound);
if (index >= 0) {
// No error here
buf[index] = 7;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Critical | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Scala | Quality | Critical | High | Yes |
Related CWEs: CWE121, CWE122, CWE129, CWE20, CWE606.
This checker is very similar to TAINTED_ARRAY_INDEX, except that it finds situations where array access with tainted index value happens through a pointer (TAINTED_ARRAY_INDEX is emitted only when the array is identified explicitly by name).
There is a .MIGHT variant of this warning (TAINTED_INT.PTR.MIGHT).
void set_base(char* base) { // Here 'base' an unknown pointer that probably points to an array.
char* env = getenv("QQQ");
size_t size = strtoul(env, NULL, 10);
base[size] = '\0'; // Accessing an array through pointer `base` by tainted index `size`.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Suppressed | Major | High | Yes |
C/C++ | Suppressed | Major | High | Yes |
Scala | Suppressed | Major | High | Yes |
Related CWEs: CWE121, CWE122, CWE129, CWE20, CWE606.
.MIGHT variant of TAINTED_INT.PTR, which checks if out-of-bounds array access could happen on some execution paths.
void set_base(char* base, bool flag) { // Here 'base' an unknown pointer that probably points to an array.
char* env = getenv("QQQ");
size_t size = strtoul(env, NULL, 10);
if (flag) {
base[size] = '\0'; // Accessing an array through pointer `base` by tainted index `size`.
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Critical | Very high | Yes |
C/C++ | Quality | Critical | Very high | Yes |
Scala | Quality | Critical | Very high | Yes |
Go | Quality | Critical | Very high | Yes |
Python | Quality | Critical | Very high | Yes |
The checker TAINTED_PTR
finds situations where a string
received from external source (from a file or from the network) is used
in an operation where uncontrolled string (or unbounded size of the
string) may cause problems, such as copying to a fixed-size array.
There is a .MIGHT variant of this warning TAINTED_PTR.MIGHT.
char* env;
char buf[100];
void test() {
env = getenv("VAR3");
// Copying a string of unbounded size to a fixed-size buffer.
strcpy(buf, env);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Suppressed | Critical | High | Yes |
C/C++ | Suppressed | Critical | High | Yes |
Scala | Suppressed | Critical | High | Yes |
.MIGHT variant of TAINTED_PTR, which find situations where string is tainted only on some execution paths.
char* env;
char buf[100];
void test(bool flag) {
if (flag) {
env = getenv("VAR3");
} else {
env = "NONE";
}
// Copying a string of unbounded size to a fixed-size buffer.
strcpy(buf, env);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
C/C++ | Quality | Major | Unknown | No |
Scala | Quality | Major | Unknown | No |
Go | Quality | Major | Unknown | No |
Related CWEs: CWE20.
It is .COND subtype of TAINTED_PTR
for situations where called function pass tainted value to critical
operations only for some paths.
const char* env;
char buf[100];
#define COPY_MODE 10
void copyIfNeeded(const char*ptr, int mode) {
if (mode == COPY_MODE)
strcpy(buf, ptr);
}
char* getEnv() {
return getenv("CODE10");
}
void test(int mode) {
env = getEnv();
// Copying a string of unbounded size to a fixed-size buffer.
copyIfNeeded(env, mode);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
C/C++ | Quality | Major | Unknown | No |
Scala | Quality | Major | Unknown | No |
Go | Quality | Major | Unknown | No |
Related CWEs: CWE20.
It is subtype of TAINTED_PTR
with both features .MIGHT and .COND. So this checker
emits situations where value is tainted only on some paths and it is
passed to function, which uses this value in critical operation not on
all paths.
const char* env;
char buf[100];
#define COPY_MODE 10
void copyIfNeeded(const char*ptr, int mode) {
if (mode == COPY_MODE)
strcpy(buf, ptr);
}
char* getEnv() {
return getenv("CODE10");
}
void test(int mode) {
env = getEnv();
if (mode<0)
env = "-";
copyIfNeeded(env, mode);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Unknown | Yes |
This checker finds uses of library function sprintf()
that may overflow the destination buffer, because some values (integer
values or strings) printed according to a constant format string are
tainted and unchecked.
size_t size;
char dst[5];
void test() {
char* env = getenv("QQQ");
size = strtoul(env, NULL, 10);
sprintf(dst, "%d", size);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Critical | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Scala | Quality | Critical | High | Yes |
Python | Quality | Critical | High | Yes |
Related CWEs: CWE120, CWE134, CWE20.
The checker TAINTED_PTR.FORMAT_STRING
finds situations
where a string received from external source (from a file or from the
network) is used as a format string parameter in functions of
printf()
family. This vulnerability may be used in a format string
attack.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
Related CWEs: CWE565.
This checker finds situations where value of a web cookie is used in a condition, thus controlling the execution flow of the application. Cookie values can be easily stolen or modified by malicious users, therefore using them to control execution flow makes application vulnerable to cookie spoofing attack.
import org.apache.commons.lang3.StringUtils;
import org.springframework.web.bind.annotation.*;
class Example {
public boolean login(
@RequestParam String username,
@RequestParam String password,
@CookieValue(value = COOKIE_NAME, required = false) String cookieValue) {
if (StringUtils.isEmpty(cookieValue)) {
return credentialsLoginFlow(username, password, response);
} else {
return cookieLoginFlow(cookieValue);
}
}
}
In function login
, parameter cookieValue
is
bound to a value of web cookie via @CookieValue
annotation.
This parameter is used to direct user-authorization flow via emptiness
test. Thus, if the user manipulates the value of this cookie, he can
bypass authorization and gain control over application and the data it
uses.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
Related CWEs: CWE918.
This checker finds situations where a web request parameter is used as URL (or a part thereof) for accessing an external resource, thus creating a possibility of server-side request forgery attack.
import java.util.List;
import java.util.ArrayList;
import java.io.IOException;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.web.bind.annotation.RequestParam;
class Example {
List<String> links(@RequestParam String url) throws IOException {
List<String> result = new ArrayList<String>();
Document doc = Jsoup.connect(url).get();
Elements links = doc.select("a");
for (Element link : links) {
result.add(link.absUrl("href"));
}
return result;
}
}
In function links
, parameter url
is bound
to web request parameter via @RequestParam
annotation. This
parameter is used as URL to fetch a remote document, which is parsed and
used to construct the function’s return value. As web request parameters
are controlled by the user, the latter can exploit this function to
import unexpected data into application and use the outcome to his
advantage.
Buffer overflow occurs when a program writes more data to a buffer (a temporary storage area in memory) than it can hold. This can lead to the excess data overwriting adjacent memory locations, potentially corrupting or altering the data stored there.
Attackers can exploit this vulnerability by deliberately crafting input data that exceeds the buffer’s capacity, allowing them to overwrite critical data, manipulate program behavior, or even execute arbitrary code. This can lead to a range of security issues, including crashes, denial of service attacks, and remote code execution.
To mitigate buffer overflow vulnerabilities, developers should use secure coding practices such as input validation and bounds checking.
In memory-safe programming languages, such as Java, the corruption is prevented by built-in memory management and bounds checking. However, if a program tries to access an element in an array, list, or other data structure using an index that is outside the valid range of indices for that data structure, a runtime exception is thrown, which can lead to unexpected changes in program behaviour including denial of service.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Low | Yes |
C/C++ | Quality | Critical | Low | Yes |
Go | Quality | Critical | Low | Yes |
Related CWEs: CWE119, CWE121, CWE124, CWE127, CWE194, CWE195.
The checker STATIC_OVERFLOW
finds situations where a
fixed-size array is accessed at a constant index outside its range.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Go | Quality | Critical | High | Yes |
Python | Quality | Critical | High | Yes |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE127, CWE194, CWE195.
The checker DYNAMIC_OVERFLOW
finds situations where a
dynamic array is accessed at a constant index outside its range.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
C/C++ | Quality | Critical | Unknown | No |
Scala | Quality | Critical | Unknown | No |
Go | Quality | Critical | Unknown | No |
The detector finds situations where a memory at the pointer was dynamically allocated with tainted size, and after this memory was accessed without checking. This may lead to a buffer overflow.
void func(FILE*f) {
int n;
// Variable 'n' is tainted because it given by external source
fscanf(f, "%d", &n);
// Pointer 'ptr' created using tainted data
char* ptr = malloc(n);
// Buffer access may lead to buffer overflow
ptr[30] = 0;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Very low | Yes |
C/C++ | Quality | Critical | Very low | Yes |
Python | Quality | Critical | Very low | Yes |
Related CWEs: CWE119, CWE121, CWE122, CWE126.
Path sensitive version of the DYNAMIC_OVERFLOW. It checks if a path exists where dynamic array is accessed at a constant index outside its range.
void buf_overflow(int a) {
char *buf;
if (a > 0)
buf = (char *) malloc(1024);
else
buf = (char *) malloc(512);
if (a > 0)
buf[1024] = 0;
else
buf[511] = 0;
free(buf);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Low | Yes |
C/C++ | Quality | Critical | Low | Yes |
Related CWEs: CWE119, CWE121, CWE124, CWE127, CWE194, CWE195.
This checker finds buffer overflow situations where a buffer is accessed (with potential overflow) in the same procedure where it’s locally defined.
void access_buf(int index) {
int buf[10];
buf[index] = 7;
}
void run() {
int i;
if (i >= 10)
access_buf(i);
}
Here, the situation is detected interprocedurally (the condition that
index
is outside bounds is specified outside function
access_buf
). Warning of this type is emitted, because
buffer access operation is in the same procedure as definition of the
buffer.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Very low | No |
C/C++ | Quality | Critical | Very low | No |
This checker finds buffer overflow situations where a buffer is passed into another procedure before being accessed (with potential overflow).
Since buf
is only 10 elements long, the access operation
in procedure access_buf
overflows the buffer. This
situation is detected interprocedurally, since the size of the buffer is
not available inside procedure access_buf
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
C/C++ | Quality | Critical | Unknown | No |
Go | Quality | Critical | Unknown | No |
This checker finds overflows for heap buffers where a buffer is passed into another procedure before being accessed (with potential overflow).
void access_buf(int* buf)
{
buf[10]=0;
}
void test() {
int* buff = new int[10];
access_buf(buff); // Overflow.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Very high | Yes |
Related CWEs: CWE121.
This checker finds situation where a function from
scanf()
family is used to read data into a buffer of known
size, but the constant format string and possible values of the source
of the data are such that the buffer could overflow as a result of the
call.
Since the number of bytes read from the stream f
is not
restricted, the buffer can overflow. One way to fix this defect is to
specify the maximum number of characters to be read explicitly:
void avoid_overflow(FILE* f) {
char buf[50];
fscanf(f, "%49s", buf); // `buf` can't overflow: bounds specified in format string.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
Visual Basic .NET | Quality | Major | Unknown | Yes |
C/C++ | Quality | Critical | Unknown | No |
C# | Quality | Major | Unknown | Yes |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE127, CWE194, CWE195.
For C/C++ this checker detects usages of memcpy
,
strcpy
and similar standard library functions that may
write beyond the end of destination buffer.
For C# this checker detects potential overflows of allocated unmanaged memory buffers on certain execution paths. It reports a warning when a buffer may be accessed (read or written) beyond its right bound. The checker reports a warning if the allocation expression is known and it can’t prove that the access is safe.
void buffer_overflow(char*p, int n) {
int buf[10];
// Last copied byte will be written outside of buf
memcpy(p, buf + 3, sizeof(int) * 7 + 1);
}
In the example above memcpy will write one byte after the end of
array buf
, corrupting data on stack.
using System;
using System.Runtime.InteropServices;
IntPtr hglobal = Marshal.AllocHGlobal(5); // ℹ️〔function System.Runtime.InteropServices.Marshal.AllocHGlobal(int) allocated buffer of size 5〕
int[] buffer = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
Marshal.Copy(buffer, 0, hglobal, 1);
Marshal.Copy(buffer, 0, hglobal, 2); // ⚠️〔BUFFER_OVERFLOW Writing 2 elements of type int into buffer hglobal can exceed its size (5 bytes)〕
Marshal.FreeHGlobal(hglobal);
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Go | Quality | Critical | Average | No |
Python | Quality | Critical | Average | Yes |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE125, CWE126.
This checker detects potential overflows of fixed-size arrays on certain execution paths. It reports a warning when an array may be accessed beyond its right bound. Out-of-bounds array access leads to Undefined Behaviour (C/C++), Runtime Exception (Java), or run-time panic (Go).
enum Type {
TYPE_ONE,
TYPE_TWO,
TYPE_THREE,
TYPE_INVALID
};
enum Type get_type (int data) {
if (data == 100)
return TYPE_ONE;
if (data == 200)
return TYPE_TWO;
return TYPE_INVALID;
}
const char* get_name (const char *n[], enum Type type) {
return n[(int)type];
}
const char* example (int data, int flag) {
const char* names[3] = {"First", "Second", "Third"};
enum Type type = flag ? TYPE_THREE : get_type(data);
// overflow in `get_name` occurs when type is TYPE_INVALID
return get_name(names, type);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Unknown | No |
This checker detects a buffer overflow when return value of
strlen
, read
or similar functions used in
buffer access offset calculation is subtracted from, resulting in
possible negative offset:
void test1(const char* buf) {
int len = strlen(buf);
// Access at -1 if buf = { '\0' }
if(buf[len - 1] == 'a') {
return;
}
}
In this example if the string buf
contains only a
\0
character (and thus is of zero length) an access outside
of the buffer will occur.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Low | No |
C/C++ | Quality | Critical | Low | No |
Go | Quality | Critical | Low | No |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE125.
This checker detects potential overflows of fixed-size arrays on
certain execution paths, when using memcpy
,
strcpy
and similar functions.
void overflow(dhcp_msg *msg, int cnt) {
int n;
char buf[10];
char other_buf[100];
if (cnt < sizeof(other_buf)-1) {
n = cnt;
} else {
n = sizeof(other_buf)-1;
}
// writing in buf instead of other_buf for which the size was calculated
memcpy(buf, msg, n);
}
In this example amount of copied data n
is calculated
for other_buf
and may be equal to 99 on either of the
branches if cnt
is sufficiently large. This will result in
out of bounds writes to buf
and will corrupt data on
stack.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Low | No |
C/C++ | Quality | Critical | Low | No |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE125.
This checker is similar to BUFFER_OVERFLOW.EX, but detects when
overflow happens inside of function because of an illegal argument
value. Less reliable reports are issued with a BUFFER_OVERFLOW.PROC.STRICT
warning subtype.
void fill(char * p, int len) {
for (int i = 0; i < len; i++) {
// Buffer overflow occurs if len is bigger than size of buffer
p[i] = 'a';
}
}
int overflow() {
char bufferInput[50];
test(bufferInput, 1000); // This call is bad
test(bufferInput, 30); // No warning here
return 0;
}
In this example function fill
may write beyond the end
of the provided array if the value of variable len
is
larger than the actual size of the buffer.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Unknown | Yes |
Related CWEs: CWE120, CWE121, CWE122, CWE124, CWE134, CWE20.
This checker detects buffer overflows when using sprintf
and snprintf
function without limiting %s
conversion specification maximum number of characters to be printed:
For sprintf
function checker detects usages of
%s
conversion without a precision modifier, which may lead
to a buffer overflow if the corresponding argument is a sufficiently
long string.
void with_sprintf(char* str) {
char buf[10];
sprintf(buf, "%s", str); // possible overflow if str is longer than 10
sprintf(buf, "%.10s", str); // everything is fine
sprintf(buf, "%10s", str); // possible overflow with str of any length
}
For snprintf
function the checker additionally takes
into account the function argument limiting the size of the output
buffer.
void with_sprintf(char* str) {
char buf[10];
snprintf(buf, 20, "%s", str); // possible overflow if str is longer than 10
snprintf(buf, 10, "%s", str); // everything is fine
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
This checker finds situation when constant buffer is accessed using a non-constant index that potentially can have arbitrary large value.
void index(int i) {
int buf[21];
buf[i] = 6; // overflow, non-constant index i accesses buffer of constant size
if(i<15) {
buf[i] = 6; // no warning, because value was checked
}
}
void with_strlen(char *s) {
int a = strlen(s);
char buf[100];
buf[a] = 0; // overflow if string is sufficiently large
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | No |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE125.
This checker is similar to BUFFER_OVERFLOW.LIB.EX, but can infer buffer sizes when using string literals for initialization.
void overflow() {
char a[5];
char b[] = "7777777"; // This buffer has size 7 + 1
strcpy(a, b); // overflow, copying buffer of size 8 to buffer of size 5
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
C/C++ | Quality | Critical | Unknown | Yes |
Go | Quality | Critical | Unknown | No |
Python | Quality | Critical | Unknown | Yes |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE125, CWE127, CWE194, CWE195.
This checker detects potential buffer underflows on certain execution paths. It reports a warning when an array may be accessed beyond its left bound (with negative index). Out-of-bounds array access leads to Undefined Behaviour (C/C++), Runtime Exception (Java), or run-time panic (Go).
unsigned a[1024];
void example(int x) {
int len, i, j;
for (i = 0; i < 1024; i += 4)
{
len = x++ / 2;
for (j = 0; j < len; j++)
a[i+j] &= 0xfffffffe;
// buffer underflow happens on 1st iteration if (x == 0)
a[i+(j-1)] |= 1;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Average | Yes |
Python | Quality | Major | Average | Yes |
Related CWEs: CWE119, CWE124, CWE129, CWE394.
The checker CHECK_AFTER_OVERFLOW
finds situations where
first, an array element with a certain index is accessed, and then this
index is compared to a value that indicates that the index may lie
outside the allowed range.
In this example, the check at the end shows that i
is
expected to sometimes have a value of 256, in which case the buffer
access above will be out of bounds.
A (suppressed) CHECK_AFTER_OVERFLOW.MACRO
subtype of this warning type is emitted if it is suspected that the
warning is a false positive, caused by the conditional expressions being
implemented within a macro. Such checks can be too general and not
always reflect possible value ranges of index variables.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Go | Quality | Normal | Unknown | No |
The checker finds situations were the array length (obtained using
built-in function len()
) is compared to 0
after some element of the same array has been already accessed. Such
situation can potentially lead to an out of range access and an
unexpected run-time panic.
func foo(arr []byte, x int) {
var _ byte = arr[x]
// Assumption that len of array 'arr' may be 0, so expression 'arr[x]' could cause a buffer overflow
if len(arr) == 0 {
fmt.Println(x)
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Python | Quality | Critical | Average | Yes |
Related CWEs: CWE119, CWE121, CWE124.
The checker OVERFLOW_AFTER_CHECK
finds situations where
first, a variable is compared to some value, indicating what the
possible values of the variable are, and then it’s used to access a
buffer in such a way that one of the possible values accepted by the
check lies out of bounds.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Go | Quality | Critical | High | No |
Python | Quality | Critical | High | Yes |
Related CWEs: CWE119, CWE121, CWE124.
The checker is similar to OVERFLOW_AFTER_CHECK, but uses SMT solver and is capable of finding and filtering out more complex defects.
struct A {
int x[10];
int y;
};
struct A array[10];
void func(struct A *q) {
int i;
for (i = 0; i <= 10; i++) {
array[i] = q[i]; // `i` may be 10, but the last index is 9.
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Go | Quality | Critical | Unknown | No |
This checker detects access to buffer that contradicts earlier
conditions using len()
.
In this example if array can have length 10, which is indicated by
if
condition. However later buffer is accessed at index 10,
which contradicts the check.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Unknown | No |
C/C++ | Quality | Minor | Unknown | No |
A checker fire a warning if some pointer is access by function under some check and after it without any check. Here we use heuristic that first check may be a buffer length.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
C/C++ | Quality | Critical | Average | Yes |
Go | Quality | Critical | Average | Yes |
Python | Quality | Critical | Average | Yes |
Related CWEs: CWE119, CWE121, CWE122, CWE124, CWE194, CWE195.
Issues of this type are detected when the value of an index used to access an array element is compared with a bound that is less strict than necessary. A bound on an index value can be used to ensure the absence of buffer overflows due to index value being too large or too small, but if the bound is inconsistent with the array size, a buffer overflow can still occur. For example, if an array has 10 elements, index values up to 9 are permitted, but checking that the index is less than 20 allows values between 10 and 19 that would lead to buffer overflow.
A less reliable warning subtype OVERFLOW_UNDER_CHECK.COND
is reported when the analyzer detects a bound condition on the value of
a variable used for computation of an element access index of an array
of unknown size but not on the resulting value of the index itself.
int buf[10];
if (i < 20)
buf[i] = 3; // Possible buffer overflow.
if (i >= -1)
buf[i] = 5; // Possible buffer overflow.
for (i = 0; i < 100; ++i)
buf[i] = 7; // Possible buffer overflow.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
C/C++ | Quality | Critical | Unknown | Yes |
Related CWEs: CWE119, CWE121, CWE124.
The checker is similar to OVERFLOW_UNDER_CHECK, but overflow is occurred inside library function call.
In this example OVERFLOW_UNDER_CHECK.LIB
will be
reported instead of OVERFLOW_UNDER_CHECK
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | Yes |
Related CWEs: CWE119, CWE121, CWE124.
Special subtype of OVERFLOW_UNDER_CHECK.LIB, where
overflow is occurred inside memcpy
function call.
void func(char* p, int len) {
char buf[100];
if (len > 101)
return;
memcpy(buf, p, len); // Error.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Critical | High | Yes |
Go | Quality | Critical | High | Yes |
Related CWEs: CWE119, CWE121, CWE124.
The checker is similar to OVERFLOW_UNDER_CHECK, but overflow is occurred inside a function, and check is performed before a function call.
void access(int index) {
int array[100];
array[index] = 0;
}
void func(int index) {
if (index < 200)
access(index); // Error.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
Related CWEs: CWE120, CWE121, CWE122, CWE170, CWE783.
The checker BUFFER_SIZE_MISMATCH
finds situations where
a size parameter passed to “safe” versions of standard functions (such
as strncpy
or memset
) is unsafe (out of local
buffer bounds).
The last parameter for strncpy()
should’ve been
sizeof(dst) - 1
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Unknown | Yes |
Similar to BUFFER_SIZE_MISMATCH, but this type of warning is emitted for the particular case when possibly nonterminated string is passed as an argument to a standard function that requires this argument to be null-terminated.
void example(char *src) {
char buf[100], dst[4000];
strncpy(buf, src, sizeof(buf)); // buf may be not null-terminated
strncpy(dst, buf, sizeof(dst)); // overflow of buf will happen in this case
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Average | Yes |
The checker BUFFER_SIZE_MISMATCH.STRICT
finds situations
where a size parameter passed to “safe” versions of standard functions
(such as memset_s
, sprintf_s
) is not constant
and may be out of bounds.
char dst[10];
void test_bad(int size, char* src) {
memcpy_s(dst, size, src, strlen(src));
}
void test_good(char* src) {
memcpy_s(dst, 10, src, strlen(src));
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
The checker DYNAMIC_SIZE_MISMATCH.STRICT
finds
situations where a size parameter passed to “safe” versions of standard
functions (such as memset_s
, sprintf_s
) is not
constant and may be out of bounds. Unlike BUFFER_SIZE_MISMATCH.STRICT it
emits warnings for buffer from heap.
void test_bad(int size, char* src) {
char* dst = malloc(100);
memcpy_s(dst, size, src, strlen(src));
}
void test_good(char* src) {
char* dst = malloc(10);
memcpy_s(dst, 10, src, strlen(src));
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Major | High | Yes |
Related CWEs: CWE120, CWE121, CWE122, CWE170.
The checker DYNAMIC_SIZE_MISMATCH
finds situations where
a size parameter passed to “safe” versions of standard functions (such
as strncpy
or memcpy
) is unsafe (out of
dynamic buffer bounds).
The last parameter for strncpy()
should’ve been
sizeof(dst) - 1
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
Related CWEs: CWE120, CWE131, CWE170, CWE783.
This checker reports warnings for instances of pointer assigned memory allocations where the pointer’s target type is larger than the block allocated.
struct Person {
int age;
char name[20];
};
struct Person *foo(void) {
struct Person *ptr;
ptr = (Person *) malloc(sizeof(ptr)); // Mismatched allocation size.
return ptr;
}
In this example, the allocation is too small for the pointer’s target
type — sizeof(*ptr)
was probably intended.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | No |
This checker reports warnings for C++ allocations using operator
new
when direct initialization is used incorrectly instead
of the dynamic array size specification.
This allocates memory for a single integer and initializes it with value 10 instead of allocating memory for 10 integer numbers.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
Related CWEs: CWE783.
This checker reports the same issue as ALLOC_SIZE_MISMATCH but for
memset
and similar functions instead of allocations.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | Yes |
This checker issues a report when memset
is apllied to a
type that is not trivially copyable.
(MEMSET_TO_NON_TRIVIALLY_COPYABLE/example.cpp)
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Very high | Yes |
Related CWEs: CWE119, CWE121, CWE124, CWE170.
This checker finds situations where a standard C library function
readlink
is used in an unsafe way. This function receives a
buffer start address and its size as arguments and returns the number of
bytes placed in the buffer. Since it doesn’t append a terminating null
character to the buffer the callee usually does this to create a string.
Doing so in a straightforward way by assigning a null value to the
buffer’s byte with an index equal to the returned value may lead to an
“off-by-one” buffer overflow in case the readlink
function
returns a value equal to the buffer size.
char buf[128];
void overflow() {
int len = readlink("/mnt/modules/pass1", buf, sizeof(buf));
if (len != -1) {
// `len` may be = `sizeof(buf)` = 128.
buf[len] = 0; // Emitted READLINK_OVERFLOW.
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This C-specific checker finds potential buffer overflows due to
calling POSIX standard functions to receive messages from sockets
recv
and recvfrom
with mismatched parameters
specifying the start address of a buffer, where the message is to be
stored, and its size.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | High | Yes |
The checker NONTERMINATED_STRING
finds situations where
“safe” versions of standard library functions working with C strings may
create non-null-terminated strings as a result. Working with such
strings as with regular ones, e.g. passing them as standard library
functions arguments, can lead to out-of-bounds memory access.
In this example even though buffer overflow won’t occur directly
executing the following code, it may create a non-null-terminated
string, which may lead to buffer overflow later, when the string is
accessed again. For example, if buffer
is a 300-character
string, the strncpy
call copies 256 bytes to the
name
field (which is declared as
char name [256]
), but there is no space left for a null
terminator.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This checker is similar to NONTERMINATED_STRING, but doesn’t assume that destination memory is zero-filled. It is reported when copying a smaller string into a bigger buffer without adding a null terminator. This can lead to unintended addition of a suffix to the copied string or nonterminated string if the destination buffer isn’t null-terminated.
When string characters are copied using memcpy
or
related functions instead of strcpy
or other string-related
functions a subtype NONTERMINATED_STRING.STRICT.MINOR
is emitted. While such functions can still be used for working with
strings they are more commonly used to process other data that shouldn’t
be null-terminated.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | High | Yes |
The checker finds patterns when the result of a strlen
standard C library function call is used in a memcpy
function call as second parameter representing the number of copied
bytes. Since the strlen
function doesn’t count the null
terminator and the memcpy
function doesn’t add it to the
end of the resulting string, this pattern results in potential creation
of a non-null-terminated string. Using such string as a regular string,
e.g. in calls to standard library string functions, can lead to
out-of-bounds memory access.
In the following example the dst
buffer is filled with
the contents of the prog->String
string but without
adding the null terminator at the end of the copied characters so if
dst
isn’t zero-filled originally the resulting string will
be non-null-terminated:
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
This checker finds situations where “safe” versions of standard functions working with C strings may create non-null-terminated strings as a result of using length parameter coming from untrusted source. Working with such strings as with regular ones, e.g. passing them as standard library functions arguments, can lead to out-of-bounds memory access.
In the following example local buffer keys_path
(with
size 4096 bytes) is filled by a strncpy
function call using
the value of an external environment variable
ADB_VENDOR_KEYS
as source data. If all 4096 characters are
copied then no space remains for a null terminator and when this
variable is later used as a string a buffer overflow may happen:
adb_keys_path = getenv("ADB_VENDOR_KEYS");
if (!adb_keys_path)
return;
strncpy(keys_path, adb_keys_path, sizeof(keys_path));
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Very high | Yes |
Related CWEs: CWE120, CWE121, CWE122, CWE127.
This checker finds situations where a call to a string copying function can lead to a buffer overflow if the source string is larger than the destination buffer.
In this example the command
buffer has only space for 99
characters (and a null terminator) but is filled without checking for an
overflow with the help of standard strcpy
and
strcat
functions using file paths that can be much
longer:
char command[100];
EST_String file1, file2;
file1 = make_tmp_filename();
file2 = make_tmp_filename();
...
strcpy(command, "cat ");
strcat(command, file1);
strcat(command, " | sed -f ");
strcat(command, sedfile);
strcat(command, " > ");
strcat(command, file2);
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Very high | No |
Related CWEs: CWE120, CWE121, CWE122.
This checker is a variant of STRING_OVERFLOW for potential buffer
overflows caused by calling of a string copying function when the the
source string is larger than the destination buffer, and when the source
string is a parameter of a function being analyzed. In this case the
function has (possibly implicit) contract that the source string should
not be larger than the destination buffer and the function caller is
responsible for making sure to fulfil it which can be hard to achieve.
To avoid such situations it’s better to check for valid source string
length inside of the function, e.g. by using the strncpy
string copying function with a string length constraint.
void error_tag(char error_string[], int error_number) {
if (error_number != 0) {
char error_buffer[100];
strcpy(error_buffer, error_string); // FLAW: copying to fixed-size buffer without
// checking source string length
if (error_number > 4) {
strcpy(error_string, "Error ");
} else {
strcpy(error_string, "Warning ");
}
strcat(error_string, error_buffer);
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | No |
The checker finds situations of potential overflow where string length is used to check possible size of buffer.
void example(char* q) {
int len = strlen(q);
if (len > 0) {
struct S*s = (struct S*)q;
s->x = 1; // Potential overflow if len < sizeof(struct S).
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Security | Minor | Unknown | No |
This C-specific checker reports potential buffer overflows when
writing strings using binary output functions fwrite
and
write
which don’t check for the null terminator. It is
based on the FIO18-C
rule from the CERT Secure Coding Standard.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Low | No |
This C-specific checker reports non-portable initialization of a
structure field using standard memcpy
function where the
start address of the field is calculated manually using
sizeof
s of the preceding structure fields instead of the
portable offsetof
macro.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Security | Minor | Unknown | No |
Related CWEs: CWE785.
Incorrect usage of the standard C library function
realpath
to get a canonicalized absolute pathname may cause
buffer overflow vulnerability. The POSIX.1-2001 standard version of
realpath
library function is broken by design, since it is
impossible to determine a suitable size for the output buffer. According
to POSIX.1-2001 a buffer of size PATH_MAX
suffices, but
PATH_MAX
need not be a defined constant, and may have to be
obtained using a pathconf
call. And asking
pathconf
does not help, since, on the one hand POSIX warns
that the result of pathconf
may be huge and unsuitable for
mallocing memory, and on the other hand pathconf
may return
-1
to signify that PATH_MAX
is not bounded.
POSIX.1-2008 allows passing a NULL
pointer as a second
argument of realpath
, allowing this design problem to be
avoided. This is the recommended secure way of using
realpath
.
This section contains detectors for various types of defects primarily related to manual management of memory and other resources. In the case of memory, such errors can lead to its corrupted state, which may go unnoticed for a long time and cause unexpected behavior of the program, or even a crash when finally detected by the operating system.
One of the most common examples of such
issues is when an invalid address is passed to a memory deallocation
function such as standard C library free
. Such functions
expect only exactly those addresses that are returned by the respective
memory allocation functions. Even if the address passed as an argument
belongs to the allocated memory block it is still an invalid parameter
value for a memory deallocation function and using it this way leads to
an undefined behaviour that commonly manifests in abnormal program
termination.
In other cases, if the program does not release the resources it has acquired, including memory, file handles, database connections, etc., after their use, it can lead to exhaustion of the resources available to the program or the entire system, which in turn can result in a slowdown of its performance or denial of service. Such issues are commonly called “resource leaks” or “memory leaks” in case of memory.
Another large group of
detectors are related to situations where a program continues to use a
resource (such as memory, file handle, or other) after it has been
released or deallocated. This can lead to undefined behavior, crashes,
or security vulnerabilities. Sometimes the cause of such problems lie in
logical errors in the application, and sometimes in the programmer’s
incorrect understanding of how resource deallocation functions work. For
example, the memory deallocation operator delete
in C++
does not reset the pointer value — it remains the same, but after
executing this operator it cannot be dereferenced; doing so leads to
undefined behavior.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
Related CWEs: CWE590.
This checker finds situation where an address of a non-heap memory block is passed to a memory deallocation function, where only addresses to heap memory, returned by memory allocation functions, are allowed. Trying to do so usually results in operating system terminating the program prematurely.
static int stats_file_history_update(struct stats_file *data_file)
{
struct stats_file _history_file, *history_file;
history_file = &_history_file;
...
err = stats_open_temp(temp_file);
if (err < 0) {
stats_free(history_file); // FLAW: freeing of non-heap memory
return err;
}
...
static void stats_free(gpointer user_data)
{
struct stats_file *file = user_data;
if (!file)
return;
...
g_free(file);
}
In the example above the local pointer history_file
is
assigned an address of a local structure variable
_history_file
, allocated on stack. In the subsequent code
an error situation, represented by a negative value returned by a
stats_open_temp
function call, is handled by calling a
stats_free
function with the value of pointer
history_file
as an argument. The stats_free
function in turn calls a g_free
function in the end which
deallocates the memory which address is passed as its parameter. This
way an address of a memory block allocated on stack is passed to the
memory deallocation function which can lead to abnormal program
termination.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Very low | Yes |
This path-sensitive version of checker FREE_NONHEAP_MEMORY finds situations where there is a reachable path from initialization of a pointer with an address of a non-heap memory block to a memory deallocation function call where this pointer value is used. When execution runs along this path this call may result in abnormal program termination since such values are invalid for memory deallocation functions working only with heap addresses.
void cond_free(int *p, int cond) {
if (cond) {
free(p);
}
}
void free_nonheap_ex(int x) {
int local_array[] = {2, 3};
cond_free(local_array, x); // FLAW: potential free of non-heap memory
}
In the example above when function free_nonheap_ex
is
called with a parameter x
value other than 0
,
address of a local array local_array
which is passed as the
first argument of the cond_free
function call is in turn
passed as an argument to the memory deallocation function
free
. Such address on the stack isn’t valid for the
free
function and this call causes undefined behaviour
usually manifesting in abnormal program termination.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Very low | No |
This checker finds situation where a pointer passed to a memory deallocation function was obtained as a result of an arithmetic operation. This can result in an invalid value for a memory deallocation function. Though sometimes the allocated memory address is shifted by a fixed offset intentionally and the correct address of its beginning could be reconstructed using reverse arithmetic operations, this is potentially a cause for incorrect values, passing which to memory deallocation functions as arguments may lead to undefined behaviour that commonly manifests in abnormal program termination.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Very low | No |
This checker finds suspicious pointer arithmetic on the result of a memory allocation function call. While semantically such code is correct this is a common typo when because of a misplaced parenthesis a value is added or subtracted from the allocated memory address instead of the allocation size argument, as in the example below. Such typo results in both incorrect size of the allocated memory block which later can lead to a buffer overflow, when this memory is used, and also to an invalid address to the allocated memory block start that can’t be passed to the memory deallocation function.
// Allocate memory for the DMA buffers
if((dma_buffer1 = (char *)JMALLOC(DMA_BUFFER_SIZE * 2) + 32) == (char *)0)
{
fprintf( stderr,"Can not allocate memory for DMA buffers\n");
exit(0);
}
In this example because of a misplaced closing parenthesis the
32
constant is incorrectly added to the return value of the
JMALLOC
memory allocation function instead of to the value
of the DMA_BUFFER_SIZE * 2
expression, which represents the
amount of memory to be allocated.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Unknown | Yes |
Related CWEs: CWE119.
It emits warnings for arithmetic operation on the results of heap allocation functions. This might be a common typo when a closing brace is mistakenly placed before a part of the arithmetic expression for allocation size calculation. Such typo results in both incorrect size of the allocated memory block which later can lead to a buffer overflow, when this memory is used, and also in an invalid address to the allocated memory block start that can’t be passed to the memory deallocation function.
char *mystrcpy(char *str) {
char *ret = malloc(strlen(str)) + 1; // Typo.
for (; *str;) *ret++ = *str++;
char *copy = ret;
*copy = 0;
return ret;
}
char *mystrcpy(char *str) {
char *ret = malloc(strlen(str) + 1); // Correct size allocation for the whole string and zero terminator.
for (; *str;) *ret++ = *str++;
char *copy = ret;
*copy = 0;
return ret;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Average | No |
This checker finds situations where a NULL
pointer is
passed to the standard library function free
. While it is
not an issue in itself, such code is useless as in this case no
operation is performed, and it might mean a problem with the program
logic such as deallocating a wrong pointer, misplacing the deallocation
function call or other.
if((*root)->right == NULL && (*root)->left == NULL){
*root = NULL;
free(*root); // FLAW: free of NULL
}
In this example the code author accidentally placed the assignment of
a NULL
value to the *root
pointer before
calling the free(*root)
function. This mistake leads to not
only calling of the free
function with a NULL
value as an argument which produces no operation but also to not
deallocating the memory that is no longer used, i.e. a memory leak.
cdata = eina_hash_find(item_custom_map, &obj);
if (NULL == cdata) {
eina_hash_del(item_custom_map, &obj, cdata);
free(cdata); // FLAW: useless free of NULL
}
In this example the free
is called with the value of the
cdata
variable only if it has a null value, checked in the
condition of the if
statement. This is a useless call that
produces no operation in this case and is caused by an accidentally
inverted condition of the if
statement intended to check
for the cdata
variable having a non-null value instead.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
Related CWEs: CWE415, CWE416, CWE672.
This checker finds situations where a memory location is accessed through a pointer that has been already deallocated. This is a critical yet hard to detect issue, because after the memory is deallocated, the operating system or memory manager may not immediately reclaim that memory. Instead, it may mark it as free and allow it to be reused for other purposes. When the program dereferences the pointer before that memory is reused, it might still see the old data, leading to seemingly normal behaviour. But during the next program run the memory may be already reused by the time the dereference is performed and the program may read some completely unrelated data or even may be terminated by the operating system for trying to access now prohibited memory block.
modes = malloc(sizeof (RRModePtr));
if (!modes)
{
RRModeDestroy (mode);
FreeResource (mode->mode.id, 0); // FLAW: accessing a field of a structure in already deallocated memory
return NULL;
}
In this example the function RRModeDestroy
is called
with the value of a mode
pointer as its argument. The
function frees the memory which address is passed as its parameter, so
the mode
variable points to the deallocated memory after
the call. But this memory is accessed in the call to the
FreeResource
function next to calculate the value of its
first argument. This results in accessing already deallocated memory
which is undefined behaviour and may not lead to reading the expected
value. To fix such issue the value of the argument should be calculated
and stored in a temporary variable before the deallocation and the value
of this variable should be used as the call argument instead of the
deallocated memory access.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
This checker finds situations where the value of a pointer to a memory location that has been deallocated, is used in some way other than dereferencing it. While the analyzer can’t indicate that this value is dereferenced causing an undefined behaviour (see DEREF_AFTER_FREE), storing it or its derivative, which also can’t be dereferenced, may lead to dereference of an invalid pointer much later in the program execution.
Bmp8 * Bmp8::FromCharDumpFile(FILE *fp) {
// create a Bmp8 object
Bmp8 *bmp_obj = new Bmp8(0, 0);
if (bmp_obj == NULL) {
return NULL;
}
if (bmp_obj->LoadFromCharDumpFile(fp) == false) {
delete bmp_obj;
}
return bmp_obj; // FLAW: potential return of an invalid pointer
}
In this example if an error happens during the
bmp_obj->LoadFromCharDumpFile(fp)
method call, which is
indicated by it returning a false
value, then the memory,
where the variable bmp_obj
points to, is deallocated by a
delete
operator. But instead of returning a null value in
this case to propagate an error (as is done in the previous
if
statement of the Bmp8::FromCharDumpFile
method), the now invalid value of this pointer is returned by the
return
statement at the end of the method which will be
eventually dereferenced by the caller, causing undefined behavior.
pa_bool_t pa_source_output_new_data_set_formats(pa_source_output_new_data *data, pa_idxset *formats) {
pa_assert(data);
pa_assert(formats);
if (data->req_formats)
pa_idxset_free(formats, (pa_free2_cb_t) pa_format_info_free2, NULL);
data->req_formats = formats; // FLAW: storing of a potentially invalid address in a structure field
if (data->source) {
/* Trigger format negotiation */
return pa_source_output_new_data_set_source(data, data->source, data->save_source);
}
return TRUE;
}
In this example the function pa_idxset_free
actually
deallocates the memory which address is passed as its first argument,
the value of a formats
parameter of the
pa_source_output_new_data_set_formats
function in this
case. If this call is executed, then the now invalidated value of the
formats
parameter is stored into the
data->req_formats
field and may be dereferenced later
after the return from the function which will cause an undefined
behavior (see DEREF_AFTER_FREE). The
issue is caused by the code author incorrectly calling the
pa_idxset_free
function with the value of a
formats
parameter as its first argument instead of the
value of the data->req_formats
field.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Low | Yes |
Subtype of USE_AFTER_FREE for
situations where a pointer is passed as argument to the standard C
realloc
or another realloc
-like function that
potentially invalidates it but the original pointer value is used
afterwards instead of a new value, returned by the realloc
call. Dereferencing the original pointer value after calling
realloc
can lead to same effects as using a pointer value
after its deallocation (see DEREF_AFTER_FREE).
int reallocation = 0;
if (nrow > array_size) {
printf("Reallocating valid_red_vals for reduced validation\n");
realloc(valid_red_vals, sizeof(double) * nrow);
reallocation = 1;
}
validation_array("Reduced", nrow, vect_out, valid_red_vals, 6, rawtime); // FLAW: using of potentially invalid pointer
In this example the memory block, which variable
valid_red_vals
points to, is expanded by calling a
realloc
function. The issue with this code is that the
result of calling realloc
is not stored and the original
value of the valid_red_vals
variable is used in call of the
validation_array
function. But realloc
might
change the location of the allocated memory block during reallocation
and return its new address, invalidating the old one.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | Yes |
This checker finds situations where a pointer referencing already deallocated memory is passed to a function call as an argument. While the analyzer can’t point to the exact place where this value is dereferenced, causing an undefined behaviour (see DEREF_AFTER_FREE), it may lead to dereference of an invalid pointer in the called function or later during the program execution.
One special case of false PASSED_TO_PROC_AFTER_FREE.EX
reports is removing of invalid pointers from collections, such as
vectors and arrays. If this is done using a function call, the analyzer
may flag as an issue passing an invalid pointer value to this function
as an argument. While technically this is indeed use of an invalid
pointer value there is no danger in using its value unless it is
dereferenced.
if (lfd++==10) {
free(tmp);
vstringf (_ ("send_pseudo %s: cannot open tmpfile %s: %s"),
name, tmp, strerror (errno)); // FLAW: use of an invalid pointer
vstring ("\r\n");
return 1;
}
In this example the memory, which address is stored in the
tmp
variable, is deallocated by a free
function call. But the contents of this memory is used right after to
write an error message by calling a vstringf
function and
passing the now invalid pointer value tmp
as one of its
arguments. To fix this issue it is enough to move the free
function call after the vstringf
function call.
...
if (old) {
setlocale(LC_CTYPE, old);
free(old);
}
ic->next = next[0];
fs = fuse_fs_new(&iconv_oper, sizeof(iconv_oper), ic);
if (!fs)
goto out_iconv_close_from;
return fs;
out_iconv_close_from:
iconv_close(ic->fromfs);
out_iconv_close_to:
iconv_close(ic->tofs);
out_free:
free(ic->from_code);
free(ic->to_code);
free(ic);
if (old) {
setlocale(LC_CTYPE, old); // FLAW: use of a potentially invalid pointer
free(old);
}
return NULL;
Here if the value of the old
pointer is non-null this
value is first used as argument in the setlocale
function
call and then is deallocated by a free
function call.
Afterwards in case of an error situation, represented by a null return
value of the fuse_fs_new
function call, the control is
transferred to the out_iconv_close_from
label by the
goto
statement. But before the final return
statement of the function there is a block that again checks the value
of the old
pointer for being non-null. Since the
old
variable value hasn’t been changed in the body of the
first if
statement but the memory, which it points to, has
been deallocated there, the condition of the last if
statement is also evaluated as true, the body of the if
statement is executed and the now invalid value of the old
pointer is passed as a second argument to the setlocale
function call (and as an argument of the free
function call
afterwards). The correct way to fix this issue is to assign a
NULL
value to the old
variable after the
memory is freed in the first if
statement body - this way
the second if
statement check is evaluated as false and its
body isn’t executed.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Average | Yes |
Related CWEs: CWE415, CWE416, CWE672.
This checker finds situations where a heap memory block, that has already been deallocated, is deallocated again. This causes an undefined behavior, which is usually manifested as an abnormal program termination by the operating system. The most common way to safeguard against this situation is to explicitly assign a null value to the pointer, that holds the deallocated memory block address, after it has been deallocated. This way the deallocated memory address can’t be used later for repeated deallocation or for dereference.
void
rk_rules_free(struct rk_rule* rules)
{
struct rk_rule* p;
for (p = rules; p->lhs != NULL; p++) {
free((void *) p->lhs);
free((void *) p->rhs);
free((void *) p->follow);
}
free(rules);
}
In this example in case of an error situation first the function
rk_rules_free
is called with the value of the
rules
pointer as an argument and then the same
rules
pointer value is used as an argument for the
free
memory deallocation function call. But there is
already a free
function call at the end of the
rk_rules_free
function body that deallocates the memory,
which address is passed as its rules
parameter value. This
results in a situation where the same memory block is deallocated twice
- first at the end of the called rk_rules_free
function and
then in its callee.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | High | No |
This checker is designed to detect dangling pointers, i.e. such
pointers that can be accessed from the caller context after they have
been freed inside the callee. While storing such pointers doesn’t cause
an issue by itself, if they are used accidentally this can lead to hard
to diagnose issues (see DEREF_AFTER_FREE). To avoid such issues it
is advised to explicitly assign a NULL
value to the
pointers that are freed to show that they are actually not
initialized.
class UndoList : public std::list<Undo *>
{
public:
UndoList() : m_name(NULL) {};
virtual ~UndoList() { if ( m_name ) { free(m_name); } };
void setOpName( const char * name ) {
if ( m_name ) { free(m_name); } // FLAW: not resetting 'm_name' value can lead to use of a freed memory
if ( name ) { m_name = strdup(name); } };
const char * getOpName() const { return m_name; };
protected:
char * m_name;
};
In this example in the setOpName
method the value of the
m_name
field isn’t set to NULL
after it is
deallocated and at the same time it isn’t always assigned a new value
leaving the m_name
field potentially contain an invalid
memory address. This allows specific sequences of calls of this method
that can lead to dereference of an already deallocated memory, double
free of the same memory block or return of an invalid memory address
from the getOpName
method: for example, first
setOpName
is called with a non-null string
"Spherify"
as argument (this initializes the
m_name
field with a non-null value); then the same
setOpName
method is called with a NULL
value
as argument (this call frees the memory that was allocated during the
first setOpName
method call but not assigns a new value to
the m_name
field since condition of the
if ( name )
statement is evaluated as false; so this field
value remains non-null); the next call of the setOpName
with any argument leads to deallocation of the same memory that has
already been deallocated, i.e. undefined behavior (see DOUBLE_FREE.EX), or the next call of the
getOpName
method returns address of the already deallocated
memory, accessing which also leads to undefined behavior (see DEREF_AFTER_FREE).
A common pattern for cleaning up fields of a structure is shown in
the example above. While in majority of the cases such methods are
called when the structure won’t be reused afterwards and in this case
there isn’t an issue with the code, it is still better to assign the
NULL
values to the deallocated pointer fields to prevent
hard to diagnose issues with the usage of the freed memory (see DEREF_AFTER_FREE) in the remaining cases.
While both null pointer dereference and dereference of a freed pointer
cause undefined behavior, the former usually result in abnormal program
termination right when the dereference occurs and the latter in the
program using corrupted data which may be much harder to detect and
debug.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | High | Yes |
Statistical version of DANGLING_POINTER.STRICT to detect pointers that have been freed but not assigned a new value afterwards. Svace emits this subtype when for sufficient number of times in the source code the same pointer is reassigned after deallocation but in several places it isn’t. This can mean that in those several places the reassignment should be done but is accidentally skipped, which leaves the pointer in an invalid state so it can’t be dereferenced afterwards (see DEREF_AFTER_FREE).
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Unknown | Yes |
Related CWEs: CWE131.
This checker detects possible incorrect calculation of a buffer size,
needed to store a string, when due to a misplaced parenthesis of a
strlen
function call, that computes the string length, a
value is added to the string address instead of its return value. In
this case the strlen
function is called with the address of
the original string substring and computes an incorrect value, which in
turn may result in the buffer size which isn’t enough to store all the
characters of the original string. Trying to copy the string into an
allocated buffer of such size can lead to a buffer overflow.
pkname=(char*)malloc(strlen(argv[optind]+4));
skname=(char*)malloc(strlen(argv[optind]+4));
sprintf(pkname,"%s.pk",argv[optind]);
sprintf(skname,"%s.sk",argv[optind]);
In this example the code author intended to calculate sizes of two
buffers pkname
and skname
, enough to store the
contents of the string argv[optind]
along with three
additional characters for their suffixes and one additional character
for null terminator. But instead of calculating the string length as
strlen(argv[optind])
and then add 4
to its
result, he misplaced the parenthesis and called the strlen
function with argv[optind]+4
value as argument. This
expression produces the address of the fifth character of the
argv[optind]
string so the strlen
call
actually computes the length of the substring starting from its fifth
character which is lesser than the original string length by four.
Afterwards the resulting buffer sizes are used as arguments in
malloc
function calls to allocate two buffers on the heap.
At last these buffers are filled using a sprintf
formatted
print function which doesn’t check for correct buffer sizes, and these
calls lead to writing of the memory outside of the allocated block.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | High | Yes |
Related CWEs: CWE401, CWE404, CWE775.
This checker detects general memory leak situations, where memory was allocated, and then all references to that memory are lost. This makes it impossible to return such memory blocks to the operating system or memory manager so that they can be reallocated for other purposes. Even though typically this doesn’t cause immediate problems, over time such lost memory can amount to large quantities, that can lead to program slow down or even crash due to exhaustion of available memory resources. Memory leaks affect user applications to a lesser degree since when the application exits all the allocated by the application memory, even leaked, is automatically reclaimed by the operating system. But various servers, services and other programs, that commonly have long uptimes, can be more susceptible to memory leaks negative effects. To avoid memory leaks a special care should be taken to properly free all the memory, that was allocated by the program, as soon as it’s clear it won’t be used anymore.
newCallbacks = realloc( offman->FreeBoxesUpdateCallback,
sizeof(FreeBoxCallbackProcPtr) * (offman->NumCallbacks + 1));
newPrivates = realloc(offman->devPrivates,
sizeof(DevUnion) * (offman->NumCallbacks + 1));
if(!newCallbacks || !newPrivates)
return FALSE;
In this code snippet there are two memory (re)allocations using a
standard C function realloc
, whose results are stored in
the newCallbacks
and newPrivates
local
variables. The subsequent if
statement checks whether any
of the allocations have failed by comparing the values of these
variables for being null and returning FALSE
value if any
of them is. But when the first allocation succeeds and the second one
fails, the newCallbacks
variable goes out of scope after
return from the function and its value, which is the address of the
memory block, successfully allocated by the first realloc
call, is lost. The issue is fixed by independently checking the results
of the two allocations and if only the second one fails, then deallocate
the result of the first one before return from the function.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Very high | Yes |
Related CWEs: CWE401, CWE404, CWE775.
This is a subtype of MEMORY_LEAK for cases
where the memory is allocated by using the standard C library string
copying function strdup
, but then all references to that
memory are lost. This function first allocates a memory block, enough to
hold all characters of its string argument, then copies all its
characters into that block and returns its address. The allocated memory
block must be freed using a standard free
function, but if
its address is lost, it’s impossible to do so and even though the memory
can’t be used by the application anymore, it is reserved for the
application by the operating system and can’t be reused for other
purposes, eventually leading to memory shortage. Such issues are
separated into its own subtype since in the majority of cases the
strdup
function is used for allocating small blocks of
memory and so such errors are less critical.
static inline void _set_str_with_default(keynode_t* node, const char *def, char *target)
{
const char *key_str = vconf_keynode_get_str(node);
char *str = NULL;
if (key_str)
str = strdup(key_str);
else
str = strdup(def);
if (!str)
_E("failed to strdup, keep old one");
free(target);
target = str; // FLAW: storing allocated memory address into local memory
} // going out of scope at the return from the function
In the above example the result of one of two strdup
function calls (for this example doesn’t actually matter which one) is
stored into the local variable str
. In the last statement
of the function the value of the str
variable is stored
into the target
parameter. However, this does not affect
the original pointer that was passed into the function. The pointer
target
in the function is a local copy of whatever pointer
was passed in as its last argument but when the
target = str;
assignment is executed, it only changes the
local copy of target
, not the original pointer outside of
this function. So when the function returns, the local variable
target
goes out of scope, and any reference to the newly
allocated memory is lost. Since there are no other references to that
memory, it cannot be freed, resulting in a memory leak. To avoid this
memory leak, the function should be modified so that it actually updates
the pointer in the caller’s context. One common approach is to pass a
pointer to the string instead of the string itself, changing the last
parameter declaration to char **target
and the assignment
to *target = str;
so that changes made to
*target
within the function affect the original pointer
passed in by the caller.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | High | Yes |
This is a subtype of MEMORY_LEAK for cases where the memory is allocated for a structure, but then all references to that memory are lost. Since the allocated memory address is lost, it’s impossible to deallocate it anymore, so even though the memory can’t be used by the application anymore, it is reserved for the application by the operating system and can’t be reused for other purposes, eventually leading to memory shortage. Such cases are classified as a separate subtype because some code patterns, specific to low-level work with allocated for structures memory, are difficult to analyze and can lead to an increased number of unclear and false reports.
pPicture->pSourcePict = (SourcePictPtr) malloc(sizeof(PictLinearGradient));
if (!pPicture->pSourcePict) {
*error = BadAlloc;
free(pPicture);
return 0;
}
pPicture->pSourcePict->linear.type = SourcePictTypeLinear;
pPicture->pSourcePict->linear.p1 = *p1;
pPicture->pSourcePict->linear.p2 = *p2;
initGradient(pPicture->pSourcePict, nStops, stops, colors, error);
if (*error) {
free(pPicture);
return 0; // FLAW: allocated memory, which address is stored in field
} // 'pPicture->pSourcePict', is leaked
In this example the memory for structure
PictLinearGradient
is allocated and stored in the
pPicture->pSourcePict
field. If this allocation is
successful, the structure fields are initialized and its address is used
as the first argument in the initGradient
function call.
This function fills other fields of the structure, but if it encounters
an error during its execution, it assigns a non-zero error code to an
integer variable, which address is passed as its last
(int *
pointer) parameter error
and returns
without deallocating the memory, which address passed as its first
parameter. After the initGradient
call the value of that
variable *error
is checked and if it’s non-zero, the
memory, which address is stored in the pPicture
variable,
is deallocated by calling a free
function, but the memory,
which address is stored in its pPicture->pSourcePict
field, is not deallocated. This leads to the only reference to that
memory being lost and impossibility to deallocate it. In order to avoid
this memory leak, it is necessary to deallocate the allocated memory by
calling a free(pPicture->pSourcePict)
before the
free(pPicture)
call.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Low | Yes |
Related CWEs: CWE401, CWE404, CWE775.
This is a subtype of MEMORY_LEAK.STRDUP for cases where a
memory block is allocated using the standard C library string copying
function strdup
, its address is stored in a structure
field, but then all references to that memory are lost. Since the
allocated memory address is lost, it’s impossible to deallocate it
anymore, so even though the memory can’t be used by the application
anymore, it is reserved for the application by the operating system and
can’t be reused for other purposes, eventually leading to memory
shortage. Such cases are classified as a separate subtype because some
code patterns, specific to low-level work with allocated for structures
memory, are difficult to analyze and can lead to an increased number of
unclear and false reports and in the majority of cases the
strdup
function is used for allocating small blocks of
memory and so such errors are less critical.
static void
gst_oss4_source_init (GstOss4Source * osssrc, GstOss4SourceClass * g_class)
{
const gchar *device;
device = g_getenv ("AUDIODEV");
if (device == NULL)
device = DEFAULT_DEVICE;
osssrc->fd = -1;
osssrc->device = g_strdup (device);
osssrc->device_name = g_strdup (DEFAULT_DEVICE_NAME);
osssrc->device_name = NULL; // FLAW: structure field 'device_name', containing
} // just allocated memory address, is overwritten
In the above example at the end of the
gst_oss4_source_init
function the value of the
osssrc->device_name
field is first initialized with the
result of a g_strdup
function call, which is a popular
open-source library GLib’s version of the C standard string copying
function strdup
, and then right in the next statement it is
reassigned a NULL
value. This overwrites the address of the
newly allocated for the copied string memory, returned by the
g_strdup
call, and so this address becomes lost since it
isn’t stored anywhere else. This leads to a memory leak. It looks like the code author
accidentally added initialization of the same structure field a second
time, overwriting the first one.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Average | Yes |
Similar to the MEMORY_LEAK checker,
checker MEMORY_LEAK.EXCEPTION
finds situations where memory is leaked but due to incorrect
exception handling. This can happen when the code, that should
deallocate the memory, is skipped due to taking an exception path.
Common and efficient way to avoid such issues is to use a RAII (Resource
Acquisition Is Initialization) idiom or smart pointers that both provide
strong exception safety guarantees, ensuring that memory is released
even if an exception occurs.
Less reliable cases when the allocation or exception handling contain
complex conditions are reported with a MEMORY_LEAK.EX.EXCEPTION
subtype.
SQLBackend *backend = new SQLBackend();
if (backend->open(PolicyStoragePath)) {
throw runtime::Exception("Policy storage I/O error");
}
In the example above the memory for a SQLBackend
object
is allocated and stored in the backend
local variable but
if the method open
returns a non-zero value indicating an
error, then an exception with type runtime::Exception
is
thrown, the execution leaves the scope, where the backend
variable can be accessed, and its value becomes lost. Since it contains
the allocated memory address, that should be deallocated, a memory leak
occurs. To solve this issue it is enough to add a
delete backend
statement before the throw
statement.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | Yes |
Related CWEs: CWE401, CWE404, CWE775.
This checker detects situations where memory was allocated and then all references to that memory were lost leading to inability to release it back to the system. Compared to the MEMORY_LEAK checker this one is able to detect errors in more complicated situations where leak occurs only on specific paths.
char *pa_getcwd(void) {
size_t l = 128;
for (;;) {
char *p = pa_xmalloc(l);
if (getcwd(p, l))
return p;
if (errno != ERANGE) // FLAW: memory is leaked when 'errno' value is not ERANGE
return NULL;
pa_xfree(p);
l *= 2;
}
}
In the example above the function pa_getcwd
starts by
allocating memory using pa_xmalloc(l)
(which is a wrapper
for the standard C library memory allocation function
malloc
but with added checks), where l
is
initially set to 128
and stores the allocated memory
address into the p
local variable. This memory is intended
to hold the current working directory string. The function then calls
standard POSIX function getcwd(p, l)
to write the path to
the current working directory into the provided buffer p
.
If this call succeeds (returns a non-null pointer), the allocated memory
pointer p
is returned. But if getcwd
call
fails, it checks global errno
variable for an error code:
if errno
is ERANGE
, it indicates that the
buffer was too small, so the function frees the allocated memory with
pa_xfree(p)
and doubles the size of l
for the
next iteration. The memory leak happens if getcwd
fails for
a reason other than needing a larger buffer (e.g., if the current
directory does not exist or there are permission issues). In this case
errno
is set to a value other than ERANGE
and
the function returns NULL
without freeing the previously
allocated memory pointed to by p
. Since there is no
mechanism to free this memory before returning, it becomes inaccessible,
leading to a memory leak.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Low | No |
This checker is a less reliable subtype of a memory leak checker MEMORY_LEAK.EX for cases where the allocated memory may be left not deallocated conditionally on some path, usually for error handling. When all references to allocated memory are lost it leads to inability to release it back to the system and eventually to a potential memory shortage in the system.
switch (argc) {
case 2:
argv_list = 1;
case 1:
usb_controller = strdup(env_get("dfu_usb_con"));
interface = strdup(env_get("dfu_interface"));
devstring = strdup(env_get("dfu_device"));
if (!usb_controller || !interface || !devstring) {
puts("DFU: default device environment is not set.\n");
ret = CMD_RET_USAGE;
goto bad_args;
}
break;
case 5:
argv_list = 4;
case 4:
usb_controller = argv[1];
interface = argv[2];
devstring = argv[3];
break;
default:
return CMD_RET_USAGE;
}
The code snippet above uses standard C library function
strdup
to allocate memory for usb_controller
,
interface
, and devstring
local variables. This
function duplicates a string and allocates enough memory to hold it,
returning a pointer to the new memory. However, this memory must be
freed later to avoid leaks. The switch statement checks the value of
argc
function parameter. If argc
is
2
, it sets argv_list
to 1
but
does not break, which means it falls through to case 1
.
This behavior is intentional, but it’s important to ensure that all
paths that allocate memory are also responsible for freeing it.
done:
dfu_free_entities();
if (dfu_reset)
run_command("reset", 0);
g_dnl_clear_detach();
bad_args:
if (argc == 1) {
free(usb_controller);
free(interface);
free(devstring);
}
#endif
return ret;
This code snippet shows the end of the same function. In the section
with the bad_args
label, memory is only freed if
argc
is equal to 1
. If argc
is
2
and the code jumps to bad_args
, it will not
free the allocated memory for usb_controller
,
interface
, or devstring
. On return from the
function values of these local variables become inaccessible, which
leads to a memory leak.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Low | Yes |
Related CWEs: CWE121, CWE122, CWE475.
This checker reports cases of using of the same source and
destination buffers in function calls where it is prohibited, such as
memcpy
. The standard C library function memcpy
is designed to copy a specified number of bytes from a source address to
a destination address and it performs this operation without any checks
for overlapping memory regions. The C standard states that providing
memcpy
with overlapping source and destination buffers
leads to undefined behaviour. For example, using a straightforward
byte-by-byte copy memcpy
implementation if the destination
starts before the source (e.g., copying from a higher memory address to
a lower one), the bytes in the destination may be overwritten before
they are copied from the source. This leads to data corruption. To avoid
such issues when copying overlapping memory regions use a slightly less
efficient standard memmove
function, which is specifically
designed to handle overlapping memory regions correctly.
if (keep_offset > 0) {
memcpy(data->buff_out, data->buff_out + sizeof(data->buff_out) - keep_offset, keep_offset);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
Visual Basic .NET | Quality | Major | High | Yes |
C/C++ | Quality | Major | High | Yes |
Scala | Quality | Major | High | Yes |
Go | Quality | Major | High | Yes |
Kotlin | Quality | Major | High | Yes |
C# | Quality | Major | High | Yes |
Related CWEs: CWE404, CWE772, CWE773, CWE775.
This checker finds situations where a file handle, a socket descriptor, a database connection or another shared resource is lost because all local variables that held its reference went out of scope or were re-assigned. This leads to inability to release such resource back to the system and eventually to a possible shortage of these resources, i.e. a resource leak.
void func1() {
FILE* f = fopen("qqq.c", "w");
}
void func2() {
FILE* f = fopen("qqq.c", "w");
f = 0;
}
In some cases it might be
possible for the programmer to predict the value of a descriptor
returned by a function that allocates it. For example, after closing
standard output descriptor 1
, the next allocated descriptor
will have value 1
again. In such cases, even if the
returned value is not recorded, the predicted value can still be used to
deallocate resources. For these situations, Svace emits warnings of
subtype HANDLE_LEAK.STRICT
.
import java.io.*;
public class HandleLeakTest {
public static void example(File f) {
try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(f))) {
output.writeObject("test");
} catch (IOException ignored) { }
}
public static void possibleFix(File f) {
try (FileOutputStream fs = new FileOutputStream(f); ObjectOutputStream output = new ObjectOutputStream(fs)) {
output.writeObject("test");
} catch (IOException ignored) { }
}
}
Function example
illustrates the defect. The underlying
FileOutputStream
is not declared in a variable. It will
never be closed directly in the generated finally block, it will be
closed only through the close
method of the wrapping
ObjectOutputStream
. The problem is, that if an exception is
thrown from the ObjectOutputStream
constructor, its
close
method will not be called and therefore the
underlying FileOutputStream
will not be closed.
Function possibleFix
illustrates a possible fix: assign
the result of FileOutputStream
in variable and use it to
construct ObjectOutputStream
. Note that the result of
FileInputStream
constructor call will be lost if
IOException
happens but this exception is handled inside
the example
method. If exception goes out of the method
scope, HANDLE_LEAK.EXCEPTION will
be emitted.
fun example(bytes: ByteArray) {
val stream = File("data.txt").inputStream()
stream.read(bytes)
}
fun possibleFix(bytes: ByteArray) {
File("data.txt").inputStream().use { it.read(bytes) }
}
Function example
illustrates the defect: file input
stream was created for data.txt
but it wasn’t closed
properly.
Function possibleFix
illustrates a possible fix: call
use
function which executes the given function block on
this resource and then closes it down correctly whether an exception is
thrown or not.
For C# this checker finds situations where a resource (object that
implements IDisposable
interface) is lost, because local
variables that held its value went out of scope or were re-assigned.
It has the following subtypes:
.FRUGAL
means that the leaked resource is a
Windows Forms form..EXCEPTION
means that the resource is leaked
because of an exception..HAS_FINALIZER
means that the resource class
has a finalizer which performs cleanup when the object is garbage
collected. Note that it will happen in unknown time after losing the
last reference to the resource, and the .NET runtime doesn’t call
finalizers on application exit..SAFEHANDLE
means that the resource class is a
type derived from
System.Runtime.InteropServices.SafeHandle
.A warning can have the following combination of these subtypes in the
specified order:
[.FRUGAL][.EXCEPTION][{.HAS_FINALIZER,.SAFEHANDLE}][.TEST]
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
Visual Basic .NET | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
Scala | Quality | Normal | Unknown | Yes |
Kotlin | Quality | Major | Unknown | Yes |
C# | Quality | Normal | Unknown | Yes |
Related CWEs: CWE404.
This checker finds situations where a file descriptor, a file handle, a socket descriptor or another shared resource is lost because due to an exception execution left a function where all its references were stored. This leads to inability to release such resource back to the system and eventually to a possible shortage of these resources.
import java.io.*;
public class HandleLeakTest {
public static void example(File f) throws IOException {
try (ObjectOutputStream output = new ObjectOutputStream(new FileOutputStream(f))) {
output.writeObject("test");
}
}
public static void possibleFix(File f) throws IOException {
try (FileOutputStream fs = new FileOutputStream(f); ObjectOutputStream output = new ObjectOutputStream(fs)) {
output.writeObject("test");
}
}
}
Function example
illustrates the defect. The underlying
FileOutputStream
is not declared in a variable. It will
never be closed directly in the generated finally block, it will be
closed only through the close
method of the wrapping
ObjectOutputStream
. The problem is, that if an exception is
thrown from the ObjectOutputStream
constructor, its
close
method will not be called and therefore the
underlying FileOutputStream
will not be closed.
Function possibleFix
illustrates a possible fix: assign
the result of FileOutputStream
in variable and use it to
construct ObjectOutputStream
. Note that the result of
FileInputStream
constructor call will be lost if
IOException
happens and this exception goes out of the
method scope. If exception is handled, HANDLE_LEAK will be emitted.
import java.io.*
import java.lang.IllegalStateException
import java.util.zip.*
fun example(f: File, com: String) {
try {
val zip = ZipOutputStream(FileOutputStream(f))
zip.putNextEntry(ZipEntry("putNextEntry may throw exception"))
zip.close()
} catch (ex: Exception) {
throw IllegalStateException(ex.message)
}
}
fun possibleFix(f: File, com: String) {
ZipOutputStream(FileOutputStream(f)).use { zip ->
zip.putNextEntry(ZipEntry("putNextEntry may throw exception"))
}
}
Function example
illustrates the defect: file output
stream was created for f
but it will not be closed if
putNextEntry
call raises an exception.
Function possibleFix
illustrates a possible fix: call
use
function which executes the given function block on
this resource and then closes it down correctly whether an exception is
thrown or not.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Major | High | Yes |
Scala | Quality | Major | High | Yes |
Go | Quality | Major | High | Yes |
Kotlin | Quality | Major | High | Yes |
This checker finds situations where a file descriptor, a file handle, a socket descriptor or another shared resource is lost because all local variables that held their values went out of scope or were re-assigned. This leads to inability to release such resource back to the system and eventually to a possible shortage of these resources. Compared to the HANDLE_LEAK checker this one is able to detect errors in more complicated situations where leak occurs only on specific paths.
import java.io.*;
class HandleLeakTest {
private static void readAndPrint(InputStream s) throws IOException {
System.out.println(s.read());
}
public static void example(String source, boolean isFile) throws IOException {
final InputStream stream;
if (isFile) {
stream = new FileInputStream(source); // FileInputStream acquired
} else {
stream = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8));
}
readAndPrint(stream);
if (!isFile) {
stream.close();
}
// leaked when the function terminates
}
public static void possibleIncorrectFix(String source, boolean isFile) throws IOException {
final InputStream stream;
if (isFile) {
stream = new FileInputStream(source); // FileInputStream acquired
} else {
stream = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8));
}
readAndPrint(stream); // leaked after IOException is thrown
if (isFile) {
stream.close();
}
}
}
Function example
illustrates the defect: depending on
the function parameter isFile
, either the
FileInputStream
or the ByteArrayInputStream
is
acquired. The FileInputStream
should be closed before
function terminates, but the ByteArrayInputStream
shouldn’t. The condition to determine if close
should be
called is incorrect. So, the FileInputStream
leaks when the
example
function terminates. Function
possibleIncorrectFix
illustrates a possible but incorrect
fix: use correct condition to determine if close
should be
called. If you apply this naive solution, the Svace will still report a
HANDLE_LEAK.EX.EXCEPTION
warning. The fully correct fix is provided in the Java example for the
HANDLE_LEAK.EX.EXCEPTION
detector.
import java.io.*
@Throws(IOException::class)
fun handleStream(stream: InputStream) {
val chunk = ByteArray(4)
stream.read(chunk)
// do some stuff
}
fun example(source: String, isFile: Boolean) {
val stream = if (isFile) File(source).inputStream() else source.byteInputStream() // FileInputStream is acquired
handleStream(stream)
if (!isFile) {
stream.close()
}
// leaked when the function terminates
}
fun possibleIncorrectFix(source: String, isFile: Boolean) {
val stream = if (isFile) File(source).inputStream() else source.byteInputStream() // FileInputStream is acquired
handleStream(stream) // leaked after IOException is thrown
if (isFile) {
stream.close()
}
}
Function example
illustrates the defect: depending on
the function parameter isFile
, either the
FileInputStream
or the ByteArrayInputStream
is
acquired. The FileInputStream
should be closed before
function terminates, but the ByteArrayInputStream
shouldn’t. The condition to determine if close
should be
called is incorrect. So, the FileInputStream
leaks when the
example
function terminates. Function
possibleIncorrectFix
illustrates a possible but incorrect
fix: use correct condition to determine if close
should be
called. If you apply this naive solution, the Svace will still report a
HANDLE_LEAK.EX.EXCEPTION
warning. The fully correct fix is provided in the Kotlin example for the
HANDLE_LEAK.EX.EXCEPTION
detector.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
Scala | Quality | Normal | Unknown | Yes |
Kotlin | Quality | Major | Unknown | Yes |
Similar to the HANDLE_LEAK.EX checker,
checker HANDLE_LEAK.EX.EXCEPTION
finds situations where a
shared resource is leaked but due to incorrect exception handling. It is
able to detect errors in complicated situations where leak occurs only
on specific paths.
import java.io.*;
class HandleLeakTest {
private static void printAndRead(InputStream s) throws IOException {
System.out.println(s.read());
}
public static void example(String source, boolean isFile) throws IOException {
final InputStream stream;
if (isFile) {
stream = new FileInputStream(source); // FileInputStream is acquired
} else {
stream = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8));
}
printAndRead(stream); // leaked after IOException is thrown
if (isFile) {
stream.close();
}
}
public static void possibleFix(String source, boolean isFile) {
InputStream stream = null;
try {
if (isFile) {
stream = new FileInputStream(source); // FileInputStream is acquired
} else {
stream = new ByteArrayInputStream(source.getBytes(StandardCharsets.UTF_8));
}
// do some stuff
} catch (IOException e) {
// handle exception
} finally {
if (stream != null && isFile) {
try {
stream.close(); // FileInputStream is closed
} catch (IOException e) { /* ... */ }
}
}
}
}
Function example
illustrates the defect: depending on
the function parameter isFile
, either the
FileInputStream
or the ByteArrayInputStream
is
acquired. The FileInputStream
should be closed before
function terminates, but the ByteArrayInputStream
shouldn’t. The condition to determine if close
should be
called is correct, but the readAndPrint
call may cause an
exception. So, the FileInputStream
leaks when the
readAndPrint
call raises an IOException
.
Function possibleFix
illustrates a possible fix: handle
all possible exceptions and release the stream
in a finally
block.
import java.io.*
@Throws(IOException::class)
fun handleStream(stream: InputStream) {
val chunk = ByteArray(4)
stream.read(chunk)
// do some stuff
}
fun example(source: String, isFile: Boolean) {
val stream = if (isFile) File(source).inputStream() else source.byteInputStream() // FileInputStream is acquired
handleStream(stream) // leaked after IOException is thrown
if (isFile) {
stream.close()
}
}
fun possibleFix(source: String, isFile: Boolean) {
val stream = if (isFile) File(source).inputStream() else source.byteInputStream() // FileInputStream is acquired
stream.use { handleStream(it) } // FileInputStream is closed
}
Function example
illustrates the defect: depending on
the function parameter isFile
, either the
FileInputStream
or the ByteArrayInputStream
is
acquired. The FileInputStream
should be closed before
function terminates, but the ByteArrayInputStream
shouldn’t. The condition to determine if close
should be
called is correct, but the handleStream
call may cause an
exception. So, the FileInputStream
leaks when the
handleStream
call raises an IOException
.
Function possibleFix
illustrates a possible fix: call
use
function which executes the given function block on the
stream
and then closes it down correctly whether an
exception is thrown or not. Note that the unconditional call of
use
(and therefore close
call) is acceptable.
Closing a ByteArrayInputStream
has no effect, but is not
prohibited.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
Scala | Quality | Normal | Unknown | No |
This Java-specific checker is a subtype of HANDLE_LEAK that reports incorrect using of
classes which implement a java.io.Closeable
interface. The
analyzer ensures that after creation of an instance of such class its
close
method is called and reports a warning otherwise. If
the issue occurs due to incorrect exception handling a subtype
HANDLE_LEAK.CLOSEABLE.EXCEPTION
is emitted.
import java.io.Closeable;
import java.io.IOException;
class MyClass implements Closeable {
@Override
public void close() throws IOException {
}
}
class Example {
public void noclose() {
MyClass s = new MyClass();//no close
}
public void withclose() {
MyClass s = new MyClass();//no leak
s.close();
}
}
In the example above Svace will emit warning for method
noclose
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This C-specific checker reports potential memory leaks caused by
misusing the SQLite library APIs working with prepared statements. Each
creation of a prepared statement object using
sqlite3_prepare_v2
function should be accompanied by a
corresponding sqlite3_finalize
call to delete the prepared
statement. Failing to do so may lead to eventual shortage of the dynamic
memory available to the application.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Average | Yes |
C/C++ | Quality | Normal | Average | Yes |
Scala | Quality | Normal | Average | Yes |
Go | Quality | Normal | Average | Yes |
Related CWEs: CWE415, CWE416, CWE672, CWE675.
This checker finds situations where a closed file descriptor is closed again.
Note that checker for Go finds double close on channels but does not on files or other resources.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Average | Yes |
C/C++ | Quality | Normal | Average | Yes |
Scala | Quality | Normal | Average | Yes |
Go | Quality | Normal | Average | Yes |
Related CWEs: CWE415, CWE416, CWE672.
This checker finds situations where a closed file descriptor is closed again, and the close happens in procedures.
Note that checker for Go finds double close on channels but does not on files or other resources.
int my_close(int p) {
if (cond()) {
close(p);
return -1;
}
return 0;
}
void test(int fd) {
my_close(fd);
close(fd);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | High | Yes |
C/C++ | Quality | Normal | High | Yes |
Scala | Quality | Normal | High | Yes |
Go | Quality | Normal | High | Yes |
Kotlin | Quality | Normal | High | Yes |
This checker finds situations where a file descriptor, file handle or a socket descriptor is closed and there is an attempt to read from or write to it.
fun example(fileName: String) {
val buf = ByteArray(1024)
val stream = FileInputStream(fileName)
stream.read(buf)
// ...
stream.skip(10)
stream.close()
stream.read(buf)
}
fun possibleFix(fileName: String) {
val buf = ByteArray(1024)
FileInputStream(fileName).use {
it.read(buf)
// ...
it.skip(10)
it.read(buf)
}
}
Function example
illustrates the defect:
stream
was closed and then there was an attempt to read
from it.
Function possibleFix
illustrates a possible fix: handle
streams with use
function call.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Average | Yes |
C/C++ | Quality | Minor | Average | Yes |
Scala | Quality | Minor | Average | Yes |
Python | Quality | Minor | Average | Yes |
This checker finds situations where a file descriptor, file handle or a socket descriptor is closed and then passed as an argument to a function.
#include<unistd.h>
#include<stdio.h>
int prepare(FILE *fd);
int openAndPrepare(FILE **fd) {
*fd = fopen("fileName", "r");
if (*fd == NULL) {
return 1;
}
int err = prepare(*fd);
if (err) {
fclose(*fd);
return 2;
}
return 0;
}
void example() {
FILE *fd;
openAndPrepare(&fd);
char buf[100];
fread(buf, 10, 10, fd);
}
void possibleFix() {
FILE *fd;
if (openAndPrepare(&fd)) {
return;
}
char buf[100];
fread(buf, 10, 10, fd);
}
Function example
illustrates the defect: fd
can be closed after the openAndPrepare
function is called,
then it is passed as the first parameter to the fwrite
function. Function possibleFix
illustrates a possible fix:
check the return value of the openAndPrepare
function
call.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Unknown | Yes |
Related CWEs: CWE416.
This Microsoft Windows-specific checker finds situations where an
instance of Microsoft COM interface is explicitly deallocated. Instead,
its Release
method should be used.
#include <unknwn.h>
DEFINE_GUID( CLSID_Arithm, 0xa888f560, 0x58e4, 0x11d0, 0xa6, 0x8a, 0x0, 0x0, 0x83, 0x7e, 0x31, 0x0);
DEFINE_GUID( IID_IArithm, 0xa888f561, 0x58e4, 0x11d0, 0xa6, 0x8a, 0x0, 0x0, 0x83, 0x7e, 0x31, 0x0);
class IArithm : public IUnknown {
public:
virtual long Add(long Op1, long Op2) = 0;
virtual long Sub(long Op1, long Op2) = 0;
};
class Arithm : public IArithm {
public:
HRESULT QueryInterface(REFIID riid, void** ppv);
ULONG AddRef();
ULONG Release();
long Add(long Op1, long Op2) { return Op1 + Op2; }
long Sub(long Op1, long Op2) { return Op1 - Op2; }
private:
DWORD m_lRef;
public:
Arithm() : m_lRef(0);
};
HRESULT Arithm::QueryInterface(REFIID riid, void** ppv) {
switch(riid) {
case IID_IUnknown:
case IID_IArithm;
*ppv = this;
AddRef();
return ( S_OK ) ;
default:
return ( E_NOINTERFACE );
}
}
ULONG Math::Release() {
InterlockedDecrement( &m_lRef );
if (m_lRef == 0) {
delete this;
return 0;
} else {
return m_lRef;
}
}
ULONG Math::AddRef() {
InterlockedIncrement( &m_lRef );
return m_lRef;
}
long summarize(IUnknown *pUnknwn, long a, long b) {
IArithm *pArithm = NULL;
HRESULT hr = pUnknwn->QueryInterface(IID_IArithm, (void **)&pArithm);
if (SUCCEEDED(hr)) {
long result = pArithm->Add(a, b);
delete pArithm; // BAD_FREE.MS_COM is reported; use pArithm->Release() here!
return result;
}
return 0;
}
These checkers find situations where number of loop iteration may be infinite because of integer overflows or other reasons.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | Yes |
C# | Quality | Major | High | Yes |
Related CWEs: CWE835.
Check for instances of loops that never terminate due to control variables which are not properly updated.
In the following example, a wrong variable is updated:
In the following example, a wrong variable is updated:
In the following example, a wrong variable is updated:
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Quality | Minor | Very high | Yes |
C# | Quality | Minor | Very high | Yes |
Check for methods that unconditionally call themselves.
In the following example, method
CodeBuilder.AppendFormat
always calls itself, which leads
to stack overflow.
public class CodeBuilder
{
public CodeBuilder AppendFormat(string format, object[] args)
{
return AppendFormat(format, args);
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Very low | No |
C/C++ | Quality | Major | Very low | No |
Go | Quality | Major | Very low | No |
This checker finds situations where the number of a loop iterations may be more than it was intended because the termination condition depends on the value of a string.
In the following example if there are no space characters in the
pos
string parameter the loop will not be terminated
normally.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Low | No |
Visual Basic .NET | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Low | No |
Go | Quality | Major | Low | No |
C# | Quality | Major | Average | Yes |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
This checker finds situations when loop termination condition may become infeasible due to an overflow or underflow of the index variable.
void example1() {
unsigned char i;
for (i = 0; i <= 250; i += 10) { // Possible integer overflow, after `i = 250`.
bar();
}
}
void example2(unsigned len) {
int i = 0;
while (len > 0) {
len -= 8; // Overflow if `len % 9 != 0`.
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
C/C++ | Quality | Major | Unknown | No |
Go | Quality | Major | Unknown | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
Checker INFINITE_LOOP.INT_OVERFLOW.ARRAY
finds
situations with integer overflow that may lead to infinite loop
execution and related to buffer access.
void with_tainted() {
unsigned char x = 0;
char* buf = getenv("HOME");
while (buf[x] == ' ') {
++x; //svace: emitted INFINITE_LOOP.INT_OVERFLOW.ARRAY
}
}
In the example above if the first 256 bytes of buffer
buf
do not contain a space character, the variable
x
will eventually be incremented enough times to overflow
an unsigned char
type resetting its value back to zero
which leads to an infinite loop.
The checker emits warnings only for tainted buffers (from external
sources). For other buffer types INFINITE_LOOP.INT_OVERFLOW.ARRAY.STRICT
is emitted.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
C/C++ | Quality | Critical | Unknown | Yes |
Go | Quality | Critical | Unknown | Yes |
Checker finds suspicious situations where loop execution is bounded only by array data.
Example:
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Major | Unknown | Yes |
C# | Quality | Normal | Unknown | Yes |
Related CWEs: CWE125, CWE190, CWE191.
This checker finds arithmetic operations with integer overflows when the result of that arithmetic operation is too big or too small to be represented as a value of the operation’s result type.
void print_integer_overflow(void) {
/* Result of the expression is -1073741827 instead of the expected 3221225469 for 32-bit integer type */
printf("%d\n", (INT_MAX / 2) * 3);
}
void foo(int a) {
if (a == int.MaxValue) // ℹ️〔 Step 1: Condition 'a == int.MaxValue' taking false branch〕
return;
long b = a + 1; // overflow is NOT possible
long c = a + 2; // ⚠️〔INTEGER_OVERFLOW An overflow in the arithmetic expression a + 2 of type int may occur〕 // ℹ️〔overflow may happen when a + 2 is Int32.MinValue〕 // ℹ️〔value without overflow may be 2147483648〕 // ℹ️〔overflow may happen when a is Int32.MaxValue - 1〕
long d = 2 + a; // do not report same
}
In the example the value of the variable a
was compared
against int.MaxValue
, so overflow in the expression
a + 1
is not possible. However overflow may happen in the
a + 1
if a
is Int32.MaxValue - 1
.
The result will be equal to Int32.MinValue
instead of
Int32.MaxValue + 1
void foo(int a) {
if (a is (int.MaxValue or (int.MaxValue - 1))
return;
long b = a + 1; // overflow is NOT possible
long c = a + 2; // overflow is NOT possible
long d = 2 + a; // overflow is NOT possible
}
Additional comparison of the variable a
against
Int32.MaxValue - 1
prevents overflow in the expression
a + 2
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Quality | Normal | Unknown | Yes |
C# | Quality | Normal | Unknown | Yes |
Checker detects integer overflow when values of variables are explicitly known at compile time.
void foo()
{
ushort size = ushort.MaxValue; // size is 65535
size += 2; // ⚠️〔INTEGER_OVERFLOW.EXPLICIT An overflow in the arithmetic expression size += 2 of type ushort will occur: result will be 1 instead of 65537〕
}
An overflow in the arithmetic expression size += 2
of
type ushort will occur on line 4: result will be 1 instead of 65537.
If overflow is expected it is possible to use unchecked
expression or statement to suppress warning.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C# | Quality | Minor | Unknown | Yes |
Checker detects if the result of expression that may overflow is used
as an argument of Marshal.AllocHGlobal
. It is required to
use checked
expression or statement to emit exception in
case of overflow and reduce risk of silent memory damage.
public static void foo(int size)
{
size++; // ⚠️〔INTEGER_OVERFLOW.CHECKED_REQUIRED An overflow in the arithmetic expression size++ which is used in Marshal.AllocHGlobal(size) may occur. Please use checked arithmetic to throw IntegerOverflowException in case of overflow instead of possible memory damage〕 // ℹ️〔overflow may happen when size++ is Int32.MinValue〕
IntPtr hglobal = Marshal.AllocHGlobal(size);
}
An overflow in the arithmetic expression size++
(line 3)
which is used in Marshal.AllocHGlobal(size)
(line 4) may
occur when size is Int32.MaxValue
public static void foo(int size)
{
checked
{
size++;
}
IntPtr hglobal = Marshal.AllocHGlobal(size);
}
To prevent silent memory corruption it is required to use checked expression or statement if overflow may happen
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Quality | Normal | Average | Yes |
C/C++ | Quality | Major | Unknown | Yes |
C# | Quality | Normal | Average | Yes |
This checker finds situations where an arithmetic expression may contain an overflow and is widened to a larger data type.
It works only on AST structures and does not analyze execution paths.
long mult(int x, int y) {
return (x * y);
}
...
z = mult(0x7FFFFFFF, 2); /* Expected 0xFFFFFFFE but result is -2. */
...
The multiplication above should be performed after widening the arguments:
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This checker finds situations where an arithmetic expression inside a macro may contain an overflow and is widened to a larger data type.
It works only on AST structures and does not analyze execution paths.
#define TRANSFORM(x, y, offset) (((x) << (y)) + (offset))
unsigned long long transform(unsigned int x, unsigned int y) {
return TRANSFORM(x, y, 0); /* Having x == 1, y == 32, we get 0 instead of 2^32 */
}
The shift above should be performed after widening the arguments:
#define TRANSFORM(x, y, offset, type) (((type)(x) << (y)) + (offset))
unsigned long long transform(unsigned int x, unsigned int y) {
return TRANSFORM(x, y, 0, unsigned long long);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This checker finds situations where an arithmetic expression is widened to a larger data type and its subexpression may contain an overflow.
It works only on AST structures and does not analyze execution paths.
The evaluation of the constant expression above should be performed in 64 bits:
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This checker finds situations where an arithmetic expression inside a macro is widened to a larger data type and its subexpression may contain an overflow.
It works only on AST structures and does not analyze execution paths.
#define MUL2x20(x) ((x) << 20)
#define COEF 0x8000
long long update(int x) {
return (x * MUL2x20(COEF)); /* always returns 0 */
}
The evaluation of the constant expression above should be performed in 64 bits:
#define MUL2x20(x) ((x) << 20)
#define COEF 0x8000LL
long long update(int x) {
return (x * MUL2x20(COEF)); /* always returns 0 */
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | No |
C/C++ | Quality | Major | Average | No |
Scala | Quality | Major | Average | No |
Kotlin | Quality | Major | Average | Yes |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
The checker finds situations where value from external source is used in arithmetic operation without checking its range. It potentially may lead to an integer overflow.
int add_to_str(const char *str) {
int index = atoi(str);
int res = index + 500; // Potential integer overflow.
return res;
}
fun example(number: String): Int {
val num = number.toInt()
return num - 1
}
fun possibleFix(number: String): Int {
number.toIntOrNull()?.let {
try {
return Math.subtractExact(it, 1)
} catch (e: ArithmeticException) {
throw e
}
}
throw IllegalStateException()
}
Function example
illustrates the defect: string
number
is cast to integer which is used in arithmetic
subtraction without checking its range.
Function possibleFix
illustrates a possible fix: use
Math
library when working with values which are potentially
from external source. Also it’s recommended to use safer
toIntOrNull
function instead of toInt
function.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Average | No |
C/C++ | Quality | Normal | Average | No |
Scala | Quality | Normal | Average | No |
Go | Quality | Normal | Average | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
The checker finds situations where value from external source is passed to variable with smaller type size.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
C/C++ | Quality | Normal | Unknown | No |
Go | Quality | Normal | Unknown | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
The checker detects cases where a potential loss of data bits due an integer truncation is possible, while an assumption about an integer data range comes from bit-manipulation operations. This warning subtype is reported for the integers of 32-bit size or a greater.
void foo(unsigned int a);
#define INVERT_BYTES_32(X) ( \
( ((X) & 0x000000FF) << 24 ) \
| ( ((X) & 0x0000FF00) << 8 ) \
| ( ((X) & 0x00FF0000) >> 8 ) \
| ( ((X) & 0xFF000000) >> 24 ) \
)
void bar(unsigned int x) {
foo(INVERT_BYTES_32(x) + 1);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
C/C++ | Quality | Normal | Unknown | No |
This checker finds situations where an arithmetic expression will inevitably overflow. It means that any way of program execution leak to overflow.
void simple_add(int var) {
int result = var + 500;
}
void func(int boo) {
int param = INT_MAX - 300;
// Result of operation 'var + 500' in function simple_add will overflow in any way
if (boo) {
simple_add(param);
} else {
simple_add(param + 200);
}
}
void func(boolean boo) {
byte var = 120;
byte result;
// Variable 'result' will contain distorted data in any case
if (boo) {
result = var + 200;
} else {
result = var << 10;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Average | No |
C/C++ | Quality | Normal | Average | No |
Go | Quality | Normal | Average | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
The checker detects cases where the subtraction is performed on an unsigned value that can be 0, and therefore, the subtraction result may underflow, while its result is passed to a function as is passed to a library function as a sensitive unsigned integer data argument.
#include <string.h>
void example(char *dst, const char *str) {
size_t len = strlen(str);
memcpy(dst, str, len - 1);
}
void possible_fix(char *dst, const char *str) {
size_t len = strlen(str);
if (len >= 0) {
memcpy(dst, str, len - 1);
}
}
Function example
illustrates the defect:
len
variable is an unsigned variable and its value comes
from the return value of strlen
function and can be 0, so a
subtraction from it without a check may underflow and result to a big
unsigned value, which is used as a memcpy
argument.
Function possible_fix
illustrates a possible fix: adding
a proper check of len
to guarantee a valid argument value
for memcpy
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Very low | No |
C/C++ | Quality | Normal | Very low | No |
Go | Quality | Normal | Very low | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
The checker detects cases where the subtraction is performed on an unsigned value that can be 0, and therefore the subtraction result may underflow, while its result is used as a loop bound.
#include <string.h>
void example(char *str) {
unsigned len = strlen(str);
unsigned i;
for (i = 0; i < len - 2; ++i) {
str[i] = '*';
}
}
void possible_fix(char *str) {
unsigned len = strlen(str);
unsigned i;
if (len < 2)
return;
for (i = 0; i < len - 2; ++i) {
str[i] = '*';
}
}
Function example
illustrates the defect:
len
variable is an unsigned variable and its value comes
from the return value of strlen
function and can be 0, so a
subtraction from it without a check may underflow and result to a big
unsigned value, which is used as a loop bound.
Function possible_fix
illustrates a possible fix: adding
a proper check of len
to guarantee a valid loop bound.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
C/C++ | Quality | Normal | Unknown | No |
Go | Quality | Normal | Unknown | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
The checker detects cases where the subtraction is performed on an unsigned value that can be 0, and therefore the subtraction result may underflow.
Unlike INT_OVERFLOW.LIB and INT_OVERFLOW.LOOP this checker reports dubious subtracting itself, not the critical use of their result, so an underflow might be an intended behavior in certain cases.
#include <string.h>
void example(char *str) {
unsigned len = strlen(str);
unsigned len1 = len - 1;
if (len1 < 10) {
str[len1] = '*';
}
}
void possible_fix(char *str) {
unsigned len = strlen(str);
if (len > 0 && len < 10) {
unsigned len1 = len - 1;
str[len1] = '*';
}
}
Function example
illustrates the defect and function
possible_fix
illustrates a possible fix.
Note that the reported issue is a possible underflow while expression
len - 1
calculation within an unsigned context, when the
value of len
variable might be 0
, but not any
use of its result further.
#include <string.h>
void dummy(unsigned arg) {
(void)arg;
}
void example(const char *str) {
unsigned len = strlen(str);
dummy(len - 1);
}
void possible_fix(const char *str) {
unsigned len = strlen(str);
if (len != 0) {
dummy(len - 1);
}
}
Function example
illustrates the defect and function
possible_fix
illustrates a possible fix.
Note that the reported issue is a possible underflow while expression
len - 1
calculation within an unsigned context, when the
value of len
variable might be 0
, but not any
use of its result further.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Low | No |
C/C++ | Quality | Normal | Low | No |
Kotlin | Quality | Normal | Low | Yes |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
This checker detects issues where an integer variable is compared to some constant value and the result of an arithmetic operation on this variable after that may overflow. The comparison identifies the bounds for the possible values of this variable.
#include <stdint.h>
extern void stub(uint64_t);
void example(uint32_t x) {
if (x <= 100000) {
uint32_t y = x * 50000;
stub(y);
}
}
void possible_fix(uint32_t x) {
if (x <= 100000) {
uint64_t y = (uint64_t)x * 50000;
stub(y);
}
}
Function example
illustrates the defect: x
is compared to 10
5
and
x
is multiplied by
5
*10
4
after that. The
result of the multiplication is greater than the maximum of
uint32_t
type range, if the value of x
reaches
the upper bound of the comparison.
Function possible_fix
illustrates a possible fix: adding
a type cast of x
value to uint64_t
type before
multiplication.
fun example(n: Int) = if (n > 100000) n * 1000000 / 8 else 0
fun possibleFix(n: Int) = if (n > 100000) (n as Long) * 1000000 / 8 else 0
Function example
illustrates the defect: n
is compared with 10
5
and
n
is multiplied by 10
6
after that. The result of the multiplication is greater than the maximum
value an instance of Int
can have.
Function possibleFix
illustrates a possible fix: cast
n
to Long
before multiplication.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Low | No |
C/C++ | Quality | Normal | Low | No |
Go | Quality | Normal | Low | No |
Kotlin | Quality | Normal | Low | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
This checker detects issues where a constant value is assigned to an integer variable and the result of an arithmetic operation on this variable after that may overflow.
#include <stdint.h>
extern void stub(uint64_t);
void example(void) {
uint32_t x = 100000;
uint32_t y = x * 50000;
stub(y);
}
void possible_fix(void) {
uint64_t x = 100000;
uint64_t y = x * 50000;
stub(y);
}
Function example
illustrates the defect: constant value
10
5
is assigned to x
of type uint32_t
and x
is multiplied by
5
*10
4
after that. The
result of the multiplication is greater than the maximum value of
uint32_t
type range.
Function possible_fix
illustrates a possible fix:
changing the type of x
variable to
uint64_t
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Go | Quality | Normal | Very low | No |
Related CWEs: CWE190, CWE191, CWE194, CWE195, CWE196, CWE197.
This checker detects issues where a signed value is converted to an unsigned after a check that is not sufficient to guarantee that the converted value is non-negative.
func example(x int32) uint32 {
if x < 123 {
return uint32(x)
}
return 1
}
func possible_fix(x int32) uint32 {
if x < 123 {
if x >= 0 {
return uint32(x)
}
}
return 1
}
Function example
illustrates the defect: variable
x
is a signed integer and the condition
x < 123
does not guarantee that the value of
x
is positive in the subsequent conversion to an unsigned
type.
Function possible_fix
demonstrates a possible fix:
guarding the conversion with a non-negative check for
x
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Go | Quality | Minor | Unknown | No |
Related CWEs: CWE190, CWE191, CWE682.
This checker finds situations where the result of an arithmetic expression casts to a bigger type, but the result could overflow before this cast.
func overflow(a int8) {
// Potential integer overflow
b := int16(a + 100)
}
func no_overflow(a int8) {
// There is no integer overflow
b := int16(a) + 100
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This checker finds for shift left arithmetic operations where the left argument is a negative value. According to the C language standard, this is undefined behavior.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
Related CWEs: CWE195.
Check for assignment of a signed value to a variable of a bigger unsigned integral type. While it is not a defect by itself this may unexpectedly lead to a large resulting value if the original signed value is negative.
It may not be obvious that due to sign extension the value of b in
the example below is 0xFFFFFFFFFFFFFFFF
rather than
0xFFFFFFFF
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
This C-specific portability checker finds assignments of a value of a
signed type int
to a variable of a bigger unsigned type
size_t
. This is a common source of various issues when
porting 32-bit code to a 64-bit platform. Since on common 32-bit
platforms signed int
and unsigned size_t
types
have same width converting between them is performed differently than on
most 64-bit platforms where size_t
is usually wider.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | No |
Related CWEs: CWE194.
This checker reports unexpected sign extension during integer promotion when result value has all of its high bits set to 1 and consequently is interpreted as a very large value.
In the example below an expression p[0]
with 1-byte
unsigned type unsigned char
is promoted to larger 4-byte
signed type int
before performing computation of expression
p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24)
and then sign-extended to type unsigned long
. In case its
high bit is set the promoted value will have its high bits set as well
and the resulting value after bitwise or
computation will
be interpreted as a very large unsigned value.
unsigned long combine(unsigned char *p) {
return (p[0] | (p[1] << 8) | (p[2] << 16) | (p[3] << 24));
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Low | No |
C/C++ | Quality | Major | Low | No |
Scala | Quality | Major | Low | No |
Go | Quality | Major | Low | No |
The checker looks for situations where sensitive data may occur in logs or be visible.
For the example above specification for getpass
must be
added:
The checker may detect sensitive data by name of used variables
(passwd
, pwd
, privkey
, etc.).
Configuration option SENSITIVE_NAME_REGEX
allows
changing regular expression for name. Configuration option
LEAK_FUNC_NAME_REGEXP
allows setting additional regular
expression for function names that are considered leaks.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Security | Minor | Very high | Yes |
C# | Security | Minor | Very high | Yes |
Related CWEs: CWE209, CWE532, CWE535.
Database connection string is exposed in user-visible output.
m_log.Info("[REGION DB]: MySql - connecting: " + Util.GetDisplayConnectionString(m_connectionString));
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Security | Minor | Very high | Yes |
C# | Security | Minor | Very high | Yes |
System information (like host name, OS version, stack trace etc.) is written into publicly-visible output. It can help the attacker to gain information about the application and the environment where it is executed.
Note: exception message is also considered publicly-visible output because they are often displayed.
public partial class ExploitDebug : System.Web.UI.Page
{
protected void btnGo_Click(object sender, EventArgs e)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.AppendFormat("Current Dir: {0}\n", Environment.CurrentDirectory);
strBuilder.AppendFormat("UserName: {0}\n", Environment.UserName);
strBuilder.AppendFormat("Machine Name: {0}\n", Environment.MachineName);
strBuilder.AppendFormat("OS Version: {0}\n", Environment.OSVersion);
throw new Exception(strBuilder.ToString());
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Low | No |
C/C++ | Quality | Major | Low | No |
Scala | Quality | Major | Low | No |
Go | Quality | Major | Low | No |
Python | Quality | Major | Low | No |
Checker finds situations where hardcoded password is passed to functions manipulating with passwords.
var pass = []byte("0123456789abcdef0123456789abcdef")
func main() {
block,_ := aes.NewCipher(pass) // Using of hardcoded password.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Security | Critical | Very high | Yes |
C/C++ | Security | Critical | Unknown | No |
C# | Security | Critical | Very high | Yes |
This checker reports various cases of insecure usages of shell commands execution or dynamic libraries loading when attacker is able to influence the environment of the running application.
In the example above if the attacker is able to modify the
PATH
environment variable where some_command
is searched he may set it in such way that allows to run his own
malicious application instead of the intended one. The problem can be
fixed as follows:
const char *command = "/path/to/some_command"; // Specify absolute path to command.
system(command);
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Security | Critical | Unknown | No |
Related CWEs: CWE676.
The checker reports a warning for a pattern when C standard library
function strncmp
for string comparison uses the result of
calling string length calculation function strlen
as the
length parameter directly. In this way only the prefix of the string is
compared because comparison stops at the null-terminator so it and the
remaining characters are not compared. It may be the source of a
vulnerability when checking the validity of sensitive data, such as
passwords. For such cases the length parameter should be calculated as
result of the expression strlen(arg) + 1
to account for the
end of the string.
int get_id(char*name);
int bad_code(char*name, char*passwd, char* argv[]) {
if (strncmp(passwd, argv[1], strlen(passwd)) == 0) {
return get_id(name);
} else {
return -1;
}
}
int good_code(char*name, char*passwd, char* argv[]) {
if (strncmp(passwd, argv[1], strlen(passwd) + 1) == 0) {
return get_id(name);
} else {
return -1;
}
}
int good_code_too(char*name, char*passwd, char* argv[]) {
if (strcmp(passwd, argv[1]) == 0) {
return get_id(name);
} else {
return -1;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
This checker finds using of an insecure in many contexts HTTP
GET
method instead of the POST
method.
void foo() {
CURL* curl = curl_easy_init();
if (curl) {
CURLcode res;
// ...
res = curl_easy_perform(curl);
}
}
Function foo
illustrates the defect: libcurl library
function curl_easy_init
creates a new session with a
GET
method by default. The operation will be processed with
the default method after calling curl_easy_perform
.
void bar() {
CURL* curl = curl_easy_init();
if (curl) {
CURLcode res;
// ...
curl_easy_setopt(curl, CURLOPT_POST, 0L);
res = curl_easy_perform(curl);
}
}
Function bar
illustrates the defect: libcurl library
function curl_east_setopt
with the option
CURLOPT_POST
and third parameter equal to zero resets the
request type back to the GET
method.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Security | Minor | Unknown | No |
Setting inappropriate parameter values in a libcurl library function
curl_easy_setopt
call may lead to security issues.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Critical | Unknown | No |
C/C++ | Quality | Critical | Unknown | No |
Kotlin | Quality | Critical | Unknown | Yes |
Related CWEs: CWE833.
This checker finds code that may result in two or more threads waiting for each other, holding locks needed for the other to resume execution.
pthread_mutex_t *a, *b;
void thread1() {
pthread_mutex_lock(a);
pthread_mutex_lock(b);
// ...
}
void thread2() {
pthread_mutex_lock(b);
pthread_mutex_lock(a);
// ...
}
class DeadlockTest {
private Object lock;
public synchronized void direct() {
synchronized(lock) {
// ...
}
}
public void reverse() {
synchronized(lock) {
synchronized(this) {
// ...
}
}
}
}
class DeadlockExample() {
lateinit var l: Any
@Synchronized
fun direct(): Unit {
synchronized(l) {
// ...
}
}
fun reverse(): Unit {
synchronized(l) {
synchronized(this) {
// ...
}
}
}
/*
fun reverseCorrect(): Unit {
synchronized(this) {
synchronized(l) {
// ...
}
}
}
*/
}
Functions direct
and reverse
illustrate the
defect: if there are two threads working with the same instance of
DeadlockExample
class, and one thread executing
direct
function acquires this
lock and tries
to acquire l
lock, while another thread executing
reverse
function acquires l
lock and tries to
acquire this
lock, then both threads will wait indefinitely
for one of them to release the lock.
Possible fix: use reverseCorrect
function instead of
reverse
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | No |
This checker finds code that might lead to a class loading deadlock. JVM might start loading super and child classes in separate threads. If static initialization block contains a reference to a subclass it might lead to a deadlock.
class SuperClass {
final static SubClass subClass = new SubClass();
}
class SubClass extends SuperClass { }
The class loader sees the subClass
static field and
tries to lock SubClass
for loading. Another thread may load
SubClass
which is inherited from SuperClass
.
It locks SuperClass
for loading which leads directly to the
deadlock.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Go | Quality | Normal | Unknown | No |
This checker finds situations, when a goroutine may not have time to
complete its execution, which are caused by using
sync.WaitGroup.Add()
inside the goroutine and
sync.WaitGroup.Wait()
after the goroutine’s function
initialization.
func foo() {
var wg sync.WaitGroup
var x int32 = 0
for i := 0; i < 100; i++ {
go func() {
wg.Add(1)
atomic.AddInt32(&x, 1)
wg.Done()
}()
}
wg.Wait()
}
Function foo
illustrates the defect:
wg.Add(1)
inside the goroutine can be executed after the
execution of wg.Wait()
, which may not wait for the
goroutine to finish.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Major | High | Yes |
The checker DOUBLE_LOCK
finds situations where a lock is
acquired twice in succession by the same thread (without getting
released).
void proc(pthread_mutex_t* mut, int x) {
pthread_mutex_lock(mut);
if (x)
pthread_mutex_unlock(mut);
pthread_mutex_lock(mut); // DOUBLE_LOCK: pthread_mutex_unlock() might not have been called.
// ...
pthread_mutex_unlock(mut);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | High | Yes |
C/C++ | Quality | Major | High | Yes |
Go | Quality | Major | High | Yes |
Related CWEs: CWE617, CWE667, CWE764.
This checker finds situations where a thread acquires a lock during function execution and leaves it locked on some paths to the exit from the function, but the function also unlocks it on some other paths. This may be caused by failing to release a lock when an erroneous situation happens.
void test(pthread_mutex_t* mut, int flag) {
pthread_mutex_lock(mut);
if (flag) {
return; // `mut` isn't unlocked on this path.
}
pthread_mutex_unlock(mut);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | High | No |
C/C++ | Quality | Minor | High | No |
Go | Quality | Minor | High | No |
Related CWEs: CWE617, CWE667, CWE764.
It is subtype of NO_UNLOCK. The checker emits warnings if mutex is locked on some paths and there are no any unlock operation.
#include <pthread.h>
pthread_mutex_t m;
void lock() {
pthread_mutex_lock(&m);
}
int func(int flag) {
if(flag>0) {
lock();
if(flag)return 0;//NO_UNLOCK.STRICT
}
return 1;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | Yes |
This checker finds situations where a lock is acquired in a class constructor and not released in its destructor.
#include <pthread.h>
class LockGuardBad {
pthread_mutex_t m;
public:
LockGuardBad() { pthread_mutex_lock(&m); } // report NO_UNLOCK.CTOR
~LockGuardBad() {}
};
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
Kotlin | Quality | Major | Unknown | Yes |
This checker finds situations where pooled or reusable objects are used for synchronization. Such objects can be locked by external code, which may lead to unexpected blocking or deadlock (the first three examples below). Also, if the object used for synchronization can be modified by external code, and if this happens during the execution of a critical section, then the next thread attempting to enter the critical section will not be blocked as expected (the fourth example below).
All three methods (localStringLockTest
,
fieldStringLockTest
and internStringTest
) use
the singleton string object stored in Java String Pool for
synchronization.
class StringLiteralExample {
public void localStringLockTest() {
String localStringLock = "Some string";
synchronized (localStringLock) {
// ...
}
}
private final String fieldStringLock = "Some string";
public void fieldStringLockTest() {
synchronized (fieldStringLock) {
// ...
}
}
private final String internStringLock = new String("Some string").intern();
public void internStringTest() {
synchronized (internStringLock) {
// ...
}
}
}
Boolean literal values share the unique instances of the
Boolean
class.
class BooleanLiteralExample {
private final Boolean booleanLock = Boolean.TRUE;
public void booleanLockTest() {
synchronized (booleanLock) {
// ...
}
}
}
Boxed types may reuse the instance for some values.
PossibleFix
method illustrates the possible fix: create a
unique instance of such type object.
class BoxedPrimitiveExample {
private int index = 0;
private final Integer boxedPrimitiveLock = index;
public void boxedPrimitiveTest() {
synchronized (boxedPrimitiveLock) {
index++;
// ...
}
}
private final Integer uniqueLock = new Integer(index);
public void possibleFix() {
synchronized (uniqueLock) {
index++;
// ...
}
}
}
Public non-final fields are accessible to the outside code. It’s
recommended to use private final fields for synchronization as
possibleFix
method shows.
class PublicNonFinalFieldExample {
public Object publicNonFinalLock = new Object();
public void publicNonFinalLockTest() {
synchronized (publicNonFinalLock) {
// ...
}
}
private final Object privateFinalLock = new Object();
public void possibleFix() {
synchronized (privateFinalLock) {
// ...
}
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | High | Yes |
This checker finds situations where a local variable allocated on stack is used as a synchronization primitive. Since each thread has its own stack such locks can’t be used for inter-thread synchronization.
class AutoLock {
public:
AutoLock(pthread_mutex_t *_m) {
m = _m;
}
AutoLock() {}
~AutoLock() {
pthread_mutex_unlock(m);
}
void lock() {
pthread_mutex_lock(m);
}
private:
pthread_mutex_t *m;
};
pthread_mutex_t m_glob; // It should be used.
void bar() {
AutoLock l;
l.lock(); // Variable `l` is on stack. `l.m` is also on stack.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | Yes |
Related CWEs: CWE667.
This checker finds situations where a blocking function is called inside a critical section. As a result, all threads have to wait for the blocking function to return, not just the thread that called it.
int proc(pthread_mutex_t* mut, pthread_mutex_t* socket) {
pthread_mutex_lock(mut); // Lock.
int res = accept(socket); // This function might take a lot of time.
pthread_mutex_unlock(mut);
return res;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | Yes |
Related CWEs: CWE662.
This checker finds cases of non-atomic usage of non-constant shared data where critical section is not sufficient to protect a variable.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Unknown | No |
C# | Quality | Major | Average | Yes |
Related CWEs: CWE366.
This detector collects statistics in a single file of variables reads and writes inside and outside critical sections. Based on this statistical data it detects possible usages of variables outside critical sections that may lead to data races.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
Kotlin | Quality | Major | Unknown | Yes |
This detector collects statistics for each access to the field.
Statistics store whether access to the field was under lock and under
what lock in particular. By handling the statistics the detector decides
if some accesses to field may cause race condition. Constructors and
equals
, hashCode
and clone
methods are excluded from consideration.
The warning will be emitted by this detector if:
NO_LOCK.STAT.EX.THRESHOLD
percentage of accesses to a field are under the same lock;NO_LOCK.STAT.EX.TWO_OF_THREE
configuration option is
true
.NO_LOCK.STAT.EX.THRESHOLD
is equal to 80% by default.
NO_LOCK.STAT.EX.TWO_OF_THREE
is false
by
default.
Note that NO_LOCK.STAT.EX.TWO_OF_THREE
is
true
for all examples below.
public class NoLockStatExample {
private int a;
private Object l = new Object();
public Example() {
a = 0; // Ignore this access to field `a` in constructor.
}
public synchronized void bar() {
a++; // Access to field `a` under `this` lock.
}
public void foo(int b) {
synchronized(l) {
a += b; // First access to field `a` under `l` lock.
a += 2; // Second access to field `a` under `l` lock.
}
}
/*
public void barCorrect() {
synchronized(l) {
a++; // Access to field `a` under `this` lock.
}
}
*/
}
Function bar
illustrates the defect: field
a
is accessed under this
lock, while in most
cases (2 of 3) this field is accessed under l
lock in
foo
function. This situation may cause a race
condition.
Possible fix: use barCorrect
function instead of
bar
, field a
is accessed under l
lock in barCorrect
function.
class NoLockStatExample {
private var a: Int = 0
lateinit var l: Any
@Synchronized
fun bar() {
a++ // Access to `a` under `this` lock.
}
fun foo(b: Int) {
synchronized(l) {
a += b // Access to `a` under `l` lock.
a += 2 // Access to `a` under `l` lock.
}
}
/*
fun barCorrect() {
synchronized(l) {
a++
}
}
*/
}
Function bar
illustrates the defect: property
a
is accessed under this
lock, while in most
cases (2 of 3) this property is accessed under l
lock in
foo
function. This situation may cause race condition.
Possible fix: use barCorrect
function instead of
bar
, property a
is accessed under
l
lock in barCorrect
function.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
This Java-specific detector issues a warning if a field is declared
with GuardedBy
annotation but later accessed without locks
(such as synchronized
).
import com.android.internal.annotations.GuardedBy;
public class NoLockGuard001 {
@GuardedBy("lock")
public boolean data;
private Object lock = new Object();
public void foo1() {
synchronized (lock) {
data = false; // Ok
}
}
public void foo2() {
data = true; // NO_LOCK.GUARD here
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
This detector finds situations when an assignment to a variable
occurs in a synchronized
block, while the variable’s value
is checked before that block.
This may lead to a synchronization error, when many processes simultaneously pass the check and then reach the synchronized block one by one.
public class NoCheckInLock {
class ClassA {
private int x;
ClassA(int xx) {
x = xx;
}
}
class ClassB {
private Object fLock = new Object();
public ClassA fSharedObj;
public boolean fCritialSection = false;
public void access(int x) {
// Compare : Checking Value 'fCritialSection'.
if (fCritialSection) {
return;
}
// Synchronized
synchronized (fLock) {
// NO_CHECK_IN_LOCK : Field 'fCritialSection' has been compared
// without locking and is assigned under lock.
fCritialSection = true;
}
fSharedObj = new ClassA(x);
}
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
This detector finds double-checked locking anti-pattern commonly used to reduce the overhead of acquiring a lock by testing the locking criterion before acquiring the lock.
class DoubleCheckedLockingExample {
private static class A { }
private A aRef;
public A brokenIdiom() {
if (aRef == null) {
synchronized (this) {
if (aRef == null) {
aRef = new A();
}
}
}
return aRef;
}
private volatile A aVolRef;
public A getA() {
A localRef = aVolRef;
if (localRef == null) {
synchronized (this) {
localRef = aVolRef;
if (localRef == null) {
aVolRef = localRef = new A();
}
}
}
return localRef;
}
}
Function brokenIdiom
illustrates the defect. Function
possibleFix
illustrates a possible fix: use
volatile
keyword in the field declaration.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
This Java-specific detector finds situations when
synchronized
block contains wait
method but
does not contain an enclosing loop. This may cause the wait
to be triggered by a condition that it is not initially intended for (so
called ‘spurious wake-up’). The solution is to put the loop under
synchronized
.
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
public class BadCheckOfWaitCond {
private void testFunc(boolean cond1, boolean cond2) {
FileInputStream fis = null;
try {
fis = new FileInputStream(new File("testFile"));
while (fis.read() > 0) {
if (cond1) {
while (true) {
synchronized (this) {
try {
if (cond2) {
this.wait(); // report BAD_WAIT_OF_COND here
} else {
break;
}
} catch (InterruptedException e) {
if (fis != null)
fis.close();
e.printStackTrace();
}
}
}
} else {
break;
}
}
fis.close();
} catch (Throwable th) {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
th.printStackTrace();
}
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Very high | No |
Related CWEs: CWE377.
Warning of this type is emitted where a call to function
mkstemp
is not preceded by a call to umask
,
which is necessary to restrict access rights to the newly created file
to its creator.
For most libc implementations function mkstemp
creates
file with correct permissions and this warning is not needed.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Very high | No |
Related CWEs: CWE377.
Even if function umask
was called before
mkstemp
, as checked by RACE.NO_UMASK, it’s possible that access
rights set by umask
are too permissive (for example,
777
). This warning is emitted if that is the case.
For most libc implementation function mkstemp
creates
file with correct permissions and this warning is not needed.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Average | Yes |
JavaScript | Quality | Major | Average | Yes |
C/C++ | Quality | Major | Average | Yes |
Go | Quality | Major | Average | Yes |
Kotlin | Quality | Major | Average | Yes |
Python | Quality | Major | Average | Yes |
C# | Quality | Minor | Average | Yes |
This checker reports a suspicious expression where the result of operation is always a constant regardless of the value of its variable operands. This may be the result of a typo or an application logic error.
int func(unsigned char c) {
if (c > 1024) { // Always false since unsigned char type has possible value
// in the range of [0; 255].
return -1;
}
}
Subtypes of this warning INVARIANT_RESULT.OP_ASSIGN
and INVARIANT_RESULT.OP_ZERO
indicate the use of a composite logic assignment operator and logic
operator with zero operands respectively. Such patterns are separated to
contain possible false positives when constant macros are used. Subtype
INVARIANT_RESULT.MACRO
is reported when neither INVARIANT_RESULT.OP_ASSIGN
nor
INVARIANT_RESULT.OP_ZERO
should be issued and the
expression is a part of a macro expansion.
In the following example, second operand of bitwise AND is zero which makes the condition always false regardless of the value of the first operand.
In the following example, a logical operator !
appears
to have been substituted for a bitwise operator ~
.
In the following example, parentheses are missed.
In the following example, the right-hand side of an |=
expression is of a wide type than the left-hand side and has high-order
bits set that will not affect the left-hand side.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Unknown | No |
C/C++ | Quality | Minor | Unknown | No |
Scala | Quality | Minor | Unknown | No |
Go | Quality | Minor | Unknown | No |
This checker is similar to INVARIANT_RESULT, but uses SMT solver to find more complex expressions with constant result.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | Yes |
Related CWEs: CWE561.
This checker finds code where the second operand of a logical operator has no impact on the result.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Average | Yes |
Visual Basic .NET | Quality | Normal | Average | Yes |
C/C++ | Quality | Normal | Average | Yes |
Go | Quality | Normal | Average | Yes |
Kotlin | Quality | Normal | Average | Yes |
Python | Quality | Normal | Average | Yes |
C# | Quality | Normal | Average | Yes |
Related CWEs: CWE561.
This checker finds source code that can’t be executed because control flow path to the code from the rest of the program is unfeasible. This is commonly caused by logic errors in the related code or nearby.
In the following example the initial value of the
fVerifyTD
variable is FALSE
and it can only be
set to TRUE
in the true branch of the first if
statement. Because of the incorrectly used
else if (fVerifyTD)
statement (instead of the intended
if (fVerifyTD)
) this check is only performed in the false
branch of the first if
statement (instead of being
performed always) where the value of the fVerifyTD
variable
can only have the initial FALSE
value and so the assignment
of the fTrustTD
variable in that if
statement
body is unreachable.
BOOL fVerifyTD = FALSE;
// If this is an exported type with a mdTokenNil class token, then then
// exported type did not give a typedefID hint. We won't be able to trust the typedef
// here.
if ((FoundExportedType != mdTokenNil) && (FoundCl == mdTokenNil))
{
fVerifyTD = TRUE;
fTrustTD = FALSE;
}
// verify that FoundCl is a valid token for pFoundModule, because
// it may be just the hint saved in an ExportedType in another scope
else if (fVerifyTD)
{
fTrustTD = pFoundModule->GetMDImport()->IsValidToken(FoundCl);
}
fun example(a: Int) {
when {
a > 10 -> print("a > 10")
a <= 10 -> print("a <= 10")
else -> print("unreachable")
}
}
fun possibleFix(a: Int) {
when {
a > 10 -> print("a > 10")
a <= 10 -> print("a <= 10")
}
}
else
branch in when
expression is
unreachable because the first and the second branches cover all possible
values of a
. The redundant branch should be removed to fix
this defect.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Unknown | No |
C/C++ | Quality | Minor | Unknown | No |
Subtype of UNREACHABLE_CODE for statements that follow a call of a function that terminates program execution.
In the following example function
report_instruction_timeout
calls a panic
function that never returns so the assignment of a new value to the
start_time
variable can’t be ever executed:
while (1) {
cpu_relax();
status = GET_MSEG_HANDLE_STATUS(h);
if (status != CCHSTATUS_ACTIVE)
break;
if (GRU_OPERATION_TIMEOUT < (get_cycles() - start_time)) {
report_instruction_timeout(h); // Calls 'panic' which doesn't return
start_time = get_cycles(); // so this assignment is unreachable
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
Visual Basic .NET | Quality | Normal | Low | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
C# | Quality | Normal | Low | Yes |
Subtype of UNREACHABLE_CODE for situations where a code fragment is unreachable because the preceding operation always throws an exception.
In the following code the compiledResourceFile
variable
in the case ".resx"
block is never assigned because of the
exception in the CompileResx
function:
private FileInfo CompileResx(Configuration solutionConfiguration) {
// For performance reasons, compilation of resx files is done in
// batch using the ResGen task in ManagedProjectBase.
throw new InvalidOperationException();
}
public FileInfo Compile(Configuration solutionConfiguration) {
FileInfo compiledResourceFile = null;
switch (InputFile.Extension.ToLower(CultureInfo.InvariantCulture)) {
case ".resx":
compiledResourceFile = CompileResx(solutionConfiguration);
break;
...
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Unknown | No |
C/C++ | Quality | Minor | Unknown | No |
Subtype of UNREACHABLE_CODE for
situations where default
label of a switch statement is
unreachable. Related situations with enum variables used as conditions
are reported with the UNREACHABLE_CODE.ENUM warning type.
While this is common to have such unreachable statements when
default
cases are strictly enforced by a coding policy or
added to just be on the safe side, occasionally such reports may show an
issue with the program logic.
In this example the default
case is unreachable because
the if
statement enclosing the switch
statement restricts the possible values of the format
variable to those handled by other cases:
if ((format == CTRL_RAW || format == CTRL_USER || format == CTRL_MGMT)
&& size >= 8) {
...
switch (format) {
case CTRL_RAW:
title = "RAW Open";
break;
case CTRL_USER:
title = "USER Open";
break;
case CTRL_MGMT:
title = "MGMT Open";
break;
default: // This case label is unreachable
title = "Control Open";
break;
}
print_packet(tv, cred, '@', index, channel, COLOR_CTRL_OPEN,
title, comm, details);
In the following example function
llhttp__internal__c_test_flags_2
can only return values
0
or 1
:
int llhttp__internal__c_test_flags_2(
llhttp__internal_t* state,
const unsigned char* p,
const unsigned char* endp) {
return (state->flags & 256) == 256;
}
But its result is used as if it can return other values too:
switch (llhttp__internal__c_test_flags_2(state, p, endp)) {
case 0:
goto s_n_llhttp__internal__n_error_11;
case 1:
goto s_n_llhttp__internal__n_invoke_test_flags_3;
default:
goto s_n_llhttp__internal__n_invoke_llhttp__before_headers_complete;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
Subtype of UNREACHABLE_CODE for
switch
statement with an enum variable as a condition
expression when that switch
statement contains case labels
for each of the possible enumeration type values. The warning is emitted
for the default
label which is unreachable in such
situation. While technically the code statements under this label are
indeed unreachable the use of a default
label may be
enforced by a coding policy.
Note that for C/C++ an enumeration variable is just an integer variable that is able to hold any value from the enumeration type domain so it can still hold an integer value outside of that domain but relying on this makes a program harder to understand and support but can be useful to filter out unsupported values.
In this example the enumeration is declared as containing four elements:
enum dss_rotation {
dss_rotation_0_degree = 0,
dss_rotation_90_degree = 1,
dss_rotation_180_degree = 2,
dss_rotation_270_degree = 3,
};
The following switch statement handles all of them (the code for the
first three is removed for brevity) in unique ways and also has a
default
label with own code that is unreachable under
normal program execution:
switch (rotation) {
case dss_rotation_90_degree:
...
case dss_rotation_180_degree:
...
case dss_rotation_270_degree:
...
case dss_rotation_0_degree:
if (!mirroring) {
*cropped_offset = (line_length * ps) *
crop->top + (crop->left / vr_ps) * ps;
} else {
*cropped_offset = (line_length * ps) *
crop->top + (crop->left / vr_ps) * ps +
(line_length * (crop->height - 1) * ps);
}
break;
default:
*cropped_offset = (line_length * ps * crop->top) /
vr_ps + (crop->left * ps) / vr_ps +
((crop->width / vr_ps) - 1) * ps;
break;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
Subtype of UNREACHABLE_CODE for
situations where a case label of a switch
statement is
unreachable.
In the following example the label case DCA_DOLBY
is
unreachable because the DCA_CHANNEL_MASK
mask zeros all
bits except the lower six so the resulting value lies in the range from
0
to 63
which 101
doesn’t belong
to.
#define DCA_MONO 0
#define DCA_CHANNEL 1
#define DCA_STEREO 2
#define DCA_STEREO_SUMDIFF 3
#define DCA_STEREO_TOTAL 4
#define DCA_DOLBY 101
#define DCA_CHANNEL_MASK 0x3F
...
switch (flags & DCA_CHANNEL_MASK) {
case DCA_STEREO:
case DCA_STEREO_SUMDIFF:
case DCA_STEREO_TOTAL:
case DCA_DOLBY:
chans = 2;
if (tpos) {
tpos[0] = GST_AUDIO_CHANNEL_POSITION_FRONT_LEFT;
tpos[1] = GST_AUDIO_CHANNEL_POSITION_FRONT_RIGHT;
}
break;
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Average | No |
C/C++ | Quality | Normal | Average | No |
Go | Quality | Normal | Average | No |
Kotlin | Quality | Normal | Average | No |
Python | Quality | Normal | Average | No |
Subtype of UNREACHABLE_CODE for situations, where a code is unreachable due to a condition that depends on global variables. This is common for debugging code locked behind a check of a global variable value, but occasionally such reports may show an issue with the program logic.
int wgn_verbose = FALSE; // Global variable that is never reassigned
...
if (wgn_verbose) // The following code is essentially unreachable unless the
{ // value of 'wgn_verbose' is changed and the program is recompiled
int i;
for (i=0; i < margin; i++)
cout << " ";
cout << "stopped samples: " << node.samples() << " impurity: "
<< node.get_impurity() << endl;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | No |
Related CWEs: CWE561.
C/C++-specific subtype of UNREACHABLE_CODE for continue
statements in loops whose body is known to be executed only once, such
as do-while
loops with constant false conditions. Contrary
to the probable intention to restart the loop execution from the
beginning the execution of the loop will be terminated. If this is the
intended behaviour it is better to use a break
statement
instead to reflect it more clearly.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Visual Basic .NET | Quality | Normal | Very high | Yes |
C/C++ | Quality | Normal | Average | No |
C# | Quality | Normal | Very high | Yes |
Related CWEs: CWE561.
Subtype of UNREACHABLE_CODE for situations where there is no execution path, even an unfeasible one, to a code fragment.
Common cases for harmless unreachable statements needed for syntactic
correctness, such as return statements or C/C++ va_end
statements are reported by UNREACHABLE_CODE.NO_PATH.RETURN
and UNREACHABLE_CODE.NO_PATH.VARARG
subtypes respectively.
Here a programmer forgot to add a default
label before
the throw
statement, so the throw
statement
belongs to case 4
section but is located after
break
.
switch (ByteCapacity)
{
case 1: Stream.WriteByte((byte)CurrentValue); break;
case 2: Stream.WriteStruct((ushort)CurrentValue); break;
case 4: Stream.WriteStruct((uint)CurrentValue); break;
throw(new InvalidOperationException());
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
C/C++ | Quality | Normal | Unknown | No |
Go | Quality | Normal | Unknown | No |
This checker detects redundant comparisons.
int example(int a) {
if (a >= 2) {
if (a > 0) { //svace: emitted REDUNDANT_COMPARISON
return 1;
}
}
return 0;
}
In the example above the second if-condition is always true because
the first if-condition has already checked that a
more than
zero.
func ret(a int) int {
return 10
}
func example(a int) int {
z := ret(a)
if z == 10 { //svace: emitted REDUNDANT_COMPARISON
return z + 3;
}
return z + a; /* unreachable code */
}
Function example
illustrates the defect: the variable
z
has a constant value received from function
ret
, so the if-condition is redundant because
z
is always 10
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Unknown | No |
C/C++ | Quality | Minor | Unknown | No |
Subtype of REDUNDANT_COMPARISON for situations, where a code is redundant due to the condition that depends on the returned value of the function call.
int func() {
return 0;
}
void example(int a) {
if (func() < 0) { //svace: emitted REDUNDANT_COMPARISON.RET
++a;
}
}
Function example
illustrates the defect: the returned
value of func()
has a constant value equals to
0
therefore the comparison with 0
is always
false.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | Yes |
C/C++ | Quality | Normal | Unknown | Yes |
Go | Quality | Normal | Unknown | Yes |
Kotlin | Quality | Normal | Unknown | Yes |
Python | Quality | Normal | Unknown | Yes |
This checker detects always-false expressions which are used in conditions that could change the control flow of a program.
Similar always-true expressions are reported by
REDUNDANT_COMPARISON
checker.
In the example above the author of the code first checks that the
value of pointer p
is not NULL
, so it can be
copied using strdup
function but then makes a typo checking
that the result of strdup
isn’t NULL
using the
same variable p
instead of q
. This typo leads
to the comparison always being false.
fun example(a: Any) {
if (a is Int) {
// Handle a as Int value.
} else if (a is Float) {
// Handle a as Float value.
} else if (a is Int) {
// Handle a as Long value.
}
}
fun possibleFix(a: Any) {
when (a) {
is Int -> { /* Handle a as Int value. */ }
is Float -> { /* Handle a as Float value. */ }
is Long -> { /* Handle a as Long value. */ }
}
}
Function example
illustrates the defect: the result of
the second a is Int
expression is always false.
Function possibleFix
illustrates a possible fix: replace
the second a is Int
expression by the
a is Long
expression. Also, it’s recommended to use
when
expression instead of large if-else expression. Kotlin
compiler produces warnings about duplicate labels in when
expressions.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Minor | Unknown | No |
C/C++ | Quality | Minor | Unknown | No |
Go | Quality | Minor | Unknown | No |
Kotlin | Quality | Minor | Unknown | No |
Subtype of REDUNDANT_COMPARISON.ALWAYS_FALSE for situations, where a code is redundant due to condition that depends on global variables.
int flag = 0;
#define mode(a) flag
void f(int* a) {
int m = mode(1);
if(m) { //svace: REDUNDANT_COMPARISON.GLOBAL
*a = 0;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Undefined | Unknown | No |
Go | Quality | Undefined | Unknown | No |
Kotlin | Quality | Undefined | Unknown | No |
Subtype of REDUNDANT_COMPARISON for situations, when there is a presence/absence check of a key in a map, but the given key is always present/absent.
import java.util.*;
public class Example {
public void presentAfterRemove(Map<Object, String> box, Object key) {
box.put(key, "value");
if (box.containsKey(key)) { // svace: emitted REDUNDANT_COMPARISON.MAP
// ...
}
}
public void absentAfterRemove(Map<Object, String> box, Object key) {
box.remove(key);
if (box.containsKey(key)) { // svace: emitted REDUNDANT_COMPARISON.MAP
// ...
}
}
public void absentAfterClear(Map<Object, String> box, Object key) {
box.clear();
if (box.containsKey(key)) { // svace: emitted REDUNDANT_COMPARISON.MAP
// ...
}
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Normal | Unknown | No |
Subtype of REDUNDANT_COMPARISON when there is a test for equality of two variables, but their types do not have overlap in subtypes.
import java.util.*;
public class Example {
public void equalsTest(List<String> names) {
String name = names.get(0);
if ("<unknown>".equals(names)) { // svace: emitted REDUNDANT_COMPARISON.EQUALS
// ...
}
name = names.get(1);
if ("<unknown".equals(name)) { // svace: not_emitted REDUNDANT_COMPARISON.EQUALS
// ...
}
}
}
This checker also works with JUnit asserts and reference equals.
import junit.framework.Assert;
import java.util.*;
public class Example1 {
interface CanFly {}
interface CanSwim {}
interface CanWalk {}
class Duck1 implements CanFly {}
class Duck2 implements CanSwim {}
class Duck3 implements CanWalk, CanFly {}
public void assertEqualsTest(CanFly first, CanSwim second, CanWalk third) {
// ...
Assert.assertEquals(first, second); // svace: emitted REDUNDANT_COMPARISON.EQUALS
// ...
// we can compare these objects because they can be of type Duck3
Assert.assertEquals(first, third); // svace: not_emitted REDUNDANT_COMPARISON.EQUALS
// ...
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | Yes |
Related CWEs: CWE457.
This checker finds situations where a value of locally defined (automatic) variable is accessed, but the variable was never initialized.
Variable st.fld
accessed at the last line was never
initialized.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Very low | No |
This checker finds different situations, where a structure of structure field is not initialized before using.
typedef struct {
int k;
int p;
int q;
} T;
void test1(T* dst) {
T val;
val.k = 3;//Structure val was not fully initilialized
memcpy(dst, &val, sizeof(T));//copied not initilized sturcture - UNINIT.STRUCT
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
Java | Quality | Major | Unknown | Yes |
Kotlin | Quality | Major | Unknown | Yes |
This detector finds following situations: method of some class overrides the method of its base class which is called from constructor, also, the method of derived class accesses this class field. This causes a NullPointerException to be thrown.
This detector requires the
COLLECT_JAVA_METHOD_OVERRIDERS
setting to be enabled. Run
svace config COLLECT_JAVA_METHOD_OVERRIDERS true
to enable
the option.
class Employee {
private final String name;
public Employee(String name) {
this.name = name;
printInfo();
}
void printInfo() {
System.out.println(name);
}
public String getName() {
return name;
}
}
class RussianEmployee extends Employee {
private final String secondName;
public RussianEmployee(String name, String secondName) {
super(name);
this.secondName = secondName;
}
@Override
void printInfo() {
System.out.println(getName() + " " + secondName);
}
}
Defect description: RussianEmployee
constructor calls
Employee
constructor which calls printInfo
method. RussianEmployee
class overrides
printInfo
method in which uninitialized field
secondName
is accessed.
Possible fix: do not call printInfo
method from
constructor of base class.
data class Prop(val len: Int, val isRussian: Boolean)
open class Employee(val name: String) {
init { printInfo() }
private fun printInfo() = println(
"Employee: name=$name name_len=${baseProp.len} is_russian=${baseProp.isRussian}")
open val baseProp: Prop = Prop(name.length, false)
}
class RussianEmployee(name: String, val secondName: String) : Employee(name) {
override val baseProp: Prop = Prop(name.length + secondName.length, true)
}
Defect description: RussianEmployee
constructor calls
Employee
constructor which calls printInfo
method. printInfo
method calls getBaseProp
method. RussianEmployee
class overrides
getBaseProp
method in which uninitialized field
secondName
is accessed.
Possible fix: do not call printInfo
method from
constructor of base class.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Critical | Unknown | No |
The checker finds situations when library function fills some buffer but caller code does not check that all values are filled. Buffer may have uninitialized memory.
struct S {
int a;
int b;
char buf[10];
int c;
};
int func_with_error(int fd) {
struct S s;
read(fd, &s, sizeof(struct S));
int x = s.c; //error, we don't know that structure 'S' was fully filled.
return x; //x may contain uninitialized values
}
int fix(int fd) {
struct S s;
int len = read(fd, &s, sizeof(struct S));
if (len!=sizeof(struct S))
return -1;
int x = s.c;
return x;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Unknown | No |
Related CERTs: CERT-SIG30-C.
This checker finds possible cases of using inconsistent data while executing asynchronous-unsafe functions from signal handlers. According to Section 7.14.1.1 of the C Rationale [ISO/IEC 2003]:
When a signal occurs, the normal flow of control of a program is interrupted. If a signal occurs that is being trapped by a signal handler, that handler is invoked. When it is finished, execution continues at the point at which the signal occurred. This arrangement can cause problems if the signal handler invokes a library function that was being executed at the time of the signal.
The warning is emitted if signal handler calls any asynchronous-unsafe function.
char* info;
void handler(int signum) {
free(info); // Free is an asynchronous unsafe function,
// so the warning will be emitted here.
info = NULL;
}
int main(void) {
signal(SIGINT, handler); // Register `handler` as a handler.
// Skipped.
return 0;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
Related CERTs: CERT-SIG34-C.
This checker finds a rare case of race condition where a handler tries to reinstall a signal handler from itself. On systems that uninstalls signal handler after signal delivery (SysV and Windows behavior) another signal can happen before the reinstallation code is run. This will lead to default signal handler call. On systems where signal handlers need to be explicitly uninstalled (BSD and Linux behavior), reinstallation of same signal handler doesn’t make sense.
The warning is emitted if a signal handler calls function
signal
for re-registering same handler again.
void handler(int signum) {
signal(signum, handler); // Call to `signal` from within `handler` to register.
// `handler` again; the warning is emitted here.
}
int main(void) {
signal(SIGINT, handler); // Register `handler` as a handler.
// Skipped.
return 0;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Unknown | No |
Related CERTs: CERT-SIG31-C.
This checker detects race conditions that can be caused by accessing
or modifying shared objects in signal handlers. The only way to
guarantee that data is read in consistent state and remains in
consistent state after modification is to read and write only variables
of type volatile sig_atomic_t
inside of signal
handlers.
The warning is emitted if signal handler accesses or modifies global
data with the type other than volatile sig_atomic_t
.
volatile sig_atomic_t iflag = 0;
int kflag = 0;
void int_handler(int signum) {
iflag = 2; // All ok, `iflag` is `volatile sig_atomic_t`.
}
void kill_handler(int signum) {
kflag = 3; // The warning is emitted: modifying shared object `kflag` in a signal handler.
}
int main(void) {
signal(SIGINT, int_handler); // Handler is registered.
signal(SIGKILL, kill_handler); // Handler is registered.
// Skipped.
return 0;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Unknown | No |
This checker finds situations where a longjmp
function
is called from a signal handler, which may cause inconsistent data use.
Such calls can lead to problems similar to those discussed in SIGHANDLER.ASYNC_UNSAFE.
The warning is emitted if the signal handler function calls the
longjump
function. It is based on a SIG32-C
rule from the CERT Secure Coding Standard.
static jmp_buf env;
void handler(int signum) {
longjmp(env, 1); // Call to `longjump` from a signal handler.
}
int main(void) {
signal(SIGINT, handler); // Handler is registered.
// Skipped.
return 0;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
This checker finds situations where undefined behavior may result
from calling function raise
from a signal handler.
According to C99, Section 7.14.1.1 [ISO/IEC 9899:1999]: > If the
signal occurs as the result of calling the abort or raise function, the
signal handler shall not call the raise function.
The warning is emitted if function raise
is called from
a signal handler, but only if a signal with that handler is explicitly
raised using functions raise
or abort
somewhere in the program. It is based on the SIG33-C
rule from the CERT Secure Coding Standard.
void log_msg(int signum) {
// Skipped.
}
void handler(int signum) {
raise(SIGUSR1); // Handler for SIGINT recusively calls `raise`.
}
int main(void) {
signal(SIGUSR1, log_msg); // Handler is registered.
signal(SIGINT, handler); // Handler is registered.
// Skipped.
raise(SIGINT); // `raise` is explicitly called for SIGINT.
// Other code.
return 0;
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
Related CERTs: CERT-SIG35-C.
This checker detects cases of undefined behavior caused by returning
control from certain signal handlers that should instead terminate the
program. According to Section 7.14.1.1 of the C standard, returning from
SIGSEGV
, SIGILL
, or SIGFPE
signal
handlers leads to undefined behavior: > If and when the function
returns, if the value of sig is SIGFPE, SIGILL, SIGSEGV, or any other
implementation-defined value corresponding to a computational exception,
the behavior is undefined; otherwise, the program will resume execution
at the point it was interrupted.
The warning is emitted if signal handler for SIGFPE
,
SIGILL
or SIGSEGV
can return without calling
the abort
function.
volatile sig_atomic_t flag;
void handler(int signum) {
flag = 1; // No call of `abort`.
}
int main(void) {
signal(SIGILL, handler); // Handler is registered.
// Skipped.
return 0;
}
Those warnings are specific for libraries.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Unknown | No |
This checker detects cases of opening 32-bit dynamic libraries via
dlopen
from <dlfcn.h>
. The warning is
emitted if the first argument of dlopen
starts with
/lib
or /usr/lib
.
typedef void* dll_handle_t;
int loadlib(const char* path) {
dll_handle_t handle = dlopen(path, RTLD_LAZY);
if (!handle)
return -errno;
return 0;
}
void foo() {
dll_handle_t handle = loadlib("/usr/lib/lib.so");
// ...
}
Invocation of loadlib
is an indirect call of
dlopen
.
Function foo
illustrates the defect: the first argument
of loadlib
starts with /usr/lib
.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Unknown | No |
Related CWEs: CWE404.
This checker finds situations where call of a library function is syntactically correct but semantically the values of the parameters don’t match their meaning, potentially leading to unexpected errors. For example, the C language libraries commonly use the same type (usually integer) for different entities (sockets, file descriptors, offsets, etc) which can be accidentally interchanged. The checker tracks the origins of the values and reports issues even when the incorrectly used values are explicitly converted to the library function parameter type.
void func() {
int fd = open("some_file.txt", SOME_FLAGS);
// Bind function is using for file descriptor
bind(fd, ADDR, ADDR_LEN);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
Related CWEs: CWE61.
This Linux-specific checker finds situations where a path of a file
being opened is not checked for containing a symbolic link beforehand.
This potentially allows a malicious user to trick application to work
with a substituted file instead of the right one. Such check can be
performed using a stat
system call.
void func(char *path, char *data) {
// Opened file path is not checked for containing a symbolic link
int fp = open(path, SOME_FLAGS);
if (fp) {
write(data, data, strlen(data));
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
Related CWEs: CWE253.
This checker finds cases where the return value of a library function
is processed incorrectly. Return value of library functions usually mark
whether the call is successful or not and if it is checked in a wrong
way it may lead to missing of proper error handling. For example, the
standard C library formatted output function printf
returns
the number of characters printed or a negative value in case of an error
and so comparing this value for equality with zero is incorrect.
void func() {
// The check for zero return value below is incorrect.
// This value should be checked either being negative (in case of an output error) or
// compared with the length of "string\n" to make sure that all characters are written.
if (fprintf(stdout, "%s\n", "string") == 0) {
int do_smth;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Unknown | No |
Related CERTs: CERT-FIO24-C.
This checker detects cases of double file opening which may lead to a race condition and unexpected program behaviour. A warning is emitted if a file is opened and isn’t closed before another attempt for the same file opening.
FILE* foo(const char* fp) {
return fopen(fp, "a");
}
void bar() {
char filepath[] = "path/to/file";
open(filepath, O_RDONLY);
// ...
FILE* output = foo(filepath);
// ...
}
In this example the bar
function illustrates the issue:
it first opens file using the open
system call and then the
same file is opened again in the foo
function using the
fopen
function.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Unknown | Yes |
This checker finds situations where POSIX system call
open
is invoked with flag O_CREAT
, but without
passing it the third parameter. In this case the permissions on the
newly created file may be set randomly.
Here if oflag
is O_CREAT
, it’s necessary to
specify the third argument to set the correct file access rights.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Security | Minor | Unknown | No |
Related CWEs: CWE732.
This C-specific checker reports a warning when too broad permissions
are set while creating or modifying file objects using standard library
functions. This may allow an untrusted user to modify application data
in an arbitrary way. Usually, an owner is given maximum permissions,
group has the same rights or less, and others have the same rights as
the group or less but less than the owner. Traditionally, permissions
0755
are used for directories and executable files and
0644
are used for regular files. These permissions prevent
modification of the file for everyone except the file owner but permit
reading for all. For private or secure storage, it is a good idea to
restrict file access for all but the owner with setting
0700
or 0600
permissions. Some software
requires restriction of group access (with 0705
and
0604
permissions). Additionally this checker reports
warnings for a common error when a decimal mode permission constant like
705
is used instead of an octal value like
0705
which results in unexpected permissions.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Normal | Average | Yes |
The Linux-specific checker finds possible fork bombs: situations when
fork
system call creates two processes and both these
processes may execute the same fork
again. This leads to
uncontrolled creation of new processes until the system isn’t able to
create them.
for (int i = 0; i < NPROC; i++) {
int pid = fork(); // FORK_BOMB emitted.
if (pid < 0) {
perror("fork():");
} else if (pid == 0) {
execve(...); // May fail: the cycle will continue in two processes.
} else {
child[i] = pid;
}
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Average | Yes |
The Linux-specific checker finds situations when two processes
created by one fork
system call may both execute another
fork
. Such code is unlikely to create exponential amount of
processes, as in FORK_BOMB, but this situation
is probably unwanted.
int pid = fork();
if (pid == 0) {
execve(...); // May fail.
}
// Second fork will create 4 parallel processes. This is probably unwanted.
int pid2 = fork(); // FORK_BOMB.MINOR emitted.
if (pid2 == 0) {
execve(...);
_exit(127);
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Average | Yes |
The Linux-specific checker finds situations when two processes
created by fork
system call and separated by a condition
like if (pid == 0)
will meet and execute the same code in
both the parent and the child process. This is commonly caused by an
application logic error.
int pid = fork();
if (pid < 0) {
perror("fork():");
} else if (pid == 0) { // Child.
execve(...); // May fail: execution will continue in two processes.
} else { // Parent.
child[i] = pid; // FORK_PROCESSES_MEET emitted.
}
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Very low | Yes |
Same as FORK_PROCESSES_MEET, but in this case the processes meet after at least one of them throws an exception.
std::string a = "Hello";
int pid = fork();
if (pid == 0) { // Child.
// May throw an exception
a.at(10) = 'u'; // FORK_PROCESSES_MEET.EXCEPTION emitted.
_exit(127);
} else { // parent
a.at(7) = 'b'; // May throw second exception.
}
Those warnings are specific to issues in C++ code.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | High | Yes |
Related CWEs: CWE457.
This checker finds constructors (including copy constructor) that fail to initialize some of their class’s fields.
class MyData {
public:
MyData();
int kind;
char ch;
int sum;
};
MyData::MyData() : ch('q') {
this->kind = 7; // Warning: field `sum` is not initialized.
}
Code detected by this checker doesn’t necessarily lead to reading of uninitialized memory, since fields that were not initialized in the constructor might be initialized later or never accessed without additional initialization.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Minor | Average | Yes |
This checker finds classes in which the destructor explicitly deallocates memory using a pointer, but explicit copy constructor is not provided. Using implicitly declared copy constructor in this case willead to double deallocation through the same pointer.
class MyData {
char* str;
int size;
public:
MyData() {
str = (char*) malloc(10);
size = 10;
}
// copy constructor is not provided nor deleted
~MyData() {
free(str); // Deallocate memory pointed to by `str`.
}
};
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Unknown | Yes |
Check for undefined behavior related to the order of initialization
of base classes such as calling a member function when base is not
initialized yet, dynamic_cast
of this
when
base is not initialized yet and calling of typeid()
with
this
as argument when base is not initialized yet.
In the example below f()
is a member function of class
B
. Constructor of class B
has base initializer
A(int a)
and uses return value of method f()
as an argument. The call f()
would be evaluated before
A
initialization is complete which is undefined
behavior.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Average | Yes |
Warnings of this type are emitted for situations where memory is
deallocated in a way that is incompatible with how it was allocated. For
example, usage of C functions malloc
/free
must
not be mixed with C++ operators
new
/delete
.
char* buf = (char*) malloc(strlen(str) + 12);
//...
delete buf; // HEAP_INCOMPATIBLE.FREE is emitted here. The bug can be fixed
// by using function `free` for deallocation instead of `delete`.
Language | Situation | Severity | Reliability | Enabled |
---|---|---|---|---|
C/C++ | Quality | Major | Very high | Yes |
Related CWEs: CWE404, CWE459, CWE762.
A subtype of HEAP_INCOMPATIBLE.FREE. Detects
situations where memory was allocated using array allocation operator
new[]
, but deallocated using operator delete
(instead of the correct delete[]
). Using incorrect
deallocation method can lead to undefined behavior.
char* buf = new char[SIZE_MAX];
// ...
delete buf; // HEAP_INCOMPATIBLE.ARRAY warning is emitted here. The bug can be
// fixed by using `delete[]`.