From a739e225b4bd202c79a5b45a4f92b63a41c1f758 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 22 Apr 2020 16:29:10 -0400 Subject: [PATCH 1/3] [GCChecker] fix tests and add Makefile --- Make.inc | 19 ++++++ src/Makefile | 18 ----- test/clangsa/.gitignore | 1 + test/clangsa/GCPushPop.cpp | 2 +- test/clangsa/Makefile | 19 ++++++ test/clangsa/MissingRoots.c | 130 ++++++++++++++++++++++++++---------- test/clangsa/lit.cfg.py | 27 ++++++++ 7 files changed, 161 insertions(+), 55 deletions(-) create mode 100644 test/clangsa/.gitignore create mode 100644 test/clangsa/Makefile create mode 100644 test/clangsa/lit.cfg.py diff --git a/Make.inc b/Make.inc index 688d462fd71fc..a52785bb92a48 100644 --- a/Make.inc +++ b/Make.inc @@ -1301,6 +1301,25 @@ ifeq ($(USE_SYSTEM_LIBM),1) USE_BINARYBUILDER_OPENLIBM := 0 endif + +# Note: we're passing *FLAGS here computed based on your system compiler to +# clang. If that causes you problems, you might want to build and/or run +# specific clang-sa-* files with clang explicitly selected: +# make CC=~+/../usr/tools/clang CXX=~+/../usr/tools/clang USECLANG=1 analyzegc +# make USECLANG=1 clang-sa-* +CLANGSA_FLAGS := +CLANGSA_CXXFLAGS := +ifeq ($(OS), Darwin) # on new XCode, the files are hidden +CLANGSA_FLAGS += -isysroot $(shell xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk +CLANGSA_CXXFLAGS += -isystem $(shell xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1 +endif +ifeq ($(USEGCC),1) +# try to help clang find the c++ files for CC by guessing the value for --prefix +# by dropping lib/gcc// from the install directory it reports +CLANGSA_CXXFLAGS += --gcc-toolchain="$(abspath $(shell LANG=C $(CC) -print-search-dirs | grep '^install: ' | sed -e "s/^install: //")/../../../..)" +endif + + # Make tricks define dir_target diff --git a/src/Makefile b/src/Makefile index b3c5946867b67..1d255b0c1eba2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -372,24 +372,6 @@ ifneq ($(BUILD_LLVM_CLANG),1) endif endif - -# Note: we're passing *FLAGS here computed based on your system compiler to -# clang. If that causes you problems, you might want to build and/or run -# specific clang-sa-* files with clang explicitly selected: -# make CC=~+/../usr/tools/clang CXX=~+/../usr/tools/clang USECLANG=1 analyzegc -# make USECLANG=1 clang-sa-* -CLANGSA_FLAGS := -CLANGSA_CXXFLAGS := -ifeq ($(OS), Darwin) # on new XCode, the files are hidden -CLANGSA_FLAGS += -isysroot $(shell xcode-select -p)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk -CLANGSA_CXXFLAGS += -isystem $(shell xcode-select -p)/Toolchains/XcodeDefault.xctoolchain/usr/include/c++/v1 -endif -ifeq ($(USEGCC),1) -# try to help clang find the c++ files for CC by guessing the value for --prefix -# by dropping lib/gcc// from the install directory it reports -CLANGSA_CXXFLAGS += --gcc-toolchain="$(abspath $(shell LANG=C $(CC) -print-search-dirs | grep '^install: ' | sed -e "s/^install: //")/../../../..)" -endif - clang-sa-%: $(SRCDIR)/%.c $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) | analyzegc-deps-check @$(call PRINT_ANALYZE, $(build_depsbindir)/clang --analyze -Xanalyzer -analyzer-werror -Xanalyzer -analyzer-output=text -Xclang -load -Xclang $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) $(CLANGSA_FLAGS) $(JCPPFLAGS) $(JCFLAGS) $(DEBUGFLAGS) -Xclang -analyzer-checker=core$(COMMA)julia.GCChecker --analyzer-no-default-checks -fcolor-diagnostics -Werror -x c $<) clang-sa-%: $(SRCDIR)/%.cpp $(build_shlibdir)/libGCCheckerPlugin.$(SHLIB_EXT) | analyzegc-deps-check diff --git a/test/clangsa/.gitignore b/test/clangsa/.gitignore new file mode 100644 index 0000000000000..aa144c71f85f8 --- /dev/null +++ b/test/clangsa/.gitignore @@ -0,0 +1 @@ +/Output/ diff --git a/test/clangsa/GCPushPop.cpp b/test/clangsa/GCPushPop.cpp index d8591b7b10473..a992630291bb5 100644 --- a/test/clangsa/GCPushPop.cpp +++ b/test/clangsa/GCPushPop.cpp @@ -1,6 +1,6 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -// RUN: %clang --analyze -Xanalyzer -analyzer-output=text -Xclang -load -Xclang %gc_plugin -Xclang -verify -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include -Xclang -analyzer-checker=core,julia.GCChecker -x c++ %s +// RUN: clang --analyze -Xanalyzer -analyzer-output=text -Xclang -load -Xclang libGCCheckerPlugin%shlibext -Xclang -verify -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} -Xclang -analyzer-checker=core,julia.GCChecker -x c++ %s #include "julia.h" diff --git a/test/clangsa/Makefile b/test/clangsa/Makefile new file mode 100644 index 0000000000000..62acb513510e3 --- /dev/null +++ b/test/clangsa/Makefile @@ -0,0 +1,19 @@ +SRCDIR := $(abspath $(dir $(lastword $(MAKEFILE_LIST)))) +JULIAHOME := $(abspath $(SRCDIR)/../..) +include $(JULIAHOME)/Make.inc + +check: $(SRCDIR) + +TESTS = $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/*.c)) $(wildcard $(SRCDIR)/*.cpp) + +$(SRCDIR) $(TESTS): + PATH=$(build_bindir):$(build_depsbindir):$$PATH \ + LD_LIBRARY_PATH="${build_libdir}:$$LD_LIBRARY_PATH" \ + CLANGSA_FLAGS="${CLANGSA_FLAGS}" \ + CLANGSACXX_FLAGS="${CLANGSACXX_FLAGS}" \ + CPPFLAGS_FLAGS="${CPPFLAGS_FLAGS}" \ + CFLAGS_FLAGS="${CFLAGS_FLAGS}" \ + CXXFLAGS_FLAGS="${CXXFLAGS_FLAGS}" \ + $(build_depsbindir)/lit/lit.py -v $@ + +.PHONY: $(TESTS) $(SRCDIR) check all diff --git a/test/clangsa/MissingRoots.c b/test/clangsa/MissingRoots.c index dc612ae4cbe9e..65b52d62b75bd 100644 --- a/test/clangsa/MissingRoots.c +++ b/test/clangsa/MissingRoots.c @@ -1,13 +1,18 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -// RUN: %clang --analyze -Xanalyzer -analyzer-output=text -Xclang -load -Xclang %gc_plugin -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include -Xclang -analyzer-checker=core,julia.GCChecker --analyzer-no-default-checks -Xclang -verify -Xclang -verify-ignore-unexpected=note -x c %s +// RUN: clang --analyze -Xanalyzer -analyzer-output=text -Xclang -load -Xclang libGCCheckerPlugin%shlibext -I%julia_home/src -I%julia_home/src/support -I%julia_home/usr/include ${CLANGSA_FLAGS} ${CPPFLAGS} ${CFLAGS} -Xclang -analyzer-checker=core,julia.GCChecker --analyzer-no-default-checks -Xclang -verify -x c %s #include "julia.h" #include "julia_internal.h" +extern void look_at_value(jl_value_t *v); +extern void process_unrooted(jl_value_t *maybe_unrooted JL_MAYBE_UNROOTED); +extern void jl_gc_safepoint(); + void unrooted_argument() { - jl_((jl_value_t*)jl_svec1(NULL)); // expected-warning{{Passing non-rooted value as argument to function}} - // expected-note@-1{{Passing non-rooted value as argument to function}} + look_at_value((jl_value_t*)jl_svec1(NULL)); // expected-warning{{Passing non-rooted value as argument to function}} + // expected-note@-1{{Passing non-rooted value as argument to function}} + // expected-note@-2{{Started tracking value here}} }; void simple_svec() { @@ -16,13 +21,10 @@ void simple_svec() { assert(jl_svecref(val, 0) == NULL); } -extern void jl_gc_safepoint(); jl_value_t *simple_missing_root() { jl_svec_t *val = jl_svec1(NULL); - // This is a GC safepoint, so the above value could have been freed - jl_gc_safepoint(); // expected-note {{Value may have been GCed here}} - return jl_svecref(val, 0); // expected-warning{{Argument value may have been GCed}} - // expected-note@-1{{Argument value may have been GCed}} + jl_gc_safepoint(); + return jl_svecref(val, 0); // XXX-expected-warning{{Passing non-rooted value as argument to function}} }; jl_value_t *root_value() { @@ -34,6 +36,64 @@ jl_value_t *root_value() { return ret; }; +void root_value_data() { + jl_svec_t *val = jl_svec1(NULL); // expected-note{{Started tracking value here}} + jl_value_t **data = jl_svec_data(val); + JL_GC_PUSH1(&val); // expected-note{{GC frame changed here}} + // expected-note@-1{{Value was rooted here}} + jl_gc_safepoint(); + look_at_value(*data); + JL_GC_POP(); // expected-note{{GC frame changed here}} + // expected-note@-1{{Root was released here}} + jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} + *data; // expected-warning{{Creating derivative of value that may have been GCed}} + // expected-note@-1{{Creating derivative of value that may have been GCed}} +}; + +void root_value_data2() { + jl_svec_t *val = jl_svec1(NULL); // expected-note{{Started tracking value here}} + jl_value_t **data = jl_svec_data(val); + JL_GC_PUSH1(&val); // expected-note{{GC frame changed here}} + // expected-note@-1{{Value was rooted here}} + jl_gc_safepoint(); + look_at_value(data[0]); + JL_GC_POP(); // expected-note{{GC frame changed here}} + // expected-note@-1{{Root was released here}} + jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} + data[0]; // expected-warning{{Creating derivative of value that may have been GCed}} + // expected-note@-1{{Creating derivative of value that may have been GCed}} +}; + + +void root_value_data3() { + jl_svec_t *val = jl_svec1(NULL); // expected-note{{Started tracking value here}} + jl_value_t **data = jl_svec_data(val); + JL_GC_PUSH1(&val); // expected-note{{GC frame changed here}} + // expected-note@-1{{Value was rooted here}} + jl_gc_safepoint(); + look_at_value(**&data); + JL_GC_POP(); // expected-note{{GC frame changed here}} + // expected-note@-1{{Root was released here}} + jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} + **&data; // expected-warning{{Creating derivative of value that may have been GCed}} + // expected-note@-1{{Creating derivative of value that may have been GCed}} +}; + +void root_value_data4() { + jl_svec_t *val = jl_svec1(NULL); // expected-note{{Started tracking value here}} + jl_value_t **data = jl_svec_data(val); + JL_GC_PUSH1(&val); // expected-note{{GC frame changed here}} + // expected-note@-1{{Value was rooted here}} + jl_gc_safepoint(); + look_at_value(*&data[0]); + JL_GC_POP(); // expected-note{{GC frame changed here}} + // expected-note@-1{{Root was released here}} + jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} + *&data[0]; // expected-warning{{Creating derivative of value that may have been GCed}} + // expected-note@-1{{Creating derivative of value that may have been GCed}} +}; + + jl_value_t *existing_root() { jl_svec_t *val = NULL; JL_GC_PUSH1(&val); @@ -46,7 +106,7 @@ jl_value_t *existing_root() { jl_value_t *late_root() { jl_svec_t *val = NULL; - val = jl_svec1(NULL); + val = jl_svec1(NULL); // expected-note {{Started tracking value here}} jl_gc_safepoint(); // expected-note {{Value may have been GCed here}} JL_GC_PUSH1(&val); // expected-warning{{Trying to root value which may have been GCed}} // expected-note@-1{{Trying to root value which may have been GCed}} @@ -58,8 +118,8 @@ jl_value_t *late_root() { jl_value_t *late_root2() { jl_svec_t *val = NULL; jl_svec_t *val2 = NULL; - JL_GC_PUSH1(&val); - val2 = jl_svec1(NULL); + JL_GC_PUSH1(&val); // expected-note {{GC frame changed here}} + val2 = jl_svec1(NULL); // expected-note {{Started tracking value here}} jl_gc_safepoint(); // expected-note {{Value may have been GCed here}} val = val2; // expected-warning{{Trying to root value which may have been GCed}} // expected-note@-1{{Trying to root value which may have been GCed}} @@ -71,19 +131,18 @@ jl_value_t *late_root2() { jl_value_t *already_freed() { jl_svec_t *val = NULL; JL_GC_PUSH1(&val); - val = jl_svec1(NULL); // expected-note {{Value was rooted here}} - JL_GC_POP(); // exptected-noted {{Root was released here}} - jl_gc_safepoint(); // expected-note {{Value may have been GCed here}} - jl_value_t *ret = jl_svecref(val, 0); // expected-warning{{Argument value may have been GCed}} - // expected-note@-1{{Argument value may have been GCed}} + val = jl_svec1(NULL); + JL_GC_POP(); + jl_gc_safepoint(); + jl_value_t *ret = jl_svecref(val, 0); return ret; }; int field_access() { - jl_svec_t *val = jl_svec1(NULL); + jl_svec_t *val = jl_svec1(NULL); // expected-note {{Started tracking value here}} jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} - return val->length == 1; // expected-warning{{Creating derivative of value that may have been GCed}} - // expected-note@-1{{Creating derivative of value that may have been GCed}} + return val->length == 1; // expected-warning{{Trying to access value which may have been GCed}} + // expected-note@-1{{Trying to access value which may have been GCed}} } int pushargs_roots() { @@ -98,27 +157,26 @@ int pushargs_roots() { int pushargs_roots_freed() { jl_value_t **margs; - jl_svec_t *val = jl_svec1(NULL);; - JL_GC_PUSHARGS(margs, 1); + jl_svec_t *val = jl_svec1(NULL); // expected-note{{Started tracking value here}} + JL_GC_PUSHARGS(margs, 1); // expected-note{{GC frame changed here}} margs[0] = (jl_value_t*)val; // expected-note{{Value was rooted here}} - JL_GC_POP(); // expected-note{{Root was released here}} + JL_GC_POP(); // expected-note{{GC frame changed here}} + // expected-note@-1{{Root was released here}} jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} - return val->length == 1; // expected-warning{{Creating derivative of value that may have been GCed}} - // expected-note@-1{{Creating derivative of value that may have been GCed}} + return val->length == 1; // expected-warning{{Trying to access value which may have been GCed}} + // expected-note@-1{{Trying to access value which may have been GCed}} } -extern void process_unrooted(jl_value_t *maybe_unrooted JL_MAYBE_UNROOTED); int unrooted() { - jl_svec_t *val = jl_svec1(NULL); + jl_svec_t *val = jl_svec1(NULL); // expected-note{{Started tracking value here}} // This is ok process_unrooted((jl_value_t*)val); // expected-note{{Value may have been GCed here}} // This is not - return val->length == 1; // expected-warning{{Creating derivative of value that may have been GCed}} - // expected-note@-1{{Creating derivative of value that may have been GCed}} + return val->length == 1; // expected-warning{{Trying to access value which may have been GCed}} + // expected-note@-1{{Trying to access value which may have been GCed}} } extern jl_value_t *global_value JL_GLOBALLY_ROOTED; -extern void look_at_value(jl_value_t *v); void globally_rooted() { jl_value_t *val = global_value; jl_gc_safepoint(); @@ -284,20 +342,22 @@ void assoc_exact_broken(jl_value_t **args, size_t n, int8_t offs, size_t world) } */ -void assoc_exact_ok(jl_value_t **args, size_t n, int8_t offs, size_t world) { +void assoc_exact_ok(jl_value_t *args1, jl_value_t **args, size_t n, int8_t offs, size_t world) { jl_typemap_level_t *cache = jl_new_typemap_level(); JL_GC_PUSH1(&cache); - jl_typemap_assoc_exact(cache->any, args, n, offs, world); + jl_typemap_assoc_exact(cache->any, args1, args, n, offs, world); JL_GC_POP(); } // jl_box_* special cases void box_special_cases1(int i) { - jl_(jl_box_long(i)); //expected-warning{{Passing non-rooted value as argument to function that may GC}} + look_at_value(jl_box_long(i)); // expected-warning{{Passing non-rooted value as argument to function}} + // expected-note@-1{{Passing non-rooted value as argument to function}} + // expected-note@-2{{Started tracking value here}} } void box_special_cases2() { - jl_(jl_box_long(0)); + look_at_value(jl_box_long(0)); } jl_value_t *alloc_something(); @@ -330,7 +390,6 @@ typedef struct _varbinding { jl_value_t *ub; } jl_varbinding_t; -extern void look_at_value(jl_value_t *v); extern void escape_vb(jl_varbinding_t **vb); void stack_rooted(jl_value_t *lb JL_MAYBE_UNROOTED, jl_value_t *ub JL_MAYBE_UNROOTED) { jl_varbinding_t vb = { NULL, lb, ub }; @@ -340,11 +399,10 @@ void stack_rooted(jl_value_t *lb JL_MAYBE_UNROOTED, jl_value_t *ub JL_MAYBE_UNRO JL_GC_POP(); } -extern void look_at_value(jl_value_t *v); void JL_NORETURN throw_internal(jl_value_t *e JL_MAYBE_UNROOTED) { jl_ptls_t ptls = jl_get_ptls_states(); - ptls->exception_in_transit = e; + ptls->sig_exception = e; jl_gc_unsafe_enter(ptls); look_at_value(e); } diff --git a/test/clangsa/lit.cfg.py b/test/clangsa/lit.cfg.py new file mode 100644 index 0000000000000..5790eab812e9c --- /dev/null +++ b/test/clangsa/lit.cfg.py @@ -0,0 +1,27 @@ +import os +import sys +import re +import platform + +import lit.util +import lit.formats + +config.name = 'Julia-GCChecker' +config.suffixes = ['.c','.cpp'] +config.test_source_root = os.path.dirname(__file__) +config.test_format = lit.formats.ShTest(True) +config.substitutions.append(('%shlibext', '.dylib' if platform.system() == 'Darwin' else '.dll' if + platform.system() == 'Windows' else '.so')) +config.substitutions.append(("%julia_home", os.path.join(os.path.dirname(__file__), "../.."))) + +path = os.path.pathsep.join((os.path.join(os.path.dirname(__file__),"../../usr/tools"), os.path.join(os.path.dirname(__file__),"../../usr/bin"), config.environment['PATH'])) +config.environment['PATH'] = path +config.environment['HOME'] = "/tmp" +config.environment['CLANGSA_FLAGS'] = os.environ.get('CLANGSA_FLAGS', "") +config.environment['CLANGSA_CXXFLAGS'] = os.environ.get('CLANGSA_CXXFLAGS', "") +config.environment['CPPFLAGS'] = os.environ.get('CPPFLAGS', "") +config.environment['CFLAGS'] = os.environ.get('CFLAGS', "") +config.environment['CXXFLAGS'] = os.environ.get('CXXFLAGS', "") + +if platform.machine() == "x86_64": + config.available_features.add('x86_64') From dd6af447c72dff6bd677b306726bc63bdc63d491 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Wed, 22 Apr 2020 16:59:58 -0400 Subject: [PATCH 2/3] [GCChecker,NFC] run clang-format -style=llvm Try to bring some semblance of style and indent consistency to this file. --- src/clangsa/GCChecker.cpp | 2548 +++++++++++++++++++------------------ 1 file changed, 1322 insertions(+), 1226 deletions(-) diff --git a/src/clangsa/GCChecker.cpp b/src/clangsa/GCChecker.cpp index 88a155b8e9aeb..2a7ed9e84d11b 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -1,17 +1,17 @@ // This file is a part of Julia. License is MIT: https://julialang.org/license -#include "clang/Tooling/Tooling.h" -#include "clang/Tooling/CompilationDatabase.h" #include "clang/Frontend/FrontendActions.h" -#include "clang/StaticAnalyzer/Frontend/FrontendActions.h" +#include "clang/StaticAnalyzer/Checkers/SValExplainer.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" +#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" -#include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" -#include "clang/StaticAnalyzer/Checkers/SValExplainer.h" +#include "clang/StaticAnalyzer/Frontend/FrontendActions.h" +#include "clang/Tooling/CompilationDatabase.h" +#include "clang/Tooling/Tooling.h" #if LLVM_VERSION_MAJOR >= 8 #include "clang/StaticAnalyzer/Frontend/CheckerRegistry.h" #else @@ -22,12 +22,12 @@ #include #if defined(__GNUC__) -# define USED_FUNC __attribute__((used)) +#define USED_FUNC __attribute__((used)) #elif defined(_COMPILER_MICROSOFT_) // Does MSVC have this? -# define USED_FUNC +#define USED_FUNC #else -# define USED_FUNC +#define USED_FUNC #endif #if LLVM_VERSION_MAJOR >= 10 @@ -37,744 +37,788 @@ using llvm::make_unique; #endif namespace { - using namespace clang; - using namespace ento; - - #define PDP std::shared_ptr - #define MakePDP make_unique - - class GCChecker : public Checker, - check::PostStmt, - check::PostStmt, - check::PostStmt, - check::Bind, - check::Location - > { - mutable std::unique_ptr BT; - template - void report_error(callback f, CheckerContext &C, const char *message) const; - void report_error(CheckerContext &C, const char *message) const { - return report_error([](BugReport *){}, C, message); - } - void report_value_error(CheckerContext &C, SymbolRef Sym, const char *message, clang::SourceRange range = clang::SourceRange()) const; - - public: - struct ValueState { - enum State { Allocated, Rooted, PotentiallyFreed, Untracked } S; - const MemRegion *Root; - int RootDepth; - - // Optional Metadata (for error messages) - const FunctionDecl *FD; - const ParmVarDecl *PVD; - - ValueState(State InS, const MemRegion *Root, int Depth) : S(InS), Root(Root), RootDepth(Depth), FD(nullptr), PVD(nullptr) {} - ValueState() : S(Untracked), Root(nullptr), RootDepth(0), FD(nullptr), PVD(nullptr) {} - - void dump() const { - llvm::dbgs() << ((S == Allocated) ? "Allocated" : - (S == Rooted) ? "Rooted" : - (S == PotentiallyFreed) ? "PotentiallyFreed" : - (S == Untracked) ? "Untracked" : "Error"); - if (S == Rooted) - llvm::dbgs() << "(" << RootDepth << ")"; - llvm::dbgs() << "\n"; - } +using namespace clang; +using namespace ento; + +#define PDP std::shared_ptr +#define MakePDP make_unique + +class GCChecker + : public Checker< + eval::Call, + check::BeginFunction, + check::EndFunction, + check::PostCall, + check::PreCall, + check::PostStmt, + check::PostStmt, + check::PostStmt, + check::PostStmt, + check::Bind, + check::Location> { + mutable std::unique_ptr BT; + template + void report_error(callback f, CheckerContext &C, const char *message) const; + void report_error(CheckerContext &C, const char *message) const { + return report_error([](BugReport *) {}, C, message); + } + void + report_value_error(CheckerContext &C, SymbolRef Sym, const char *message, + clang::SourceRange range = clang::SourceRange()) const; + +public: + struct ValueState { + enum State { Allocated, Rooted, PotentiallyFreed, Untracked } S; + const MemRegion *Root; + int RootDepth; + + // Optional Metadata (for error messages) + const FunctionDecl *FD; + const ParmVarDecl *PVD; + + ValueState(State InS, const MemRegion *Root, int Depth) + : S(InS), Root(Root), RootDepth(Depth), FD(nullptr), PVD(nullptr) {} + ValueState() + : S(Untracked), Root(nullptr), RootDepth(0), FD(nullptr), PVD(nullptr) { + } + USED_FUNC void dump() const { + llvm::dbgs() << ((S == Allocated) ? "Allocated" + : (S == Rooted) ? "Rooted" + : (S == PotentiallyFreed) ? "PotentiallyFreed" + : (S == Untracked) ? "Untracked" + : "Error"); + if (S == Rooted) + llvm::dbgs() << "(" << RootDepth << ")"; + llvm::dbgs() << "\n"; + } - bool operator==(const ValueState &VS) const { - return S == VS.S && Root == VS.Root && RootDepth == VS.RootDepth; - } - bool operator!=(const ValueState &VS) const { - return S != VS.S || Root != VS.Root || RootDepth != VS.RootDepth; - } + bool operator==(const ValueState &VS) const { + return S == VS.S && Root == VS.Root && RootDepth == VS.RootDepth; + } + bool operator!=(const ValueState &VS) const { + return S != VS.S || Root != VS.Root || RootDepth != VS.RootDepth; + } - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddInteger(S); - ID.AddPointer(Root); - ID.AddInteger(RootDepth); - } + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(S); + ID.AddPointer(Root); + ID.AddInteger(RootDepth); + } - bool isRooted() const { return S == Rooted; } - bool isPotentiallyFreed() const { return S == PotentiallyFreed; } - bool isJustAllocated() const { return S == Allocated; } - bool isUntracked() const { return S == Untracked; } + bool isRooted() const { return S == Rooted; } + bool isPotentiallyFreed() const { return S == PotentiallyFreed; } + bool isJustAllocated() const { return S == Allocated; } + bool isUntracked() const { return S == Untracked; } - bool isRootedBy(const MemRegion *R) const { - assert(R != nullptr); - return isRooted() && R == Root; - } + bool isRootedBy(const MemRegion *R) const { + assert(R != nullptr); + return isRooted() && R == Root; + } - static ValueState getAllocated() { return ValueState(Allocated, nullptr, -1); } - static ValueState getFreed() { return ValueState(PotentiallyFreed, nullptr, -1); } - static ValueState getUntracked() { return ValueState(Untracked, nullptr, -1); } - static ValueState getRooted(const MemRegion *Root, int Depth) { return ValueState(Rooted, Root, Depth); } - static ValueState getForArgument(const FunctionDecl *FD, - const ParmVarDecl *PVD) { - bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD); - bool maybeUnrooted = declHasAnnotation(PVD, "julia_maybe_unrooted"); - if (!isFunctionSafepoint || maybeUnrooted) { - ValueState VS = getAllocated(); - VS.PVD = PVD; VS.FD = FD; - return VS; - } - return getRooted(nullptr, -1); - } - }; + static ValueState getAllocated() { + return ValueState(Allocated, nullptr, -1); + } + static ValueState getFreed() { + return ValueState(PotentiallyFreed, nullptr, -1); + } + static ValueState getUntracked() { + return ValueState(Untracked, nullptr, -1); + } + static ValueState getRooted(const MemRegion *Root, int Depth) { + return ValueState(Rooted, Root, Depth); + } + static ValueState getForArgument(const FunctionDecl *FD, + const ParmVarDecl *PVD) { + bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(FD); + bool maybeUnrooted = declHasAnnotation(PVD, "julia_maybe_unrooted"); + if (!isFunctionSafepoint || maybeUnrooted) { + ValueState VS = getAllocated(); + VS.PVD = PVD; + VS.FD = FD; + return VS; + } + return getRooted(nullptr, -1); + } + }; - struct RootState { - enum Kind { Root, RootArray } K; - int RootedAtDepth; + struct RootState { + enum Kind { Root, RootArray } K; + int RootedAtDepth; - RootState(Kind InK, int Depth) : K(InK), RootedAtDepth(Depth) {} + RootState(Kind InK, int Depth) : K(InK), RootedAtDepth(Depth) {} - bool operator==(const RootState &VS) const { - return K == VS.K && RootedAtDepth == VS.RootedAtDepth; - } - bool operator!=(const RootState &VS) const { - return K != VS.K || RootedAtDepth != VS.RootedAtDepth; - } + bool operator==(const RootState &VS) const { + return K == VS.K && RootedAtDepth == VS.RootedAtDepth; + } + bool operator!=(const RootState &VS) const { + return K != VS.K || RootedAtDepth != VS.RootedAtDepth; + } - bool shouldPopAtDepth(int Depth) const { return Depth == RootedAtDepth; } - bool isRootArray() const { return K == RootArray; } + bool shouldPopAtDepth(int Depth) const { return Depth == RootedAtDepth; } + bool isRootArray() const { return K == RootArray; } - void Profile(llvm::FoldingSetNodeID &ID) const { - ID.AddInteger(K); - ID.AddInteger(RootedAtDepth); - } + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.AddInteger(K); + ID.AddInteger(RootedAtDepth); + } - static RootState getRoot(int Depth) { return RootState(Root, Depth); } - static RootState getRootArray(int Depth) { return RootState(RootArray, Depth); } - }; - - private: - template - static bool isJuliaType(callback f, QualType QT) { - if (QT->isPointerType() || QT->isArrayType()) - return isJuliaType(f, clang::QualType(QT->getPointeeOrArrayElementType(),0)); - const TypedefType *TT = QT->getAs(); - if (TT) { - if (f(TT->getDecl()->getName())) - return true; - } - const TagDecl *TD = QT->getUnqualifiedDesugaredType()->getAsTagDecl(); - if (!TD) { - return false; - } - return f(TD->getName()); - } - static bool isValueCollection(QualType QT) { - if (QT->isPointerType() || QT->isArrayType()) - return isValueCollection(clang::QualType(QT->getPointeeOrArrayElementType(), 0)); - const TagDecl *TD = QT->getUnqualifiedDesugaredType()->getAsTagDecl(); - if (!TD) - return false; - return declHasAnnotation(TD, "julia_rooted_value_collection"); - } - template - static SymbolRef walkToRoot(callback f, const ProgramStateRef &State, const MemRegion *Region); - - static bool isGCTrackedType(QualType Type); - bool isGloballyRootedType(QualType Type) const; - static void dumpState(const ProgramStateRef &State); - static bool declHasAnnotation(const clang::Decl *D, const char *which); - static bool isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD); - bool isSafepoint(const CallEvent &Call) const; - bool processPotentialSafepoint(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const; - bool processAllocationOfResult(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const; - bool processArgumentRooting(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const; - bool rootRegionIfGlobal(const MemRegion *R, ProgramStateRef &, CheckerContext &C, ValueState *ValS = nullptr) const; - static const ValueState *getValStateForRegion(ASTContext &AstC, const ProgramStateRef &State, const MemRegion *R, bool Debug = false); - bool gcEnabledHere(CheckerContext &C) const; - bool safepointEnabledHere(CheckerContext &C) const; - bool propagateArgumentRootedness(CheckerContext &C, ProgramStateRef &State) const; - SymbolRef getSymbolForResult(const Expr *Result, const ValueState *OldValS, ProgramStateRef &State, CheckerContext &C) const; - - public: - void checkBeginFunction(CheckerContext &Ctx) const; + static RootState getRoot(int Depth) { return RootState(Root, Depth); } + static RootState getRootArray(int Depth) { + return RootState(RootArray, Depth); + } + }; + +private: + template + static bool isJuliaType(callback f, QualType QT) { + if (QT->isPointerType() || QT->isArrayType()) + return isJuliaType( + f, clang::QualType(QT->getPointeeOrArrayElementType(), 0)); + const TypedefType *TT = QT->getAs(); + if (TT) { + if (f(TT->getDecl()->getName())) + return true; + } + const TagDecl *TD = QT->getUnqualifiedDesugaredType()->getAsTagDecl(); + if (!TD) { + return false; + } + return f(TD->getName()); + } + static bool isValueCollection(QualType QT) { + if (QT->isPointerType() || QT->isArrayType()) + return isValueCollection( + clang::QualType(QT->getPointeeOrArrayElementType(), 0)); + const TagDecl *TD = QT->getUnqualifiedDesugaredType()->getAsTagDecl(); + if (!TD) + return false; + return declHasAnnotation(TD, "julia_rooted_value_collection"); + } + template + static SymbolRef walkToRoot(callback f, const ProgramStateRef &State, + const MemRegion *Region); + + static bool isGCTrackedType(QualType Type); + bool isGloballyRootedType(QualType Type) const; + static void dumpState(const ProgramStateRef &State); + static bool declHasAnnotation(const clang::Decl *D, const char *which); + static bool isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD); + bool isSafepoint(const CallEvent &Call) const; + bool processPotentialSafepoint(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const; + bool processAllocationOfResult(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const; + bool processArgumentRooting(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const; + bool rootRegionIfGlobal(const MemRegion *R, ProgramStateRef &, + CheckerContext &C, ValueState *ValS = nullptr) const; + static const ValueState *getValStateForRegion(ASTContext &AstC, + const ProgramStateRef &State, + const MemRegion *R, + bool Debug = false); + bool gcEnabledHere(CheckerContext &C) const; + bool safepointEnabledHere(CheckerContext &C) const; + bool propagateArgumentRootedness(CheckerContext &C, + ProgramStateRef &State) const; + SymbolRef getSymbolForResult(const Expr *Result, const ValueState *OldValS, + ProgramStateRef &State, CheckerContext &C) const; + +public: + void checkBeginFunction(CheckerContext &Ctx) const; #if LLVM_VERSION_MAJOR >= 7 - void checkEndFunction(const clang::ReturnStmt* RS, CheckerContext &Ctx) const; + void checkEndFunction(const clang::ReturnStmt *RS, CheckerContext &Ctx) const; #else - void checkEndFunction(CheckerContext &Ctx) const; + void checkEndFunction(CheckerContext &Ctx) const; #endif #if LLVM_VERSION_MAJOR >= 9 - bool evalCall(const CallEvent &Call, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; #else - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallExpr *CE, CheckerContext &C) const; #endif - void checkPreCall(const CallEvent &Call, CheckerContext &C) const; - void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - void checkPostStmt(const CStyleCastExpr *CE, CheckerContext &C) const; - void checkPostStmt(const ArraySubscriptExpr *CE, CheckerContext &C) const; - void checkPostStmt(const MemberExpr *ME, CheckerContext &C) const; - void checkPostStmt(const UnaryOperator *UO, CheckerContext &C) const; - void checkDerivingExpr(const Expr *Result, const Expr *Parent, bool ParentIsLoc, CheckerContext &C) const; - void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &) const; - void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, CheckerContext &) const; - class GCBugVisitor + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + void checkPostStmt(const CStyleCastExpr *CE, CheckerContext &C) const; + void checkPostStmt(const ArraySubscriptExpr *CE, CheckerContext &C) const; + void checkPostStmt(const MemberExpr *ME, CheckerContext &C) const; + void checkPostStmt(const UnaryOperator *UO, CheckerContext &C) const; + void checkDerivingExpr(const Expr *Result, const Expr *Parent, + bool ParentIsLoc, CheckerContext &C) const; + void checkBind(SVal Loc, SVal Val, const Stmt *S, CheckerContext &) const; + void checkLocation(SVal Loc, bool IsLoad, const Stmt *S, + CheckerContext &) const; + class GCBugVisitor #if LLVM_VERSION_MAJOR >= 7 - : public BugReporterVisitor { + : public BugReporterVisitor { #else - : public BugReporterVisitorImpl { + : public BugReporterVisitorImpl { #endif - public: - GCBugVisitor() {} + public: + GCBugVisitor() {} - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - } + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + } - PDP VisitNode(const ExplodedNode *N, + PDP VisitNode(const ExplodedNode *N, #if LLVM_VERSION_MAJOR < 8 - const ExplodedNode *PrevN, + const ExplodedNode *PrevN, #endif - BugReporterContext &BRC, - BugReport &BR) override; - }; + BugReporterContext &BRC, BugReport &BR) override; + }; - class GCValueBugVisitor + class GCValueBugVisitor #if LLVM_VERSION_MAJOR >= 7 - : public BugReporterVisitor { + : public BugReporterVisitor { #else - : public BugReporterVisitorImpl { + : public BugReporterVisitorImpl { #endif - protected: - SymbolRef Sym; + protected: + SymbolRef Sym; - public: - GCValueBugVisitor(SymbolRef S) : Sym(S) {} + public: + GCValueBugVisitor(SymbolRef S) : Sym(S) {} - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - ID.AddPointer(Sym); - } + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int X = 0; + ID.AddPointer(&X); + ID.AddPointer(Sym); + } - PDP ExplainNoPropagation( - const ExplodedNode *N, PathDiagnosticLocation Pos, BugReporterContext &BRC, BugReport &BR); - PDP ExplainNoPropagationFromExpr(const clang::Expr *FromWhere, - const ExplodedNode *N, PathDiagnosticLocation Pos, BugReporterContext &BRC, BugReport &BR); + PDP ExplainNoPropagation(const ExplodedNode *N, PathDiagnosticLocation Pos, + BugReporterContext &BRC, BugReport &BR); + PDP ExplainNoPropagationFromExpr(const clang::Expr *FromWhere, + const ExplodedNode *N, + PathDiagnosticLocation Pos, + BugReporterContext &BRC, BugReport &BR); - PDP VisitNode(const ExplodedNode *N, + PDP VisitNode(const ExplodedNode *N, #if LLVM_VERSION_MAJOR < 8 - const ExplodedNode *PrevN, + const ExplodedNode *PrevN, #endif - BugReporterContext &BRC, - BugReport &BR) override; - }; - }; + BugReporterContext &BRC, BugReport &BR) override; + }; // namespace +}; -} +} // namespace REGISTER_TRAIT_WITH_PROGRAMSTATE(GCDepth, unsigned) REGISTER_TRAIT_WITH_PROGRAMSTATE(GCDisabledAt, unsigned) REGISTER_TRAIT_WITH_PROGRAMSTATE(SafepointDisabledAt, unsigned) REGISTER_TRAIT_WITH_PROGRAMSTATE(MayCallSafepoint, bool) REGISTER_MAP_WITH_PROGRAMSTATE(GCValueMap, SymbolRef, GCChecker::ValueState) -REGISTER_MAP_WITH_PROGRAMSTATE(GCRootMap, const MemRegion *, GCChecker::RootState) +REGISTER_MAP_WITH_PROGRAMSTATE(GCRootMap, const MemRegion *, + GCChecker::RootState) template -SymbolRef GCChecker::walkToRoot(callback f, const ProgramStateRef &State, const MemRegion *Region) -{ - if (!Region) - return nullptr; - while (true) { - const SymbolicRegion *SR = Region->getSymbolicBase(); - if (!SR) { - return nullptr; - } - SymbolRef Sym = SR->getSymbol(); - const ValueState *OldVState = State->get(Sym); - if (f(Sym, OldVState)) { - if (const SymbolRegionValue *SRV = dyn_cast(Sym)) { - Region = SRV->getRegion(); - continue; - } else if (const SymbolDerived *SD = dyn_cast(Sym)) { - Region = SD->getRegion(); - continue; - } - return nullptr; - } - return Sym; +SymbolRef GCChecker::walkToRoot(callback f, const ProgramStateRef &State, + const MemRegion *Region) { + if (!Region) + return nullptr; + while (true) { + const SymbolicRegion *SR = Region->getSymbolicBase(); + if (!SR) { + return nullptr; + } + SymbolRef Sym = SR->getSymbol(); + const ValueState *OldVState = State->get(Sym); + if (f(Sym, OldVState)) { + if (const SymbolRegionValue *SRV = dyn_cast(Sym)) { + Region = SRV->getRegion(); + continue; + } else if (const SymbolDerived *SD = dyn_cast(Sym)) { + Region = SD->getRegion(); + continue; + } + return nullptr; } + return Sym; + } } - namespace Helpers { - static const VarRegion *walk_back_to_global_VR(const MemRegion *Region) { - if (!Region) - return nullptr; - while (true) { - const VarRegion *VR = Region->getAs(); - if (VR && VR->getDecl()->hasGlobalStorage()) { - return VR; - } - const SymbolicRegion *SymR = Region->getAs(); - if (SymR) { - const SymbolRegionValue *SymRV = dyn_cast(SymR->getSymbol()); - if (!SymRV) { - const SymbolDerived *SD = dyn_cast(SymR->getSymbol()); - if (SD) { - Region = SD->getRegion(); - continue; - } - break; - } - Region = SymRV->getRegion(); - continue; +static const VarRegion *walk_back_to_global_VR(const MemRegion *Region) { + if (!Region) + return nullptr; + while (true) { + const VarRegion *VR = Region->getAs(); + if (VR && VR->getDecl()->hasGlobalStorage()) { + return VR; + } + const SymbolicRegion *SymR = Region->getAs(); + if (SymR) { + const SymbolRegionValue *SymRV = + dyn_cast(SymR->getSymbol()); + if (!SymRV) { + const SymbolDerived *SD = dyn_cast(SymR->getSymbol()); + if (SD) { + Region = SD->getRegion(); + continue; } - const SubRegion *SR = Region->getAs(); - if (!SR) - break; - Region = SR->getSuperRegion(); + break; + } + Region = SymRV->getRegion(); + continue; } - return nullptr; + const SubRegion *SR = Region->getAs(); + if (!SR) + break; + Region = SR->getSuperRegion(); } + return nullptr; } +} // namespace Helpers -PDP GCChecker::GCBugVisitor::VisitNode( - const ExplodedNode *N, +PDP GCChecker::GCBugVisitor::VisitNode(const ExplodedNode *N, #if LLVM_VERSION_MAJOR < 8 - const ExplodedNode *, + const ExplodedNode *, #endif - BugReporterContext &BRC, BugReport &BR) { - const ExplodedNode *PrevN = N->getFirstPred(); - unsigned NewGCDepth = N->getState()->get(); - unsigned OldGCDepth = PrevN->getState()->get(); - if (NewGCDepth != OldGCDepth) { - PathDiagnosticLocation Pos(PathDiagnosticLocation::getStmt(N), - BRC.getSourceManager(), - N->getLocationContext()); - return MakePDP(Pos, "GC frame changed here."); - } - unsigned NewGCState = N->getState()->get(); - unsigned OldGCState = PrevN->getState()->get(); - if (false /*NewGCState != OldGCState*/) { - PathDiagnosticLocation Pos(PathDiagnosticLocation::getStmt(N), - BRC.getSourceManager(), - N->getLocationContext()); - return MakePDP(Pos, "GC enabledness changed here."); - } - return nullptr; + BugReporterContext &BRC, BugReport &BR) { + const ExplodedNode *PrevN = N->getFirstPred(); + unsigned NewGCDepth = N->getState()->get(); + unsigned OldGCDepth = PrevN->getState()->get(); + if (NewGCDepth != OldGCDepth) { + PathDiagnosticLocation Pos(PathDiagnosticLocation::getStmt(N), + BRC.getSourceManager(), N->getLocationContext()); + return MakePDP(Pos, "GC frame changed here."); + } + unsigned NewGCState = N->getState()->get(); + unsigned OldGCState = PrevN->getState()->get(); + if (false /*NewGCState != OldGCState*/) { + PathDiagnosticLocation Pos(PathDiagnosticLocation::getStmt(N), + BRC.getSourceManager(), N->getLocationContext()); + return MakePDP(Pos, "GC enabledness changed here."); + } + return nullptr; } -PDP GCChecker::GCValueBugVisitor::ExplainNoPropagationFromExpr(const clang::Expr *FromWhere, - const ExplodedNode *N, PathDiagnosticLocation Pos, BugReporterContext &BRC, BugReport &BR) -{ - const MemRegion *Region = N->getState()->getSVal(FromWhere, N->getLocationContext()).getAsRegion(); - SValExplainer Ex(BRC.getASTContext()); - SymbolRef Parent = walkToRoot([&](SymbolRef Sym, const ValueState *OldVState) { - return !OldVState; - }, - N->getState(), Region); - if (!Parent && Region) { - Parent = walkToRoot([&](SymbolRef Sym, const ValueState *OldVState) { - return !OldVState; - }, - N->getState(), N->getState()->getSVal(Region).getAsRegion()); - } - if (!Parent) { - // May have been derived from a global. Check that - const VarRegion *VR = Helpers::walk_back_to_global_VR(Region); - if (VR) { - BR.addNote("Derivation root was here", PathDiagnosticLocation::create(VR->getDecl(), BRC.getSourceManager())); - const VarDecl *VD = VR->getDecl(); - if (VD) { - if (!declHasAnnotation(VD,"julia_globally_rooted")) { - return MakePDP(Pos, "Argument value was derived from unrooted global. May need GLOBALLY_ROOTED annotation."); - } else if (!isGCTrackedType(VD->getType())) { - return MakePDP(Pos, "Argument value was derived global with untracked type. You may want to update the checker's type list"); - } - } - return MakePDP(Pos, "Argument value was derived from global, but the checker did not propagate the root. This may be a bug"); +PDP GCChecker::GCValueBugVisitor::ExplainNoPropagationFromExpr( + const clang::Expr *FromWhere, const ExplodedNode *N, + PathDiagnosticLocation Pos, BugReporterContext &BRC, BugReport &BR) { + const MemRegion *Region = + N->getState()->getSVal(FromWhere, N->getLocationContext()).getAsRegion(); + SValExplainer Ex(BRC.getASTContext()); + SymbolRef Parent = walkToRoot( + [&](SymbolRef Sym, const ValueState *OldVState) { return !OldVState; }, + N->getState(), Region); + if (!Parent && Region) { + Parent = walkToRoot( + [&](SymbolRef Sym, const ValueState *OldVState) { return !OldVState; }, + N->getState(), N->getState()->getSVal(Region).getAsRegion()); + } + if (!Parent) { + // May have been derived from a global. Check that + const VarRegion *VR = Helpers::walk_back_to_global_VR(Region); + if (VR) { + BR.addNote("Derivation root was here", + PathDiagnosticLocation::create(VR->getDecl(), + BRC.getSourceManager())); + const VarDecl *VD = VR->getDecl(); + if (VD) { + if (!declHasAnnotation(VD, "julia_globally_rooted")) { + return MakePDP(Pos, "Argument value was derived from unrooted " + "global. May need GLOBALLY_ROOTED annotation."); + } else if (!isGCTrackedType(VD->getType())) { + return MakePDP( + Pos, "Argument value was derived global with untracked type. You " + "may want to update the checker's type list"); } - return MakePDP(Pos, "Could not propagate root. Argument value was untracked."); - } - const ValueState *ValS = N->getState()->get(Parent); - assert(ValS); - if (ValS->isPotentiallyFreed()) { - BR.addVisitor(make_unique(Parent)); - return MakePDP(Pos, "Root not propagated because it may have been freed. Tracking."); - } else if (ValS->isRooted()) { - BR.addVisitor(make_unique(Parent)); - return MakePDP(Pos, "Root was not propagated due to a bug. Tracking base value."); - } else { - BR.addVisitor(make_unique(Parent)); - return MakePDP(Pos, "No Root to propagate. Tracking."); + } + return MakePDP(Pos, + "Argument value was derived from global, but the checker " + "did not propagate the root. This may be a bug"); } + return MakePDP(Pos, + "Could not propagate root. Argument value was untracked."); + } + const ValueState *ValS = N->getState()->get(Parent); + assert(ValS); + if (ValS->isPotentiallyFreed()) { + BR.addVisitor(make_unique(Parent)); + return MakePDP( + Pos, "Root not propagated because it may have been freed. Tracking."); + } else if (ValS->isRooted()) { + BR.addVisitor(make_unique(Parent)); + return MakePDP( + Pos, "Root was not propagated due to a bug. Tracking base value."); + } else { + BR.addVisitor(make_unique(Parent)); + return MakePDP(Pos, "No Root to propagate. Tracking."); + } } PDP GCChecker::GCValueBugVisitor::ExplainNoPropagation( - const ExplodedNode *N, PathDiagnosticLocation Pos, BugReporterContext &BRC, BugReport &BR) -{ - if (N->getLocation().getAs()) { - const clang::Stmt *TheS = N->getLocation().castAs().getStmt(); - const clang::CallExpr *CE = dyn_cast(TheS); - const clang::MemberExpr *ME = dyn_cast(TheS); - if (ME) - return ExplainNoPropagationFromExpr(ME->getBase(), N, Pos, BRC, BR); - const clang::ArraySubscriptExpr *ASE = dyn_cast(TheS); - if (ASE) - return ExplainNoPropagationFromExpr(ASE->getLHS(), N, Pos, BRC, BR); - if (!CE) - return nullptr; - const clang::FunctionDecl *FD = CE->getDirectCallee(); - if (!FD) - return nullptr; - for (unsigned i = 0; i < FD->getNumParams(); ++i) { - if (!declHasAnnotation(FD->getParamDecl(i), "julia_propagates_root")) - continue; - return ExplainNoPropagationFromExpr(CE->getArg(i), N, Pos, BRC, BR); - } - return nullptr; + const ExplodedNode *N, PathDiagnosticLocation Pos, BugReporterContext &BRC, + BugReport &BR) { + if (N->getLocation().getAs()) { + const clang::Stmt *TheS = N->getLocation().castAs().getStmt(); + const clang::CallExpr *CE = dyn_cast(TheS); + const clang::MemberExpr *ME = dyn_cast(TheS); + if (ME) + return ExplainNoPropagationFromExpr(ME->getBase(), N, Pos, BRC, BR); + const clang::ArraySubscriptExpr *ASE = dyn_cast(TheS); + if (ASE) + return ExplainNoPropagationFromExpr(ASE->getLHS(), N, Pos, BRC, BR); + if (!CE) + return nullptr; + const clang::FunctionDecl *FD = CE->getDirectCallee(); + if (!FD) + return nullptr; + for (unsigned i = 0; i < FD->getNumParams(); ++i) { + if (!declHasAnnotation(FD->getParamDecl(i), "julia_propagates_root")) + continue; + return ExplainNoPropagationFromExpr(CE->getArg(i), N, Pos, BRC, BR); } return nullptr; + } + return nullptr; } -PDP GCChecker::GCValueBugVisitor::VisitNode( - const ExplodedNode *N, +PDP GCChecker::GCValueBugVisitor::VisitNode(const ExplodedNode *N, #if LLVM_VERSION_MAJOR < 8 - const ExplodedNode *, + const ExplodedNode *, #endif - BugReporterContext &BRC, BugReport &BR) { - const ExplodedNode *PrevN = N->getFirstPred(); - const ValueState *NewValueState = N->getState()->get(Sym); - const ValueState *OldValueState = PrevN->getState()->get(Sym); - const Stmt *Stmt = PathDiagnosticLocation::getStmt(N); - - PathDiagnosticLocation Pos; - if (Stmt) - Pos = PathDiagnosticLocation{Stmt, - BRC.getSourceManager(), - N->getLocationContext()}; - else - Pos = PathDiagnosticLocation::createDeclEnd(N->getLocationContext(), - BRC.getSourceManager()); - if (!NewValueState) - return nullptr; - if (!OldValueState) { - if (NewValueState->isRooted()) { - return MakePDP(Pos, "Started tracking value here (root was inherited)."); - } else { - if (NewValueState->FD) { - bool isFunctionSafepoint = !isFDAnnotatedNotSafepoint(NewValueState->FD); - bool maybeUnrooted = declHasAnnotation(NewValueState->PVD, "julia_maybe_unrooted"); - assert(isFunctionSafepoint || maybeUnrooted); (void)maybeUnrooted; - Pos = PathDiagnosticLocation{NewValueState->PVD, - BRC.getSourceManager()}; - if (!isFunctionSafepoint) - return MakePDP(Pos, "Argument not rooted, because function was annotated as not a safepoint"); - else - return MakePDP(Pos, "Argument was annotated as MAYBE_UNROOTED."); - } else { - PDP Diag = ExplainNoPropagation(N, Pos, BRC, BR); - if (Diag) - return Diag; - return MakePDP(Pos, "Started tracking value here."); - } - } - } - if (!OldValueState->isUntracked() && - NewValueState->isUntracked()) { + BugReporterContext &BRC, + BugReport &BR) { + const ExplodedNode *PrevN = N->getFirstPred(); + const ValueState *NewValueState = N->getState()->get(Sym); + const ValueState *OldValueState = PrevN->getState()->get(Sym); + const Stmt *Stmt = PathDiagnosticLocation::getStmt(N); + + PathDiagnosticLocation Pos; + if (Stmt) + Pos = PathDiagnosticLocation{Stmt, BRC.getSourceManager(), + N->getLocationContext()}; + else + Pos = PathDiagnosticLocation::createDeclEnd(N->getLocationContext(), + BRC.getSourceManager()); + if (!NewValueState) + return nullptr; + if (!OldValueState) { + if (NewValueState->isRooted()) { + return MakePDP(Pos, "Started tracking value here (root was inherited)."); + } else { + if (NewValueState->FD) { + bool isFunctionSafepoint = + !isFDAnnotatedNotSafepoint(NewValueState->FD); + bool maybeUnrooted = + declHasAnnotation(NewValueState->PVD, "julia_maybe_unrooted"); + assert(isFunctionSafepoint || maybeUnrooted); + (void)maybeUnrooted; + Pos = + PathDiagnosticLocation{NewValueState->PVD, BRC.getSourceManager()}; + if (!isFunctionSafepoint) + return MakePDP(Pos, "Argument not rooted, because function was " + "annotated as not a safepoint"); + else + return MakePDP(Pos, "Argument was annotated as MAYBE_UNROOTED."); + } else { PDP Diag = ExplainNoPropagation(N, Pos, BRC, BR); if (Diag) - return Diag; - return MakePDP(Pos, "Created untracked derivative."); - } else if (NewValueState->isPotentiallyFreed() && - OldValueState->isJustAllocated()) { - // std::make_shared< in later LLVM - return MakePDP(Pos, "Value may have been GCed here."); - } else if (NewValueState->isPotentiallyFreed() && - !OldValueState->isPotentiallyFreed()) { - // std::make_shared< in later LLVM - return MakePDP(Pos, "Value may have been GCed here (though I don't know why)."); - } else if (NewValueState->isRooted() && - OldValueState->isJustAllocated()) { - return MakePDP(Pos, "Value was rooted here."); - } else if (!NewValueState->isRooted() && - OldValueState->isRooted()) { - return MakePDP(Pos, "Root was released here."); - } else if (NewValueState->RootDepth != - OldValueState->RootDepth) { - return MakePDP(Pos, "Rooting Depth changed here."); + return Diag; + return MakePDP(Pos, "Started tracking value here."); + } } - return nullptr; + } + if (!OldValueState->isUntracked() && NewValueState->isUntracked()) { + PDP Diag = ExplainNoPropagation(N, Pos, BRC, BR); + if (Diag) + return Diag; + return MakePDP(Pos, "Created untracked derivative."); + } else if (NewValueState->isPotentiallyFreed() && + OldValueState->isJustAllocated()) { + // std::make_shared< in later LLVM + return MakePDP(Pos, "Value may have been GCed here."); + } else if (NewValueState->isPotentiallyFreed() && + !OldValueState->isPotentiallyFreed()) { + // std::make_shared< in later LLVM + return MakePDP(Pos, + "Value may have been GCed here (though I don't know why)."); + } else if (NewValueState->isRooted() && OldValueState->isJustAllocated()) { + return MakePDP(Pos, "Value was rooted here."); + } else if (!NewValueState->isRooted() && OldValueState->isRooted()) { + return MakePDP(Pos, "Root was released here."); + } else if (NewValueState->RootDepth != OldValueState->RootDepth) { + return MakePDP(Pos, "Rooting Depth changed here."); + } + return nullptr; } template -void GCChecker::report_error(callback f, CheckerContext &C, const char *message) const -{ - // Generate an error node. - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - - if (!BT) - BT.reset(new BugType(this, "Invalid GC thingy", - categories::LogicError)); - auto Report = make_unique(*BT, message, N); - Report->addVisitor(make_unique()); - f(Report.get()); - C.emitReport(std::move(Report)); +void GCChecker::report_error(callback f, CheckerContext &C, + const char *message) const { + // Generate an error node. + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return; + + if (!BT) + BT.reset(new BugType(this, "Invalid GC thingy", categories::LogicError)); + auto Report = make_unique(*BT, message, N); + Report->addVisitor(make_unique()); + f(Report.get()); + C.emitReport(std::move(Report)); } -void GCChecker::report_value_error(CheckerContext &C, SymbolRef Sym, const char *message, SourceRange range) const -{ - // Generate an error node. - ExplodedNode *N = C.generateErrorNode(); - if (!N) - return; - - if (!BT) - BT.reset(new BugType(this, "Invalid GC thingy", - categories::LogicError)); - auto Report = make_unique(*BT, message, N); - Report->addVisitor(make_unique(Sym)); - Report->addVisitor(make_unique()); - Report->addVisitor(make_unique()); - if (!range.isInvalid()) { - Report->addRange(range); - } - C.emitReport(std::move(Report)); +void GCChecker::report_value_error(CheckerContext &C, SymbolRef Sym, + const char *message, + SourceRange range) const { + // Generate an error node. + ExplodedNode *N = C.generateErrorNode(); + if (!N) + return; + + if (!BT) + BT.reset(new BugType(this, "Invalid GC thingy", categories::LogicError)); + auto Report = make_unique(*BT, message, N); + Report->addVisitor(make_unique(Sym)); + Report->addVisitor(make_unique()); + Report->addVisitor(make_unique()); + if (!range.isInvalid()) { + Report->addRange(range); + } + C.emitReport(std::move(Report)); } bool GCChecker::gcEnabledHere(CheckerContext &C) const { - unsigned disabledAt = C.getState()->get(); - return disabledAt == (unsigned)-1; + unsigned disabledAt = C.getState()->get(); + return disabledAt == (unsigned)-1; } bool GCChecker::safepointEnabledHere(CheckerContext &C) const { - unsigned disabledAt = C.getState()->get(); - return disabledAt == (unsigned)-1; + unsigned disabledAt = C.getState()->get(); + return disabledAt == (unsigned)-1; } -bool GCChecker::propagateArgumentRootedness(CheckerContext &C, ProgramStateRef &State) const { - const auto *LCtx = C.getLocationContext(); +bool GCChecker::propagateArgumentRootedness(CheckerContext &C, + ProgramStateRef &State) const { + const auto *LCtx = C.getLocationContext(); - const auto *Site = cast(LCtx)->getCallSite(); - if (!Site) - return false; + const auto *Site = cast(LCtx)->getCallSite(); + if (!Site) + return false; - const auto *FD = dyn_cast(LCtx->getDecl()); - if (!FD) - return false; + const auto *FD = dyn_cast(LCtx->getDecl()); + if (!FD) + return false; - const auto *CE = dyn_cast(Site); - if (!CE) - return false; + const auto *CE = dyn_cast(Site); + if (!CE) + return false; - //FD->dump(); + // FD->dump(); - bool Change = false; - int idx = 0; - SValExplainer Ex(C.getASTContext()); - for (const auto P : FD->parameters()) { - if (!isGCTrackedType(P->getType())) { - continue; - } - auto Arg = State->getSVal(CE->getArg(idx++), LCtx->getParent()); - SymbolRef ArgSym = walkToRoot([](SymbolRef Sym, const ValueState *OldVState) {return !OldVState;}, - State, Arg.getAsRegion()); - if (!ArgSym) { - continue; - } - const ValueState *ValS = State->get(ArgSym); - if (!ValS) { - report_error([&](BugReport *Report) { - Report->addNote( - "Tried to find root for this parameter in inlined call", - PathDiagnosticLocation::create(P, C.getSourceManager())); - }, C, "Missed allocation of parameter"); - continue; - } - auto Param = State->getLValue(P, LCtx); - SymbolRef ParamSym = State->getSVal(Param).getAsSymbol(); - if (!ParamSym) { - continue; - } - if (isGloballyRootedType(P->getType())) { - State = State->set(ParamSym, ValueState::getRooted(nullptr, -1)); - Change = true; - continue; - } - State = State->set(ParamSym, *ValS); - Change = true; + bool Change = false; + int idx = 0; + SValExplainer Ex(C.getASTContext()); + for (const auto P : FD->parameters()) { + if (!isGCTrackedType(P->getType())) { + continue; } - return Change; -} - -void GCChecker::checkBeginFunction(CheckerContext &C) const { - // Consider top-level argument values rooted, unless an annotation says otherwise - const auto *LCtx = C.getLocationContext(); - const auto *FD = dyn_cast(LCtx->getDecl()); - if (!FD) - return; - ProgramStateRef State = C.getState(); - bool Change = false; - if (C.inTopFrame()) { - State = State->set((unsigned)-1); - State = State->set((unsigned)-1); - Change = true; - } - if (State->get() == (unsigned)-1) { - if (declHasAnnotation(FD, "julia_gc_disabled")) { - State = State->set(C.getStackFrame()->getIndex()); - Change = true; - } + auto Arg = State->getSVal(CE->getArg(idx++), LCtx->getParent()); + SymbolRef ArgSym = walkToRoot( + [](SymbolRef Sym, const ValueState *OldVState) { return !OldVState; }, + State, Arg.getAsRegion()); + if (!ArgSym) { + continue; } - if (State->get() == (unsigned)-1 && isFDAnnotatedNotSafepoint(FD)) - { - State = State->set(C.getStackFrame()->getIndex()); - Change = true; + const ValueState *ValS = State->get(ArgSym); + if (!ValS) { + report_error( + [&](BugReport *Report) { + Report->addNote( + "Tried to find root for this parameter in inlined call", + PathDiagnosticLocation::create(P, C.getSourceManager())); + }, + C, "Missed allocation of parameter"); + continue; } - if (!C.inTopFrame()) - { - if (propagateArgumentRootedness(C, State) || Change) - C.addTransition(State); - return; + auto Param = State->getLValue(P, LCtx); + SymbolRef ParamSym = State->getSVal(Param).getAsSymbol(); + if (!ParamSym) { + continue; } - SValExplainer Ex(C.getASTContext()); - for (const auto P : FD->parameters()) { - if (declHasAnnotation(P, "julia_require_rooted_slot")) { - auto Param = State->getLValue(P, LCtx); - const MemRegion *Root = State->getSVal(Param).getAsRegion(); - State = State->set(Root, - RootState::getRoot(-1)); - } else if (isGCTrackedType(P->getType())) { - auto Param = State->getLValue(P, LCtx); - SymbolRef AssignedSym = State->getSVal(Param).getAsSymbol(); - if (!AssignedSym) - continue; - assert(AssignedSym); - State = State->set(AssignedSym, - ValueState::getForArgument(FD, P)); - Change = true; - } + if (isGloballyRootedType(P->getType())) { + State = + State->set(ParamSym, ValueState::getRooted(nullptr, -1)); + Change = true; + continue; } - if (Change) { - C.addTransition(State); + State = State->set(ParamSym, *ValS); + Change = true; + } + return Change; +} + +void GCChecker::checkBeginFunction(CheckerContext &C) const { + // Consider top-level argument values rooted, unless an annotation says + // otherwise + const auto *LCtx = C.getLocationContext(); + const auto *FD = dyn_cast(LCtx->getDecl()); + if (!FD) + return; + ProgramStateRef State = C.getState(); + bool Change = false; + if (C.inTopFrame()) { + State = State->set((unsigned)-1); + State = State->set((unsigned)-1); + Change = true; + } + if (State->get() == (unsigned)-1) { + if (declHasAnnotation(FD, "julia_gc_disabled")) { + State = State->set(C.getStackFrame()->getIndex()); + Change = true; + } + } + if (State->get() == (unsigned)-1 && + isFDAnnotatedNotSafepoint(FD)) { + State = State->set(C.getStackFrame()->getIndex()); + Change = true; + } + if (!C.inTopFrame()) { + if (propagateArgumentRootedness(C, State) || Change) + C.addTransition(State); + return; + } + SValExplainer Ex(C.getASTContext()); + for (const auto P : FD->parameters()) { + if (declHasAnnotation(P, "julia_require_rooted_slot")) { + auto Param = State->getLValue(P, LCtx); + const MemRegion *Root = State->getSVal(Param).getAsRegion(); + State = State->set(Root, RootState::getRoot(-1)); + } else if (isGCTrackedType(P->getType())) { + auto Param = State->getLValue(P, LCtx); + SymbolRef AssignedSym = State->getSVal(Param).getAsSymbol(); + if (!AssignedSym) + continue; + assert(AssignedSym); + State = State->set(AssignedSym, + ValueState::getForArgument(FD, P)); + Change = true; } + } + if (Change) { + C.addTransition(State); + } } #if LLVM_VERSION_MAJOR >= 7 -void GCChecker::checkEndFunction(const clang::ReturnStmt* RS, CheckerContext &C) const { +void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, + CheckerContext &C) const { #else void GCChecker::checkEndFunction(CheckerContext &C) const { #endif - ProgramStateRef State = C.getState(); - bool Changed = false; - if (State->get() == C.getStackFrame()->getIndex()) - { - State = State->set((unsigned)-1); - Changed = true; - } - if (State->get() == C.getStackFrame()->getIndex()) { - State = State->set((unsigned)-1); - Changed = true; - } - if (Changed) - C.addTransition(State); - if (!C.inTopFrame()) - return; - if (C.getState()->get() > 0) - report_error(C, "Non-popped GC frame present at end of function"); + ProgramStateRef State = C.getState(); + bool Changed = false; + if (State->get() == C.getStackFrame()->getIndex()) { + State = State->set((unsigned)-1); + Changed = true; + } + if (State->get() == C.getStackFrame()->getIndex()) { + State = State->set((unsigned)-1); + Changed = true; + } + if (Changed) + C.addTransition(State); + if (!C.inTopFrame()) + return; + if (C.getState()->get() > 0) + report_error(C, "Non-popped GC frame present at end of function"); } bool GCChecker::declHasAnnotation(const clang::Decl *D, const char *which) { for (const auto *Ann : D->specific_attrs()) { - if (Ann->getAnnotation() == which) - return true; + if (Ann->getAnnotation() == which) + return true; } return false; } bool GCChecker::isFDAnnotatedNotSafepoint(const clang::FunctionDecl *FD) { - return declHasAnnotation(FD, "julia_not_safepoint"); + return declHasAnnotation(FD, "julia_not_safepoint"); } bool GCChecker::isGCTrackedType(QualType QT) { - return isValueCollection(QT) || isJuliaType([](StringRef Name) { - if (Name.endswith_lower("jl_value_t") || - Name.endswith_lower("jl_svec_t") || - Name.endswith_lower("jl_sym_t") || - Name.endswith_lower("jl_expr_t") || - Name.endswith_lower("jl_code_info_t") || - Name.endswith_lower("jl_array_t") || - Name.endswith_lower("jl_method_t") || - Name.endswith_lower("jl_method_instance_t") || - Name.endswith_lower("jl_tupletype_t") || - Name.endswith_lower("jl_datatype_t") || - Name.endswith_lower("jl_typemap_entry_t") || - Name.endswith_lower("jl_typemap_level_t") || - Name.endswith_lower("jl_typename_t") || - Name.endswith_lower("jl_module_t") || - Name.endswith_lower("jl_tupletype_t") || - Name.endswith_lower("jl_gc_tracked_buffer_t") || - Name.endswith_lower("jl_tls_states_t") || - Name.endswith_lower("jl_binding_t") || - Name.endswith_lower("jl_ordereddict_t") || - Name.endswith_lower("jl_tvar_t") || - Name.endswith_lower("jl_typemap_t") || - Name.endswith_lower("jl_unionall_t") || - Name.endswith_lower("jl_methtable_t") || - Name.endswith_lower("jl_cgval_t") || - Name.endswith_lower("jl_codectx_t") || - Name.endswith_lower("jl_ast_context_t") || - Name.endswith_lower("jl_code_instance_t") || - Name.endswith_lower("jl_excstack_t") || - Name.endswith_lower("jl_task_t") || - Name.endswith_lower("jl_uniontype_t") || - // Probably not technically true for these, but let's allow it - Name.endswith_lower("typemap_intersection_env") || - Name.endswith_lower("interpreter_state") || - Name.endswith_lower("jl_typeenv_t") || - Name.endswith_lower("jl_stenv_t") || - Name.endswith_lower("jl_varbinding_t") || - Name.endswith_lower("set_world") || - Name.endswith_lower("jl_codectx_t")) - { - return true; - } - return false; - }, - QT); + return isValueCollection(QT) || + isJuliaType( + [](StringRef Name) { + if (Name.endswith_lower("jl_value_t") || + Name.endswith_lower("jl_svec_t") || + Name.endswith_lower("jl_sym_t") || + Name.endswith_lower("jl_expr_t") || + Name.endswith_lower("jl_code_info_t") || + Name.endswith_lower("jl_array_t") || + Name.endswith_lower("jl_method_t") || + Name.endswith_lower("jl_method_instance_t") || + Name.endswith_lower("jl_tupletype_t") || + Name.endswith_lower("jl_datatype_t") || + Name.endswith_lower("jl_typemap_entry_t") || + Name.endswith_lower("jl_typemap_level_t") || + Name.endswith_lower("jl_typename_t") || + Name.endswith_lower("jl_module_t") || + Name.endswith_lower("jl_tupletype_t") || + Name.endswith_lower("jl_gc_tracked_buffer_t") || + Name.endswith_lower("jl_tls_states_t") || + Name.endswith_lower("jl_binding_t") || + Name.endswith_lower("jl_ordereddict_t") || + Name.endswith_lower("jl_tvar_t") || + Name.endswith_lower("jl_typemap_t") || + Name.endswith_lower("jl_unionall_t") || + Name.endswith_lower("jl_methtable_t") || + Name.endswith_lower("jl_cgval_t") || + Name.endswith_lower("jl_codectx_t") || + Name.endswith_lower("jl_ast_context_t") || + Name.endswith_lower("jl_code_instance_t") || + Name.endswith_lower("jl_excstack_t") || + Name.endswith_lower("jl_task_t") || + Name.endswith_lower("jl_uniontype_t") || + // Probably not technically true for these, but let's allow + // it + Name.endswith_lower("typemap_intersection_env") || + Name.endswith_lower("interpreter_state") || + Name.endswith_lower("jl_typeenv_t") || + Name.endswith_lower("jl_stenv_t") || + Name.endswith_lower("jl_varbinding_t") || + Name.endswith_lower("set_world") || + Name.endswith_lower("jl_codectx_t")) { + return true; + } + return false; + }, + QT); } bool GCChecker::isGloballyRootedType(QualType QT) const { - return isJuliaType([](StringRef Name) { - return Name.endswith_lower("jl_sym_t"); - }, QT); + return isJuliaType( + [](StringRef Name) { return Name.endswith_lower("jl_sym_t"); }, QT); } -bool GCChecker::isSafepoint(const CallEvent &Call) const -{ +bool GCChecker::isSafepoint(const CallEvent &Call) const { bool isCalleeSafepoint = true; if (Call.isInSystemHeader()) { - // defined by -isystem per https://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-in-system-headers - isCalleeSafepoint = false; + // defined by -isystem per + // https://clang.llvm.org/docs/UsersManual.html#controlling-diagnostics-in-system-headers + isCalleeSafepoint = false; } else { auto *Decl = Call.getDecl(); const DeclContext *DC = Decl ? Decl->getDeclContext() : nullptr; while (DC) { - // Anything in llvm or std is not a safepoint - if (const NamespaceDecl *NDC = dyn_cast(DC)) - if (NDC->getName() == "llvm" || NDC->getName() == "std") - return false; - DC = DC->getParent(); + // Anything in llvm or std is not a safepoint + if (const NamespaceDecl *NDC = dyn_cast(DC)) + if (NDC->getName() == "llvm" || NDC->getName() == "std") + return false; + DC = DC->getParent(); } const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; if (!Decl || !FD) { - const clang::Expr *Callee = dyn_cast(Call.getOriginExpr())->getCallee(); + const clang::Expr *Callee = + dyn_cast(Call.getOriginExpr())->getCallee(); if (const TypedefType *TDT = dyn_cast(Callee->getType())) { - isCalleeSafepoint = !declHasAnnotation(TDT->getDecl(), "julia_not_safepoint"); - } - else if (const CXXPseudoDestructorExpr *PDE = dyn_cast(Callee)) { + isCalleeSafepoint = + !declHasAnnotation(TDT->getDecl(), "julia_not_safepoint"); + } else if (const CXXPseudoDestructorExpr *PDE = + dyn_cast(Callee)) { // A pseudo-destructor is an expression that looks like a member // access to a destructor of a scalar type. A pseudo-destructor // expression has no run-time semantics beyond evaluating the base @@ -797,8 +841,9 @@ bool GCChecker::isSafepoint(const CallEvent &Call) const return isCalleeSafepoint; } -bool GCChecker::processPotentialSafepoint(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const -{ +bool GCChecker::processPotentialSafepoint(const CallEvent &Call, + CheckerContext &C, + ProgramStateRef &State) const { if (!isSafepoint(Call)) return false; bool DidChange = false; @@ -810,81 +855,89 @@ bool GCChecker::processPotentialSafepoint(const CallEvent &Call, CheckerContext SymbolRef SpeciallyRootedSymbol = nullptr; if (FD) { for (unsigned i = 0; i < FD->getNumParams(); ++i) { - QualType ParmType = FD->getParamDecl(i)->getType(); - if (declHasAnnotation(FD->getParamDecl(i), "julia_temporarily_roots")) { - if (ParmType->isPointerType() && ParmType->getPointeeType()->isPointerType() && - isGCTrackedType(ParmType->getPointeeType())) { - // This is probably an out parameter. Find the value it refers to now. - SVal Loaded = State->getSVal(Call.getArgSVal(i).getAs().getValue()); - SpeciallyRootedSymbol = Loaded.getAsSymbol(); - continue; - } - SVal Test = Call.getArgSVal(i); - // Walk backwards to find the symbol that we're tracking for this - // value - const MemRegion *Region = Test.getAsRegion(); - SpeciallyRootedSymbol = walkToRoot([&](SymbolRef Sym, const ValueState *OldVState) { - return !OldVState; - }, State, Region); - break; + QualType ParmType = FD->getParamDecl(i)->getType(); + if (declHasAnnotation(FD->getParamDecl(i), "julia_temporarily_roots")) { + if (ParmType->isPointerType() && + ParmType->getPointeeType()->isPointerType() && + isGCTrackedType(ParmType->getPointeeType())) { + // This is probably an out parameter. Find the value it refers to now. + SVal Loaded = + State->getSVal(Call.getArgSVal(i).getAs().getValue()); + SpeciallyRootedSymbol = Loaded.getAsSymbol(); + continue; } + SVal Test = Call.getArgSVal(i); + // Walk backwards to find the symbol that we're tracking for this + // value + const MemRegion *Region = Test.getAsRegion(); + SpeciallyRootedSymbol = + walkToRoot([&](SymbolRef Sym, + const ValueState *OldVState) { return !OldVState; }, + State, Region); + break; + } } } // Symbolically free all unrooted values. GCValueMapTy AMap = State->get(); for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { - if (I.getData().isJustAllocated()) { - if (SpeciallyRootedSymbol == I.getKey()) - continue; - State = State->set(I.getKey(), ValueState::getFreed()); - DidChange = true; - } + if (I.getData().isJustAllocated()) { + if (SpeciallyRootedSymbol == I.getKey()) + continue; + State = State->set(I.getKey(), ValueState::getFreed()); + DidChange = true; + } } return DidChange; } - - -const GCChecker::ValueState *GCChecker::getValStateForRegion(ASTContext &AstC, - const ProgramStateRef &State, const MemRegion *Region, bool Debug) { - if (!Region) - return nullptr; - SValExplainer Ex(AstC); - SymbolRef Sym = walkToRoot([&](SymbolRef Sym, const ValueState *OldVState) { - return !OldVState || !OldVState->isRooted(); - }, - State, Region); - if (!Sym) - return nullptr; - return State->get(Sym); +const GCChecker::ValueState * +GCChecker::getValStateForRegion(ASTContext &AstC, const ProgramStateRef &State, + const MemRegion *Region, bool Debug) { + if (!Region) + return nullptr; + SValExplainer Ex(AstC); + SymbolRef Sym = walkToRoot( + [&](SymbolRef Sym, const ValueState *OldVState) { + return !OldVState || !OldVState->isRooted(); + }, + State, Region); + if (!Sym) + return nullptr; + return State->get(Sym); } -bool GCChecker::processArgumentRooting(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const { - auto *Decl = Call.getDecl(); - const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; - if (!FD) - return false; - const MemRegion *RootingRegion = nullptr; - SymbolRef RootedSymbol = nullptr; - for (unsigned i = 0; i < FD->getNumParams(); ++i) { - if (declHasAnnotation(FD->getParamDecl(i), "julia_rooting_argument")) { - RootingRegion = Call.getArgSVal(i).getAsRegion(); - } else if (declHasAnnotation(FD->getParamDecl(i), "julia_rooted_argument")) { - RootedSymbol = Call.getArgSVal(i).getAsSymbol(); - } +bool GCChecker::processArgumentRooting(const CallEvent &Call, CheckerContext &C, + ProgramStateRef &State) const { + auto *Decl = Call.getDecl(); + const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; + if (!FD) + return false; + const MemRegion *RootingRegion = nullptr; + SymbolRef RootedSymbol = nullptr; + for (unsigned i = 0; i < FD->getNumParams(); ++i) { + if (declHasAnnotation(FD->getParamDecl(i), "julia_rooting_argument")) { + RootingRegion = Call.getArgSVal(i).getAsRegion(); + } else if (declHasAnnotation(FD->getParamDecl(i), + "julia_rooted_argument")) { + RootedSymbol = Call.getArgSVal(i).getAsSymbol(); } - if (!RootingRegion || !RootedSymbol) - return false; - SValExplainer Ex(C.getASTContext()); - const ValueState *OldVState = getValStateForRegion(C.getASTContext(), State, RootingRegion); - if (!OldVState) - return false; - State = State->set(RootedSymbol, *OldVState); - return true; + } + if (!RootingRegion || !RootedSymbol) + return false; + SValExplainer Ex(C.getASTContext()); + const ValueState *OldVState = + getValStateForRegion(C.getASTContext(), State, RootingRegion); + if (!OldVState) + return false; + State = State->set(RootedSymbol, *OldVState); + return true; } -bool GCChecker::processAllocationOfResult(const CallEvent &Call, CheckerContext &C, ProgramStateRef &State) const { +bool GCChecker::processAllocationOfResult(const CallEvent &Call, + CheckerContext &C, + ProgramStateRef &State) const { QualType QT = Call.getResultType(); if (!isGCTrackedType(QT)) return false; @@ -894,69 +947,73 @@ bool GCChecker::processAllocationOfResult(const CallEvent &Call, CheckerContext SymbolRef Sym = Call.getReturnValue().getAsSymbol(); SValExplainer Ex(C.getASTContext()); if (!Sym) { - SVal S = C.getSValBuilder().conjureSymbolVal(Call.getOriginExpr(), - C.getLocationContext(), QT, C.blockCount()); + SVal S = C.getSValBuilder().conjureSymbolVal( + Call.getOriginExpr(), C.getLocationContext(), QT, C.blockCount()); State = State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), S); Sym = S.getAsSymbol(); } if (isGloballyRootedType(QT)) - State = State->set(Sym, ValueState::getRooted(nullptr, -1)); + State = State->set(Sym, ValueState::getRooted(nullptr, -1)); else { - const ValueState *ValS = State->get(Sym); - ValueState NewVState = ValueState::getAllocated(); - if (ValS) { - // If the call was inlined, we may have accidentally killed the return - // value above. Revive it here. - const ValueState *PrevValState = C.getState()->get(Sym); - if (!ValS->isPotentiallyFreed() || (PrevValState && PrevValState->isPotentiallyFreed())) { - return false; - } - NewVState = *PrevValState; + const ValueState *ValS = State->get(Sym); + ValueState NewVState = ValueState::getAllocated(); + if (ValS) { + // If the call was inlined, we may have accidentally killed the return + // value above. Revive it here. + const ValueState *PrevValState = C.getState()->get(Sym); + if (!ValS->isPotentiallyFreed() || + (PrevValState && PrevValState->isPotentiallyFreed())) { + return false; } - auto *Decl = Call.getDecl(); - const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; - if (FD) { - if (declHasAnnotation(FD, "julia_globally_rooted")) { + NewVState = *PrevValState; + } + auto *Decl = Call.getDecl(); + const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; + if (FD) { + if (declHasAnnotation(FD, "julia_globally_rooted")) { + NewVState = ValueState::getRooted(nullptr, -1); + } else { + // Special case for jl_box_ functions which have value-dependent + // global roots. + StringRef FDName = + FD->getDeclName().isIdentifier() ? FD->getName() : ""; + if (FDName.startswith_lower("jl_box_")) { + SVal Arg = Call.getArgSVal(0); + if (auto CI = Arg.getAs()) { + const llvm::APSInt &Value = CI->getValue(); + bool GloballyRooted = false; + const int64_t NBOX_C = 1024; + if (FDName.startswith_lower("jl_box_u")) { + if (Value < NBOX_C) { + GloballyRooted = true; + } + } else { + if (-NBOX_C / 2 < Value && Value < (NBOX_C - NBOX_C / 2)) { + GloballyRooted = true; + } + } + if (GloballyRooted) { NewVState = ValueState::getRooted(nullptr, -1); - } else { - // Special case for jl_box_ functions which have value-dependent - // global roots. - StringRef FDName = FD->getDeclName().isIdentifier() ? FD->getName() : ""; - if (FDName.startswith_lower("jl_box_")) { - SVal Arg = Call.getArgSVal(0); - if (auto CI = Arg.getAs()) { - const llvm::APSInt &Value = CI->getValue(); - bool GloballyRooted = false; - const int64_t NBOX_C = 1024; - if (FDName.startswith_lower("jl_box_u")) { - if (Value < NBOX_C) { - GloballyRooted = true; - } - } else { - if (-NBOX_C/2 < Value && Value < (NBOX_C - NBOX_C/2)) { - GloballyRooted = true; - } - } - if (GloballyRooted) { - NewVState = ValueState::getRooted(nullptr, -1); - } - } - } else { - for (unsigned i = 0; i < FD->getNumParams(); ++i) { - if (declHasAnnotation(FD->getParamDecl(i), "julia_propagates_root")) { - SVal Test = Call.getArgSVal(i); - // Walk backwards to find the region that roots this value - const MemRegion *Region = Test.getAsRegion(); - const ValueState *OldVState = getValStateForRegion(C.getASTContext(), State, Region); - if (OldVState) - NewVState = *OldVState; - break; - } - } - } + } + } + } else { + for (unsigned i = 0; i < FD->getNumParams(); ++i) { + if (declHasAnnotation(FD->getParamDecl(i), + "julia_propagates_root")) { + SVal Test = Call.getArgSVal(i); + // Walk backwards to find the region that roots this value + const MemRegion *Region = Test.getAsRegion(); + const ValueState *OldVState = + getValStateForRegion(C.getASTContext(), State, Region); + if (OldVState) + NewVState = *OldVState; + break; + } } + } } - State = State->set(Sym, NewVState); + } + State = State->set(Sym, NewVState); } return true; } @@ -971,253 +1028,274 @@ void GCChecker::checkPostCall(const CallEvent &Call, CheckerContext &C) const { } // Implicitly root values that were casted to globally rooted values -void GCChecker::checkPostStmt(const CStyleCastExpr *CE, CheckerContext &C) const { - if (!isGloballyRootedType(CE->getTypeAsWritten())) - return; - SymbolRef Sym = C.getSVal(CE).getAsSymbol(); - if (!Sym) - return; - C.addTransition(C.getState()->set(Sym, ValueState::getRooted(nullptr, -1))); +void GCChecker::checkPostStmt(const CStyleCastExpr *CE, + CheckerContext &C) const { + if (!isGloballyRootedType(CE->getTypeAsWritten())) + return; + SymbolRef Sym = C.getSVal(CE).getAsSymbol(); + if (!Sym) + return; + C.addTransition( + C.getState()->set(Sym, ValueState::getRooted(nullptr, -1))); } -SymbolRef GCChecker::getSymbolForResult(const Expr *Result, const ValueState *OldValS, ProgramStateRef &State, CheckerContext &C) const -{ +SymbolRef GCChecker::getSymbolForResult(const Expr *Result, + const ValueState *OldValS, + ProgramStateRef &State, + CheckerContext &C) const { auto ValLoc = C.getSVal(Result).getAs(); if (!ValLoc) { - return nullptr; + return nullptr; } SVal Loaded = State->getSVal(*ValLoc); SValExplainer Ex(C.getASTContext()); if (Loaded.isUnknown()) { QualType QT = Result->getType(); if (!QT->isPointerType()) - return nullptr; + return nullptr; if (OldValS || GCChecker::isGCTrackedType(QT)) { - Loaded = C.getSValBuilder().conjureSymbolVal(nullptr, Result, C.getLocationContext(), - Result->getType(), - C.blockCount()); + Loaded = C.getSValBuilder().conjureSymbolVal( + nullptr, Result, C.getLocationContext(), Result->getType(), + C.blockCount()); State = State->bindLoc(*ValLoc, Loaded, C.getLocationContext()); - //State = State->BindExpr(Result, C.getLocationContext(), State->getSVal(*ValLoc)); + // State = State->BindExpr(Result, C.getLocationContext(), + // State->getSVal(*ValLoc)); } } return Loaded.getAsSymbol(); } -void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent, bool ParentIsLoc, CheckerContext &C) const -{ - bool ResultTracked = true; - ProgramStateRef State = C.getState(); - if (isGloballyRootedType(Result->getType())) { - SymbolRef NewSym = getSymbolForResult(Result, nullptr, State, C); - if (!NewSym) { - return; - } - const ValueState *NewValS = State->get(NewSym); - if (NewValS && NewValS->isRooted() && NewValS->RootDepth == -1) { - return; - } - C.addTransition(State->set(NewSym, ValueState::getRooted(nullptr, -1))); - return; - } - if (!isGCTrackedType(Result->getType())) { - // TODO: We may want to refine this. This is to track pointers through the array list - // in jl_module_t. - bool ParentIsModule = isJuliaType([](StringRef Name) { - return Name.endswith_lower("jl_module_t"); }, Parent->getType()); - bool ResultIsArrayList = isJuliaType([](StringRef Name) { - return Name.endswith_lower("arraylist_t"); }, Result->getType()); - if (!(ParentIsModule && ResultIsArrayList) && - isGCTrackedType(Parent->getType())) { - ResultTracked = false; - } - } - // This is the pointer - SValExplainer Ex(C.getASTContext()); - auto ValLoc = C.getSVal(Result).getAs(); - if (!ValLoc) { - return; - } - SVal ParentVal = C.getSVal(Parent); - SymbolRef OldSym = ParentVal.getAsSymbol(true); - const MemRegion *Region = C.getSVal(Parent).getAsRegion(); - const ValueState *OldValS = OldSym ? State->get(OldSym) : nullptr; - SymbolRef NewSym = getSymbolForResult(Result, OldValS, State, C); +void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent, + bool ParentIsLoc, CheckerContext &C) const { + bool ResultTracked = true; + ProgramStateRef State = C.getState(); + if (isGloballyRootedType(Result->getType())) { + SymbolRef NewSym = getSymbolForResult(Result, nullptr, State, C); if (!NewSym) { - return; + return; } - // NewSym might already have a better root const ValueState *NewValS = State->get(NewSym); - if (Region) { - const VarRegion *VR = Region->getAs(); - bool inheritedState = false; - ValueState Updated = ValueState::getRooted(Region, -1); - if (VR && isa(VR->getDecl())) { - // This works around us not being able to track symbols for struct/union - // parameters very well. - const auto *FD = dyn_cast(C.getLocationContext()->getDecl()); - if (FD) { - inheritedState = true; - Updated = ValueState::getForArgument(FD, cast(VR->getDecl())); - } - } else { - VR = Helpers::walk_back_to_global_VR(Region); - if (VR) { - if (VR && rootRegionIfGlobal(VR, State, C)) { - inheritedState = true; - } - } - } - if (inheritedState && ResultTracked) { - C.addTransition(State->set(NewSym, Updated)); - return; - } + if (NewValS && NewValS->isRooted() && NewValS->RootDepth == -1) { + return; } - if (NewValS && NewValS->isRooted()) { - return; + C.addTransition( + State->set(NewSym, ValueState::getRooted(nullptr, -1))); + return; + } + if (!isGCTrackedType(Result->getType())) { + // TODO: We may want to refine this. This is to track pointers through the + // array list in jl_module_t. + bool ParentIsModule = isJuliaType( + [](StringRef Name) { return Name.endswith_lower("jl_module_t"); }, + Parent->getType()); + bool ResultIsArrayList = isJuliaType( + [](StringRef Name) { return Name.endswith_lower("arraylist_t"); }, + Result->getType()); + if (!(ParentIsModule && ResultIsArrayList) && + isGCTrackedType(Parent->getType())) { + ResultTracked = false; } - if (!OldValS) { - // This way we'll get better diagnostics - if (isGCTrackedType(Result->getType())) { - C.addTransition(State->set(NewSym, ValueState::getUntracked())); + } + // This is the pointer + SValExplainer Ex(C.getASTContext()); + auto ValLoc = C.getSVal(Result).getAs(); + if (!ValLoc) { + return; + } + SVal ParentVal = C.getSVal(Parent); + SymbolRef OldSym = ParentVal.getAsSymbol(true); + const MemRegion *Region = C.getSVal(Parent).getAsRegion(); + const ValueState *OldValS = OldSym ? State->get(OldSym) : nullptr; + SymbolRef NewSym = getSymbolForResult(Result, OldValS, State, C); + if (!NewSym) { + return; + } + // NewSym might already have a better root + const ValueState *NewValS = State->get(NewSym); + if (Region) { + const VarRegion *VR = Region->getAs(); + bool inheritedState = false; + ValueState Updated = ValueState::getRooted(Region, -1); + if (VR && isa(VR->getDecl())) { + // This works around us not being able to track symbols for struct/union + // parameters very well. + const auto *FD = + dyn_cast(C.getLocationContext()->getDecl()); + if (FD) { + inheritedState = true; + Updated = + ValueState::getForArgument(FD, cast(VR->getDecl())); + } + } else { + VR = Helpers::walk_back_to_global_VR(Region); + if (VR) { + if (VR && rootRegionIfGlobal(VR, State, C)) { + inheritedState = true; } - return; + } } - if (OldValS->isPotentiallyFreed()) { - report_value_error(C, OldSym, "Creating derivative of value that may have been GCed"); - } else if (ResultTracked) { - C.addTransition(State->set(NewSym, *OldValS)); - return; + if (inheritedState && ResultTracked) { + C.addTransition(State->set(NewSym, Updated)); + return; } + } + if (NewValS && NewValS->isRooted()) { + return; + } + if (!OldValS) { + // This way we'll get better diagnostics + if (isGCTrackedType(Result->getType())) { + C.addTransition( + State->set(NewSym, ValueState::getUntracked())); + } + return; + } + if (OldValS->isPotentiallyFreed()) { + report_value_error(C, OldSym, + "Creating derivative of value that may have been GCed"); + } else if (ResultTracked) { + C.addTransition(State->set(NewSym, *OldValS)); + return; + } } // Propagate rootedness through subscript -void GCChecker::checkPostStmt(const ArraySubscriptExpr *ASE, CheckerContext &C) const -{ - // Could be a root array, in which case this should be considered rooted - // by that array. - const MemRegion *Region = C.getSVal(ASE->getLHS()).getAsRegion(); - ProgramStateRef State = C.getState(); - SValExplainer Ex(C.getASTContext()); - if (Region && Region->getAs() && isGCTrackedType(ASE->getType())) { - const RootState *RS = State->get( - Region->getAs()->getSuperRegion()); - if (RS) { - ValueState ValS = ValueState::getRooted(Region, State->get()); - SymbolRef NewSym = getSymbolForResult(ASE, &ValS, State, C); - if (!NewSym) { - return; - } - const ValueState *ExistingValS = State->get(NewSym); - if (ExistingValS && ExistingValS->isRooted() && ExistingValS->RootDepth < ValS.RootDepth) - return; - C.addTransition(State->set(NewSym, ValS)); - return; - } +void GCChecker::checkPostStmt(const ArraySubscriptExpr *ASE, + CheckerContext &C) const { + // Could be a root array, in which case this should be considered rooted + // by that array. + const MemRegion *Region = C.getSVal(ASE->getLHS()).getAsRegion(); + ProgramStateRef State = C.getState(); + SValExplainer Ex(C.getASTContext()); + if (Region && Region->getAs() && + isGCTrackedType(ASE->getType())) { + const RootState *RS = + State->get(Region->getAs()->getSuperRegion()); + if (RS) { + ValueState ValS = ValueState::getRooted(Region, State->get()); + SymbolRef NewSym = getSymbolForResult(ASE, &ValS, State, C); + if (!NewSym) { + return; + } + const ValueState *ExistingValS = State->get(NewSym); + if (ExistingValS && ExistingValS->isRooted() && + ExistingValS->RootDepth < ValS.RootDepth) + return; + C.addTransition(State->set(NewSym, ValS)); + return; } - checkDerivingExpr(ASE, ASE->getLHS(), true, C); + } + checkDerivingExpr(ASE, ASE->getLHS(), true, C); } -void GCChecker::checkPostStmt(const MemberExpr *ME, CheckerContext &C) const -{ - // It is possible for the member itself to be gcrooted, so check that first - const MemRegion *Region = C.getSVal(ME).getAsRegion(); - ProgramStateRef State = C.getState(); - if (Region && isGCTrackedType(ME->getType())) { - if (const RootState *RS = State->get(Region)) { - ValueState ValS = ValueState::getRooted(Region, RS->RootedAtDepth); - SymbolRef NewSym = getSymbolForResult(ME, &ValS, State, C); - if (!NewSym) - return; - const ValueState *ExistingValS = State->get(NewSym); - if (ExistingValS && ExistingValS->isRooted() && ExistingValS->RootDepth < ValS.RootDepth) - return; - C.addTransition(C.getState()->set(NewSym, ValS)); - return; - } - } - if (!ME->getType()->isPointerType()) +void GCChecker::checkPostStmt(const MemberExpr *ME, CheckerContext &C) const { + // It is possible for the member itself to be gcrooted, so check that first + const MemRegion *Region = C.getSVal(ME).getAsRegion(); + ProgramStateRef State = C.getState(); + if (Region && isGCTrackedType(ME->getType())) { + if (const RootState *RS = State->get(Region)) { + ValueState ValS = ValueState::getRooted(Region, RS->RootedAtDepth); + SymbolRef NewSym = getSymbolForResult(ME, &ValS, State, C); + if (!NewSym) + return; + const ValueState *ExistingValS = State->get(NewSym); + if (ExistingValS && ExistingValS->isRooted() && + ExistingValS->RootDepth < ValS.RootDepth) return; - clang::Expr *Base = ME->getBase(); - checkDerivingExpr(ME, Base, true, C); + C.addTransition(C.getState()->set(NewSym, ValS)); + return; + } + } + if (!ME->getType()->isPointerType()) + return; + clang::Expr *Base = ME->getBase(); + checkDerivingExpr(ME, Base, true, C); } -void GCChecker::checkPostStmt(const UnaryOperator *UO, CheckerContext &C) const -{ - if (UO->getOpcode() == UO_Deref) { - checkDerivingExpr(UO, UO->getSubExpr(), true, C); - } +void GCChecker::checkPostStmt(const UnaryOperator *UO, + CheckerContext &C) const { + if (UO->getOpcode() == UO_Deref) { + checkDerivingExpr(UO, UO->getSubExpr(), true, C); + } } USED_FUNC void GCChecker::dumpState(const ProgramStateRef &State) { - GCValueMapTy AMap = State->get(); - llvm::raw_ostream &Out = llvm::outs(); - Out << "State: " << "\n"; - for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { - I.getKey()->dumpToStream(Out); - } + GCValueMapTy AMap = State->get(); + llvm::raw_ostream &Out = llvm::outs(); + Out << "State: " + << "\n"; + for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { + I.getKey()->dumpToStream(Out); + } } void GCChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - if (!gcEnabledHere(C)) - return; - unsigned NumArgs = Call.getNumArgs(); - ProgramStateRef State = C.getState(); - bool isCalleeSafepoint = isSafepoint(Call); - auto *Decl = Call.getDecl(); - const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; - if (!safepointEnabledHere(C) && isCalleeSafepoint) { - // Suppress this warning if the function is noreturn. - // We could separate out "not safepoint, except for noreturn functions", - // but that seems like a lot of effort with little benefit. - if (!FD || !FD->isNoReturn()) - { - report_error(C, "Calling potential safepoint from function annotated JL_NOTSAFEPOINT"); - return; - } + if (!gcEnabledHere(C)) + return; + unsigned NumArgs = Call.getNumArgs(); + ProgramStateRef State = C.getState(); + bool isCalleeSafepoint = isSafepoint(Call); + auto *Decl = Call.getDecl(); + const FunctionDecl *FD = Decl ? Decl->getAsFunction() : nullptr; + if (!safepointEnabledHere(C) && isCalleeSafepoint) { + // Suppress this warning if the function is noreturn. + // We could separate out "not safepoint, except for noreturn functions", + // but that seems like a lot of effort with little benefit. + if (!FD || !FD->isNoReturn()) { + report_error(C, "Calling potential safepoint from function annotated " + "JL_NOTSAFEPOINT"); + return; } - SValExplainer Ex(C.getASTContext()); - if (FD && FD->getDeclName().isIdentifier() && FD->getName() == "JL_GC_PROMISE_ROOTED") - return; - for (unsigned idx = 0; idx < NumArgs; ++idx) { - SVal Arg = Call.getArgSVal(idx); - SymbolRef Sym = Arg.getAsSymbol(); - // Hack to work around passing unions/structs by value. - if (auto LCV = Arg.getAs()) { - const MemRegion *R = LCV->getRegion(); - if (R) { - if (const SubRegion *SR = R->getAs()) { - if (const SymbolicRegion *SSR = SR->getSuperRegion()->getAs()) { - Sym = SSR->getSymbol(); - } - } + } + SValExplainer Ex(C.getASTContext()); + if (FD && FD->getDeclName().isIdentifier() && + FD->getName() == "JL_GC_PROMISE_ROOTED") + return; + for (unsigned idx = 0; idx < NumArgs; ++idx) { + SVal Arg = Call.getArgSVal(idx); + SymbolRef Sym = Arg.getAsSymbol(); + // Hack to work around passing unions/structs by value. + if (auto LCV = Arg.getAs()) { + const MemRegion *R = LCV->getRegion(); + if (R) { + if (const SubRegion *SR = R->getAs()) { + if (const SymbolicRegion *SSR = + SR->getSuperRegion()->getAs()) { + Sym = SSR->getSymbol(); } } - if (!Sym) - continue; - auto *ValState = State->get(Sym); - if (!ValState) - continue; - SourceRange range; - if (const Expr *E = Call.getArgExpr(idx)) { - range = E->getSourceRange(); - if (!isGCTrackedType(E->getType())) - continue; - } - if (ValState->isPotentiallyFreed()) { - report_value_error(C, Sym, "Argument value may have been GCed", range); - } - if (ValState->isRooted()) - continue; - bool MaybeUnrooted = false; - if (FD) { - if (idx < FD->getNumParams()) { - MaybeUnrooted = declHasAnnotation(FD->getParamDecl(idx), "julia_maybe_unrooted"); - } - } - if (!MaybeUnrooted && isCalleeSafepoint) { - report_value_error(C, Sym, "Passing non-rooted value as argument to function that may GC", range); - } + } + } + if (!Sym) + continue; + auto *ValState = State->get(Sym); + if (!ValState) + continue; + SourceRange range; + if (const Expr *E = Call.getArgExpr(idx)) { + range = E->getSourceRange(); + if (!isGCTrackedType(E->getType())) + continue; + } + if (ValState->isPotentiallyFreed()) { + report_value_error(C, Sym, "Argument value may have been GCed", range); + } + if (ValState->isRooted()) + continue; + bool MaybeUnrooted = false; + if (FD) { + if (idx < FD->getNumParams()) { + MaybeUnrooted = + declHasAnnotation(FD->getParamDecl(idx), "julia_maybe_unrooted"); + } } + if (!MaybeUnrooted && isCalleeSafepoint) { + report_value_error( + C, Sym, + "Passing non-rooted value as argument to function that may GC", + range); + } + } } #if LLVM_VERSION_MAJOR >= 9 @@ -1225,326 +1303,344 @@ bool GCChecker::evalCall(const CallEvent &Call, #else bool GCChecker::evalCall(const CallExpr *CE, #endif - CheckerContext &C) const { - // These checks should have no effect on the surrounding environment - // (globals should not be invalidated, etc), hence the use of evalCall. + CheckerContext &C) const { + // These checks should have no effect on the surrounding environment + // (globals should not be invalidated, etc), hence the use of evalCall. #if LLVM_VERSION_MAJOR >= 9 - const CallExpr *CE = dyn_cast(Call.getOriginExpr()); + const CallExpr *CE = dyn_cast(Call.getOriginExpr()); #endif - unsigned CurrentDepth = C.getState()->get(); - auto name = C.getCalleeName(CE); - SValExplainer Ex(C.getASTContext()); - if (name == "JL_GC_POP") { - if (CurrentDepth == 0) { - report_error(C, "JL_GC_POP without corresponding push"); - return true; - } - CurrentDepth -= 1; - // Go through all roots, see which ones are no longer with us. - // The go through the values and unroot those for which those were our - // roots. - ProgramStateRef State = C.getState()->set(CurrentDepth); - GCRootMapTy AMap = State->get(); - SmallVector PoppedRoots; - for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { - if (I.getData().shouldPopAtDepth(CurrentDepth)) { - PoppedRoots.push_back(I.getKey()); - State = State->remove(I.getKey()); - } - } - GCValueMapTy VMap = State->get(); - for (const MemRegion *R : PoppedRoots) { - for (auto I = VMap.begin(), E = VMap.end(); I != E; ++I) { - if (I.getData().isRootedBy(R)) { - State = State->set(I.getKey(), ValueState::getAllocated()); - } - } - } - C.addTransition(State); - return true; - } else if (name == "JL_GC_PUSH1" || - name == "JL_GC_PUSH2" || - name == "JL_GC_PUSH3" || - name == "JL_GC_PUSH4" || - name == "JL_GC_PUSH5" || - name == "JL_GC_PUSH6") { - ProgramStateRef State = C.getState(); - // Transform slots to roots, transform values to rooted - unsigned NumArgs = CE->getNumArgs(); - for (unsigned i = 0; i < NumArgs; ++i) { - SVal V = C.getSVal(CE->getArg(i)); - auto MRV = V.getAs(); - if (!MRV) { - report_error(C, "JL_GC_PUSH with something other than a local variable"); - return true; - } - const MemRegion *Region = MRV->getRegion(); - State = State->set(Region, RootState::getRoot(CurrentDepth)); - // Now for the value - SVal Value = State->getSVal(Region); - SymbolRef Sym = Value.getAsSymbol(); - if (!Sym) - continue; - const ValueState *ValState = State->get(Sym); - if (!ValState) - continue; - if (ValState->isPotentiallyFreed()) - report_value_error(C, Sym, "Trying to root value which may have been GCed"); - if (!ValState->isRooted()) { - State = State->set(Sym, ValueState::getRooted(Region, CurrentDepth)); - } - } - CurrentDepth += 1; - State = State->set(CurrentDepth); - C.addTransition(State); - return true; - } else if (name == "_JL_GC_PUSHARGS") { - ProgramStateRef State = C.getState(); - SVal ArgArray = C.getSVal(CE->getArg(0)); - auto MRV = ArgArray.getAs(); - if (!MRV) { - report_error(C, "JL_GC_PUSH with something other than an args array"); - return true; - } - const MemRegion *Region = MRV->getRegion()->StripCasts(); - State = State->set(Region, RootState::getRootArray(CurrentDepth)); - // The Argument array may also be used as a value, so make it rooted - // SymbolRef ArgArraySym = ArgArray.getAsSymbol(); - // assert(ArgArraySym); - // State = State->set(ArgArraySym, ValueState::getRooted(Region, CurrentDepth)); - CurrentDepth += 1; - State = State->set(CurrentDepth); - C.addTransition(State); - return true; - } else if (name == "JL_GC_PROMISE_ROOTED") { - SVal Arg = C.getSVal(CE->getArg(0)); - SymbolRef Sym = Arg.getAsSymbol(); - if (!Sym) { - report_error(C, "Can not understand this promise."); - return true; - } - C.addTransition(C.getState()->set(Sym, ValueState::getRooted(nullptr, -1))); - return true; - } else if (name == "jl_gc_push_arraylist") { - CurrentDepth += 1; - ProgramStateRef State = C.getState()->set(CurrentDepth); - SVal ArrayList = C.getSVal(CE->getArg(1)); - // Try to find the items field - FieldDecl *FD = NULL; - RecordDecl *RD = dyn_cast_or_null(CE->getArg(1)->getType()->getPointeeType()->getAsTagDecl()); - if (RD) { - for (FieldDecl *X : RD->fields()) { - if (X->getName() == "items") { - FD = X; - break; - } - } - } - if (FD) { - Loc ItemsLoc = State->getLValue(FD, ArrayList).getAs().getValue(); - SVal Items = State->getSVal(ItemsLoc); - if (Items.isUnknown()) { - Items = C.getSValBuilder().conjureSymbolVal(CE, - C.getLocationContext(), FD->getType(), - C.blockCount()); - State = State->bindLoc(ItemsLoc, Items, C.getLocationContext()); - } - assert(Items.getAsRegion()); - // The items list is now rooted - State = State->set(Items.getAsRegion(), - RootState::getRootArray(CurrentDepth)); - } - C.addTransition(State); - return true; - } else if (name == "jl_ast_preserve") { - // TODO: Maybe bind the rooting to the context. For now, the second - // argument gets unconditionally rooted - ProgramStateRef State = C.getState(); - SymbolRef Sym = C.getSVal(CE->getArg(1)).getAsSymbol(); - if (!Sym) - return true; - C.addTransition(State->set(Sym, ValueState::getRooted(nullptr, -1))); - return true; - } else if (name == "jl_gc_enable") { - ProgramStateRef State = C.getState(); - // Check for a literal argument - SVal Arg = C.getSVal(CE->getArg(0)); - auto CI = Arg.getAs(); - bool EnabledAfter = true; - if (CI) { - const llvm::APSInt &Val = CI->getValue(); - EnabledAfter = Val != 0; - } else { - cast(Arg.getAsSymbol())->getStmt()->dump(); - } - bool EnabledNow = State->get() == (unsigned)-1; - if (!EnabledAfter) { - State = State->set((unsigned)-2); - } else { - State = State->set((unsigned)-1); + unsigned CurrentDepth = C.getState()->get(); + auto name = C.getCalleeName(CE); + SValExplainer Ex(C.getASTContext()); + if (name == "JL_GC_POP") { + if (CurrentDepth == 0) { + report_error(C, "JL_GC_POP without corresponding push"); + return true; + } + CurrentDepth -= 1; + // Go through all roots, see which ones are no longer with us. + // The go through the values and unroot those for which those were our + // roots. + ProgramStateRef State = C.getState()->set(CurrentDepth); + GCRootMapTy AMap = State->get(); + SmallVector PoppedRoots; + for (auto I = AMap.begin(), E = AMap.end(); I != E; ++I) { + if (I.getData().shouldPopAtDepth(CurrentDepth)) { + PoppedRoots.push_back(I.getKey()); + State = State->remove(I.getKey()); + } + } + GCValueMapTy VMap = State->get(); + for (const MemRegion *R : PoppedRoots) { + for (auto I = VMap.begin(), E = VMap.end(); I != E; ++I) { + if (I.getData().isRootedBy(R)) { + State = + State->set(I.getKey(), ValueState::getAllocated()); } - // GC State is explicitly modeled, so let's make sure - // the execution matches our model - SVal Result = C.getSValBuilder().makeTruthVal(EnabledNow, CE->getType()); - C.addTransition(State->BindExpr(CE, C.getLocationContext(), Result)); - return true; + } } - return false; -} - -void GCChecker::checkBind(SVal LVal, SVal RVal, const clang::Stmt *S, CheckerContext &C) const { - auto State = C.getState(); - const MemRegion *R = LVal.getAsRegion(); - if (!R) { - return; + C.addTransition(State); + return true; + } else if (name == "JL_GC_PUSH1" || name == "JL_GC_PUSH2" || + name == "JL_GC_PUSH3" || name == "JL_GC_PUSH4" || + name == "JL_GC_PUSH5" || name == "JL_GC_PUSH6") { + ProgramStateRef State = C.getState(); + // Transform slots to roots, transform values to rooted + unsigned NumArgs = CE->getNumArgs(); + for (unsigned i = 0; i < NumArgs; ++i) { + SVal V = C.getSVal(CE->getArg(i)); + auto MRV = V.getAs(); + if (!MRV) { + report_error(C, + "JL_GC_PUSH with something other than a local variable"); + return true; + } + const MemRegion *Region = MRV->getRegion(); + State = State->set(Region, RootState::getRoot(CurrentDepth)); + // Now for the value + SVal Value = State->getSVal(Region); + SymbolRef Sym = Value.getAsSymbol(); + if (!Sym) + continue; + const ValueState *ValState = State->get(Sym); + if (!ValState) + continue; + if (ValState->isPotentiallyFreed()) + report_value_error(C, Sym, + "Trying to root value which may have been GCed"); + if (!ValState->isRooted()) { + State = State->set( + Sym, ValueState::getRooted(Region, CurrentDepth)); + } } - bool shouldBeRootArray = false; - const ElementRegion *ER = R->getAs(); - if (ER) { - R = R->getBaseRegion()->StripCasts(); - shouldBeRootArray = true; + CurrentDepth += 1; + State = State->set(CurrentDepth); + C.addTransition(State); + return true; + } else if (name == "_JL_GC_PUSHARGS") { + ProgramStateRef State = C.getState(); + SVal ArgArray = C.getSVal(CE->getArg(0)); + auto MRV = ArgArray.getAs(); + if (!MRV) { + report_error(C, "JL_GC_PUSH with something other than an args array"); + return true; } - SymbolRef Sym = RVal.getAsSymbol(); + const MemRegion *Region = MRV->getRegion()->StripCasts(); + State = + State->set(Region, RootState::getRootArray(CurrentDepth)); + // The Argument array may also be used as a value, so make it rooted + // SymbolRef ArgArraySym = ArgArray.getAsSymbol(); + // assert(ArgArraySym); + // State = State->set(ArgArraySym, ValueState::getRooted(Region, + // CurrentDepth)); + CurrentDepth += 1; + State = State->set(CurrentDepth); + C.addTransition(State); + return true; + } else if (name == "JL_GC_PROMISE_ROOTED") { + SVal Arg = C.getSVal(CE->getArg(0)); + SymbolRef Sym = Arg.getAsSymbol(); if (!Sym) { - return; + report_error(C, "Can not understand this promise."); + return true; } - const auto *RootState = State->get(R); - if (!RootState) { - const ValueState *ValSP = nullptr; - ValueState ValS; - if (rootRegionIfGlobal(R->getBaseRegion(), State, C, &ValS)) { - ValSP = &ValS; - } else { - ValSP = getValStateForRegion(C.getASTContext(), State, R); - } - if (!ValSP || !ValSP->isRooted()) { - return; + C.addTransition( + C.getState()->set(Sym, ValueState::getRooted(nullptr, -1))); + return true; + } else if (name == "jl_gc_push_arraylist") { + CurrentDepth += 1; + ProgramStateRef State = C.getState()->set(CurrentDepth); + SVal ArrayList = C.getSVal(CE->getArg(1)); + // Try to find the items field + FieldDecl *FD = NULL; + RecordDecl *RD = dyn_cast_or_null( + CE->getArg(1)->getType()->getPointeeType()->getAsTagDecl()); + if (RD) { + for (FieldDecl *X : RD->fields()) { + if (X->getName() == "items") { + FD = X; + break; } - const auto *RValState = State->get(Sym); - if (RValState && RValState->isRooted() && RValState->RootDepth < ValSP->RootDepth) - return; - C.addTransition(State->set(Sym, *ValSP)); - return; - } - if (shouldBeRootArray && !RootState->isRootArray()) { - report_error(C, "This assignment looks weird. Expected a root array on the LHS."); - return; + } } - const auto *RValState = State->get(Sym); - if (!RValState) { - if (rootRegionIfGlobal(Sym->getOriginRegion(), State, C)) { - C.addTransition(State); - return; - } - Sym->dump(); - if (auto *SC = dyn_cast(Sym)) { - SC->getStmt()->dump(); - } - report_value_error(C, Sym, "Saw assignment to root, but missed the allocation"); - return; + if (FD) { + Loc ItemsLoc = State->getLValue(FD, ArrayList).getAs().getValue(); + SVal Items = State->getSVal(ItemsLoc); + if (Items.isUnknown()) { + Items = C.getSValBuilder().conjureSymbolVal( + CE, C.getLocationContext(), FD->getType(), C.blockCount()); + State = State->bindLoc(ItemsLoc, Items, C.getLocationContext()); + } + assert(Items.getAsRegion()); + // The items list is now rooted + State = State->set(Items.getAsRegion(), + RootState::getRootArray(CurrentDepth)); } - if (RValState->isPotentiallyFreed()) - report_value_error(C, Sym, "Trying to root value which may have been GCed"); - if (!RValState->isRooted() || RValState->RootDepth > RootState->RootedAtDepth) { - C.addTransition(State->set(Sym, ValueState::getRooted(R, RootState->RootedAtDepth))); + C.addTransition(State); + return true; + } else if (name == "jl_ast_preserve") { + // TODO: Maybe bind the rooting to the context. For now, the second + // argument gets unconditionally rooted + ProgramStateRef State = C.getState(); + SymbolRef Sym = C.getSVal(CE->getArg(1)).getAsSymbol(); + if (!Sym) + return true; + C.addTransition( + State->set(Sym, ValueState::getRooted(nullptr, -1))); + return true; + } else if (name == "jl_gc_enable") { + ProgramStateRef State = C.getState(); + // Check for a literal argument + SVal Arg = C.getSVal(CE->getArg(0)); + auto CI = Arg.getAs(); + bool EnabledAfter = true; + if (CI) { + const llvm::APSInt &Val = CI->getValue(); + EnabledAfter = Val != 0; + } else { + cast(Arg.getAsSymbol())->getStmt()->dump(); } -} - -bool GCChecker::rootRegionIfGlobal(const MemRegion *R, ProgramStateRef &State, CheckerContext &C, ValueState *ValS) const { - if (!R) - return false; - SValExplainer Ex(C.getASTContext()); - const VarRegion *VR = R->getAs(); - if (!VR) - return false; - const VarDecl *VD = VR->getDecl(); - assert(VD); - if (!VD->hasGlobalStorage()) - return false; - if (!isGCTrackedType(VD->getType())) - return false; - bool isGlobalRoot = false; - if (declHasAnnotation(VD, "julia_globally_rooted") || - isGloballyRootedType(VD->getType())) { - State = State->set(R, RootState::getRoot(-1)); - isGlobalRoot = true; - } - SVal TheVal = State->getSVal(R); - SymbolRef Sym = TheVal.getAsSymbol(); - ValueState TheValS(isGlobalRoot ? ValueState::getRooted(R, -1) : ValueState::getAllocated()); - if (ValS) - *ValS = TheValS; - if (Sym) { - const ValueState *GVState = C.getState()->get(Sym); - if (!GVState) - State = State->set(Sym, TheValS); + bool EnabledNow = State->get() == (unsigned)-1; + if (!EnabledAfter) { + State = State->set((unsigned)-2); + } else { + State = State->set((unsigned)-1); } + // GC State is explicitly modeled, so let's make sure + // the execution matches our model + SVal Result = C.getSValBuilder().makeTruthVal(EnabledNow, CE->getType()); + C.addTransition(State->BindExpr(CE, C.getLocationContext(), Result)); return true; + } + return false; } -void GCChecker::checkLocation(SVal SLoc, bool IsLoad, const Stmt *S, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - bool DidChange = false; - const RootState *RS = nullptr; - // Loading from a root produces a rooted symbol. TODO: Can we do something better than this. - if (IsLoad && (RS = State->get(SLoc.getAsRegion()))) { - SymbolRef LoadedSym = State->getSVal(SLoc.getAs().getValue()).getAsSymbol(); - if (LoadedSym) { - const ValueState *ValS = State->get(LoadedSym); - if (!ValS || !ValS->isRooted() || ValS->RootDepth > RS->RootedAtDepth) { - DidChange = true; - State = State->set(LoadedSym, ValueState::getRooted(SLoc.getAsRegion(), - RS->RootedAtDepth)); - } - } - } - // If it's just the symbol by itself, let it be. We allow dead pointer to be - // passed around, so long as they're not accessed. However, we do want to - // start tracking any globals that may have been accessed. - if (rootRegionIfGlobal(SLoc.getAsRegion(), State, C)) { - C.addTransition(State); - return; +void GCChecker::checkBind(SVal LVal, SVal RVal, const clang::Stmt *S, + CheckerContext &C) const { + auto State = C.getState(); + const MemRegion *R = LVal.getAsRegion(); + if (!R) { + return; + } + bool shouldBeRootArray = false; + const ElementRegion *ER = R->getAs(); + if (ER) { + R = R->getBaseRegion()->StripCasts(); + shouldBeRootArray = true; + } + SymbolRef Sym = RVal.getAsSymbol(); + if (!Sym) { + return; + } + const auto *RootState = State->get(R); + if (!RootState) { + const ValueState *ValSP = nullptr; + ValueState ValS; + if (rootRegionIfGlobal(R->getBaseRegion(), State, C, &ValS)) { + ValSP = &ValS; + } else { + ValSP = getValStateForRegion(C.getASTContext(), State, R); } - SymbolRef SymByItself = SLoc.getAsSymbol(false); - if (SymByItself) { - DidChange && C.addTransition(State); - return; + if (!ValSP || !ValSP->isRooted()) { + return; } - // This will walk backwards until it finds the base symbol - SymbolRef Sym = SLoc.getAsSymbol(true); - if (!Sym) { - DidChange && C.addTransition(State); - return; + const auto *RValState = State->get(Sym); + if (RValState && RValState->isRooted() && + RValState->RootDepth < ValSP->RootDepth) + return; + C.addTransition(State->set(Sym, *ValSP)); + return; + } + if (shouldBeRootArray && !RootState->isRootArray()) { + report_error( + C, "This assignment looks weird. Expected a root array on the LHS."); + return; + } + const auto *RValState = State->get(Sym); + if (!RValState) { + if (rootRegionIfGlobal(Sym->getOriginRegion(), State, C)) { + C.addTransition(State); + return; } - const ValueState *VState = State->get(Sym); - if (!VState) { - DidChange && C.addTransition(State); - return; + Sym->dump(); + if (auto *SC = dyn_cast(Sym)) { + SC->getStmt()->dump(); } - if (VState->isPotentiallyFreed()) { - report_value_error(C, Sym, "Trying to access value which may have been GCed"); + report_value_error(C, Sym, + "Saw assignment to root, but missed the allocation"); + return; + } + if (RValState->isPotentiallyFreed()) + report_value_error(C, Sym, "Trying to root value which may have been GCed"); + if (!RValState->isRooted() || + RValState->RootDepth > RootState->RootedAtDepth) { + C.addTransition(State->set( + Sym, ValueState::getRooted(R, RootState->RootedAtDepth))); + } +} + +bool GCChecker::rootRegionIfGlobal(const MemRegion *R, ProgramStateRef &State, + CheckerContext &C, ValueState *ValS) const { + if (!R) + return false; + SValExplainer Ex(C.getASTContext()); + const VarRegion *VR = R->getAs(); + if (!VR) + return false; + const VarDecl *VD = VR->getDecl(); + assert(VD); + if (!VD->hasGlobalStorage()) + return false; + if (!isGCTrackedType(VD->getType())) + return false; + bool isGlobalRoot = false; + if (declHasAnnotation(VD, "julia_globally_rooted") || + isGloballyRootedType(VD->getType())) { + State = State->set(R, RootState::getRoot(-1)); + isGlobalRoot = true; + } + SVal TheVal = State->getSVal(R); + SymbolRef Sym = TheVal.getAsSymbol(); + ValueState TheValS(isGlobalRoot ? ValueState::getRooted(R, -1) + : ValueState::getAllocated()); + if (ValS) + *ValS = TheValS; + if (Sym) { + const ValueState *GVState = C.getState()->get(Sym); + if (!GVState) + State = State->set(Sym, TheValS); + } + return true; +} + +void GCChecker::checkLocation(SVal SLoc, bool IsLoad, const Stmt *S, + CheckerContext &C) const { + ProgramStateRef State = C.getState(); + bool DidChange = false; + const RootState *RS = nullptr; + // Loading from a root produces a rooted symbol. TODO: Can we do something + // better than this. + if (IsLoad && (RS = State->get(SLoc.getAsRegion()))) { + SymbolRef LoadedSym = + State->getSVal(SLoc.getAs().getValue()).getAsSymbol(); + if (LoadedSym) { + const ValueState *ValS = State->get(LoadedSym); + if (!ValS || !ValS->isRooted() || ValS->RootDepth > RS->RootedAtDepth) { + DidChange = true; + State = State->set( + LoadedSym, + ValueState::getRooted(SLoc.getAsRegion(), RS->RootedAtDepth)); + } } - DidChange && C.addTransition(State); + } + // If it's just the symbol by itself, let it be. We allow dead pointer to be + // passed around, so long as they're not accessed. However, we do want to + // start tracking any globals that may have been accessed. + if (rootRegionIfGlobal(SLoc.getAsRegion(), State, C)) { + C.addTransition(State); + return; + } + SymbolRef SymByItself = SLoc.getAsSymbol(false); + if (SymByItself) { + DidChange &&C.addTransition(State); + return; + } + // This will walk backwards until it finds the base symbol + SymbolRef Sym = SLoc.getAsSymbol(true); + if (!Sym) { + DidChange &&C.addTransition(State); + return; + } + const ValueState *VState = State->get(Sym); + if (!VState) { + DidChange &&C.addTransition(State); + return; + } + if (VState->isPotentiallyFreed()) { + report_value_error(C, Sym, + "Trying to access value which may have been GCed"); + } + DidChange &&C.addTransition(State); } namespace clang { namespace ento { void registerGCChecker(CheckerManager &mgr) { - mgr.registerChecker(); -} -} + mgr.registerChecker(); } +} // namespace ento +} // namespace clang #ifdef CLANG_PLUGIN extern "C" const char clang_analyzerAPIVersionString[] = CLANG_ANALYZER_API_VERSION_STRING; -extern "C" -void clang_registerCheckers (CheckerRegistry ®istry) { - registry.addChecker("julia.GCChecker", - "Validates julia gc invariants" +extern "C" void clang_registerCheckers(CheckerRegistry ®istry) { + registry.addChecker( + "julia.GCChecker", "Validates julia gc invariants" #if LLVM_VERSION_MAJOR >= 8 - ,"https://docs.julialang.org/en/v1/devdocs/gc-sa/" + , + "https://docs.julialang.org/en/v1/devdocs/gc-sa/" #endif - ); + ); } #endif From ad25da24a2a1358fe1c469c81878c37a9373edb4 Mon Sep 17 00:00:00 2001 From: Jameson Nash Date: Fri, 24 Apr 2020 15:11:56 -0400 Subject: [PATCH 3/3] [GCChecker] fix a few tests by looking through casts --- src/clangsa/GCChecker.cpp | 34 +++++++++++++++++++++------------- test/clangsa/Makefile | 2 +- test/clangsa/MissingRoots.c | 23 ++++++++++++++--------- 3 files changed, 36 insertions(+), 23 deletions(-) diff --git a/src/clangsa/GCChecker.cpp b/src/clangsa/GCChecker.cpp index 2a7ed9e84d11b..4058283ee44e4 100644 --- a/src/clangsa/GCChecker.cpp +++ b/src/clangsa/GCChecker.cpp @@ -200,6 +200,7 @@ class GCChecker const MemRegion *Region); static bool isGCTrackedType(QualType Type); + static bool isGCTracked(const Expr *E); bool isGloballyRootedType(QualType Type) const; static void dumpState(const ProgramStateRef &State); static bool declHasAnnotation(const clang::Decl *D, const char *which); @@ -703,12 +704,8 @@ void GCChecker::checkBeginFunction(CheckerContext &C) const { } } -#if LLVM_VERSION_MAJOR >= 7 void GCChecker::checkEndFunction(const clang::ReturnStmt *RS, CheckerContext &C) const { -#else -void GCChecker::checkEndFunction(CheckerContext &C) const { -#endif ProgramStateRef State = C.getState(); bool Changed = false; if (State->get() == C.getStackFrame()->getIndex()) { @@ -789,6 +786,19 @@ bool GCChecker::isGCTrackedType(QualType QT) { QT); } +bool GCChecker::isGCTracked(const Expr *E) { + while (1) { + if (isGCTrackedType(E->getType())) + return true; + if (auto ICE = dyn_cast(E)) + E = ICE->getSubExpr(); + else if (auto CE = dyn_cast(E)) + E = CE->getSubExpr(); + else + return false; + } +} + bool GCChecker::isGloballyRootedType(QualType QT) const { return isJuliaType( [](StringRef Name) { return Name.endswith_lower("jl_sym_t"); }, QT); @@ -1053,7 +1063,7 @@ SymbolRef GCChecker::getSymbolForResult(const Expr *Result, QualType QT = Result->getType(); if (!QT->isPointerType()) return nullptr; - if (OldValS || GCChecker::isGCTrackedType(QT)) { + if (OldValS || GCChecker::isGCTracked(Result)) { Loaded = C.getSValBuilder().conjureSymbolVal( nullptr, Result, C.getLocationContext(), Result->getType(), C.blockCount()); @@ -1082,7 +1092,7 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent, State->set(NewSym, ValueState::getRooted(nullptr, -1))); return; } - if (!isGCTrackedType(Result->getType())) { + if (!isGCTracked(Result)) { // TODO: We may want to refine this. This is to track pointers through the // array list in jl_module_t. bool ParentIsModule = isJuliaType( @@ -1091,8 +1101,7 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent, bool ResultIsArrayList = isJuliaType( [](StringRef Name) { return Name.endswith_lower("arraylist_t"); }, Result->getType()); - if (!(ParentIsModule && ResultIsArrayList) && - isGCTrackedType(Parent->getType())) { + if (!(ParentIsModule && ResultIsArrayList) && isGCTracked(Parent)) { ResultTracked = false; } } @@ -1144,7 +1153,7 @@ void GCChecker::checkDerivingExpr(const Expr *Result, const Expr *Parent, } if (!OldValS) { // This way we'll get better diagnostics - if (isGCTrackedType(Result->getType())) { + if (isGCTracked(Result)) { C.addTransition( State->set(NewSym, ValueState::getUntracked())); } @@ -1167,8 +1176,7 @@ void GCChecker::checkPostStmt(const ArraySubscriptExpr *ASE, const MemRegion *Region = C.getSVal(ASE->getLHS()).getAsRegion(); ProgramStateRef State = C.getState(); SValExplainer Ex(C.getASTContext()); - if (Region && Region->getAs() && - isGCTrackedType(ASE->getType())) { + if (Region && Region->getAs() && isGCTracked(ASE)) { const RootState *RS = State->get(Region->getAs()->getSuperRegion()); if (RS) { @@ -1192,7 +1200,7 @@ void GCChecker::checkPostStmt(const MemberExpr *ME, CheckerContext &C) const { // It is possible for the member itself to be gcrooted, so check that first const MemRegion *Region = C.getSVal(ME).getAsRegion(); ProgramStateRef State = C.getState(); - if (Region && isGCTrackedType(ME->getType())) { + if (Region && isGCTracked(ME)) { if (const RootState *RS = State->get(Region)) { ValueState ValS = ValueState::getRooted(Region, RS->RootedAtDepth); SymbolRef NewSym = getSymbolForResult(ME, &ValS, State, C); @@ -1274,7 +1282,7 @@ void GCChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { SourceRange range; if (const Expr *E = Call.getArgExpr(idx)) { range = E->getSourceRange(); - if (!isGCTrackedType(E->getType())) + if (!isGCTracked(E)) continue; } if (ValState->isPotentiallyFreed()) { diff --git a/test/clangsa/Makefile b/test/clangsa/Makefile index 62acb513510e3..cc630e82e1cbb 100644 --- a/test/clangsa/Makefile +++ b/test/clangsa/Makefile @@ -4,7 +4,7 @@ include $(JULIAHOME)/Make.inc check: $(SRCDIR) -TESTS = $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/*.c)) $(wildcard $(SRCDIR)/*.cpp) +TESTS = $(patsubst $(SRCDIR)/%,%,$(wildcard $(SRCDIR)/*.c) $(wildcard $(SRCDIR)/*.cpp)) $(SRCDIR) $(TESTS): PATH=$(build_bindir):$(build_depsbindir):$$PATH \ diff --git a/test/clangsa/MissingRoots.c b/test/clangsa/MissingRoots.c index 65b52d62b75bd..001d5814ba097 100644 --- a/test/clangsa/MissingRoots.c +++ b/test/clangsa/MissingRoots.c @@ -10,7 +10,7 @@ extern void process_unrooted(jl_value_t *maybe_unrooted JL_MAYBE_UNROOTED); extern void jl_gc_safepoint(); void unrooted_argument() { - look_at_value((jl_value_t*)jl_svec1(NULL)); // expected-warning{{Passing non-rooted value as argument to function}} + look_at_value((jl_value_t*)jl_svec1(NULL)); // expected-warning{{Passing non-rooted value as argument to function that may GC}} // expected-note@-1{{Passing non-rooted value as argument to function}} // expected-note@-2{{Started tracking value here}} }; @@ -22,9 +22,10 @@ void simple_svec() { } jl_value_t *simple_missing_root() { - jl_svec_t *val = jl_svec1(NULL); - jl_gc_safepoint(); - return jl_svecref(val, 0); // XXX-expected-warning{{Passing non-rooted value as argument to function}} + jl_svec_t *val = jl_svec1(NULL); // expected-note{{Started tracking value here}} + jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} + return jl_svecref(val, 0); // expected-warning{{Argument value may have been GCed}} + // expected-note@-1{{Argument value may have been GCed}} }; jl_value_t *root_value() { @@ -130,14 +131,18 @@ jl_value_t *late_root2() { jl_value_t *already_freed() { jl_svec_t *val = NULL; - JL_GC_PUSH1(&val); - val = jl_svec1(NULL); - JL_GC_POP(); - jl_gc_safepoint(); - jl_value_t *ret = jl_svecref(val, 0); + JL_GC_PUSH1(&val); // expected-note{{GC frame changed here}} + val = jl_svec1(NULL); // expected-note{{Started tracking value here}} + // expected-note@-1{{Value was rooted here}} + JL_GC_POP(); // expected-note{{GC frame changed here}} + // expected-note@-1{{Root was released here}} + jl_gc_safepoint(); // expected-note{{Value may have been GCed here}} + jl_value_t *ret = jl_svecref(val, 0); // expected-warning{{Argument value may have been GCed}} + // expected-note@-1{{Argument value may have been GCed}} return ret; }; + int field_access() { jl_svec_t *val = jl_svec1(NULL); // expected-note {{Started tracking value here}} jl_gc_safepoint(); // expected-note{{Value may have been GCed here}}