diff --git a/.clang-tidy b/.clang-tidy index 81be7c096..b0f274372 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -54,7 +54,7 @@ Checks: '*, -readability-magic-numbers, -readability-named-parameter' WarningsAsErrors: '' -HeaderFilterRegex: '' +HeaderFilterRegex: 'src/.*' AnalyzeTemporaryDtors: false FormatStyle: none CheckOptions: diff --git a/.github/workflows/diff.yaml b/.github/workflows/diff.yaml index 67be98576..4ba546412 100644 --- a/.github/workflows/diff.yaml +++ b/.github/workflows/diff.yaml @@ -66,7 +66,7 @@ jobs: path: build/output/memgraph*.deb coverage_build: - name: "Coverage build" + name: "Code analysis" runs-on: [self-hosted, General, Linux, X64, Debian10] env: THREADS: 24 @@ -79,7 +79,7 @@ jobs: # branches and tags. (default: 1) fetch-depth: 0 - - name: Build coverage binaries + - name: Build combined ASAN, UBSAN and coverage binaries run: | # Activate toolchain. source /opt/toolchain-v2/activate @@ -87,9 +87,8 @@ jobs: # Initialize dependencies. ./init - # Build coverage binaries. cd build - cmake -DTEST_COVERAGE=ON .. + cmake -DTEST_COVERAGE=ON -DASAN=ON -DUBSAN=ON .. make -j$THREADS memgraph__unit - name: Run unit tests @@ -97,9 +96,9 @@ jobs: # Activate toolchain. source /opt/toolchain-v2/activate - # Run unit tests. + # Run unit tests. It is restricted to 2 threads intentionally, because higher concurrency makes the timing related tests unstable. cd build - ctest -R memgraph__unit --output-on-failure -j$THREADS + LSAN_OPTIONS=suppressions=$PWD/../tools/lsan.supp UBSAN_OPTIONS=halt_on_error=1 ctest -R memgraph__unit --output-on-failure -j2 - name: Compute code coverage run: | @@ -120,6 +119,16 @@ jobs: name: "Code coverage" path: tools/github/generated/code_coverage.tar.gz + - name: Run clang-tidy + run: | + source /opt/toolchain-v2/activate + + # Restrict clang-tidy results only to the modified parts + git diff -U0 master... -- src | ./tools/github/clang-tidy/clang-tidy-diff.py -p 1 -j $THREADS -path build | tee ./build/clang_tidy_output.txt + + # Fail if any warning is reported + ! cat ./build/clang_tidy_output.txt | ./tools/github/clang-tidy/grep_error_lines.sh > /dev/null + debug_build: name: "Debug build" runs-on: [self-hosted, General, Linux, X64, Debian10] diff --git a/.github/workflows/full_clang_tidy.yaml b/.github/workflows/full_clang_tidy.yaml new file mode 100644 index 000000000..d1f4151ba --- /dev/null +++ b/.github/workflows/full_clang_tidy.yaml @@ -0,0 +1,44 @@ +name: Run clang-tidy on the full codebase + +on: + workflow_dispatch: + +jobs: + clang_tidy_check: + name: "Clang-tidy check" + runs-on: [self-hosted, Linux, X64, Ubuntu20.04] + env: + THREADS: 24 + + steps: + - name: Set up repository + uses: actions/checkout@v2 + with: + # Number of commits to fetch. `0` indicates all history for all + # branches and tags. (default: 1) + fetch-depth: 0 + + - name: Build debug binaries + run: | + # Activate toolchain. + source /opt/toolchain-v2/activate + + # Initialize dependencies. + ./init + + # Build debug binaries. + + cd build + cmake .. + make -j$THREADS + + - name: Run clang-tidy + run: | + source /opt/toolchain-v2/activate + + # The results are also written to standard output in order to retain them in the logs + ./tools/github/clang-tidy/run-clang-tidy.py -p build -j $THREADS -clang-tidy-binary=/opt/toolchain-v2/bin/clang-tidy "$PWD/src/*" | + tee ./build/full_clang_tidy_output.txt + + - name: Summarize clang-tidy results + run: cat ./build/full_clang_tidy_output.txt | ./tools/github/clang-tidy/count_errors.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 663eba656..5a0d3310f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -312,8 +312,9 @@ if (UBSAN) # runtime library and c++ standard libraries are present. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=undefined -fno-omit-frame-pointer -fno-sanitize=vptr") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined -fno-sanitize=vptr") - # Run program with environment variable UBSAN_OPTIONS=print_stacktrace=1 - # Make sure llvm-symbolizer binary is in path + # Run program with environment variable UBSAN_OPTIONS=print_stacktrace=1. + # Make sure llvm-symbolizer binary is in path. + # To make the program abort on undefined behavior, use UBSAN_OPTIONS=halt_on_error=1. endif() set(MG_PYTHON_VERSION "" CACHE STRING "Specify the exact python version used by the query modules") diff --git a/src/query/interpreter.cpp b/src/query/interpreter.cpp index 62391e218..54cafbd53 100644 --- a/src/query/interpreter.cpp +++ b/src/query/interpreter.cpp @@ -1122,6 +1122,8 @@ PreparedQuery PrepareReplicationQuery(ParsedQuery parsed_query, const bool in_ex return std::nullopt; }, RWType::NONE}; + // False positive report for the std::make_shared above + // NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks) } PreparedQuery PrepareLockPathQuery(ParsedQuery parsed_query, const bool in_explicit_transaction, @@ -1434,10 +1436,12 @@ Interpreter::PrepareResult Interpreter::Prepare(const std::string &query_string, in_explicit_transaction_ ? static_cast(query_executions_.size() - 1) : std::optional{}; // Handle transaction control queries. - auto query_upper = utils::Trim(utils::ToUpperCase(query_string)); - if (query_upper == "BEGIN" || query_upper == "COMMIT" || query_upper == "ROLLBACK") { - query_execution->prepared_query.emplace(PrepareTransactionQuery(query_upper)); + const auto upper_case_query = utils::ToUpperCase(query_string); + const auto trimmed_query = utils::Trim(upper_case_query); + + if (trimmed_query == "BEGIN" || trimmed_query == "COMMIT" || trimmed_query == "ROLLBACK") { + query_execution->prepared_query.emplace(PrepareTransactionQuery(trimmed_query)); return {query_execution->prepared_query->header, query_execution->prepared_query->privileges, qid}; } diff --git a/src/utils/memory.cpp b/src/utils/memory.cpp index 7acffa5c5..fe7e6b4d8 100644 --- a/src/utils/memory.cpp +++ b/src/utils/memory.cpp @@ -23,6 +23,9 @@ size_t GrowMonotonicBuffer(size_t current_size, size_t max_size) { return std::ceil(next_size); } +__attribute__((no_sanitize("pointer-overflow"))) void CheckAllocationSizeOverflow(void *aligned_ptr, size_t bytes) { + if (reinterpret_cast(aligned_ptr) + bytes <= aligned_ptr) throw BadAlloc("Allocation size overflow"); +} } // namespace MonotonicBufferResource::MonotonicBufferResource(size_t initial_size) : initial_size_(initial_size) {} @@ -121,7 +124,7 @@ void *MonotonicBufferResource::DoAllocate(size_t bytes, size_t alignment) { next_buffer_size_ = GrowMonotonicBuffer(next_buffer_size_, std::numeric_limits::max() - sizeof(Buffer)); } if (reinterpret_cast(aligned_ptr) < buffer_head) throw BadAlloc("Allocation alignment overflow"); - if (reinterpret_cast(aligned_ptr) + bytes <= aligned_ptr) throw BadAlloc("Allocation size overflow"); + CheckAllocationSizeOverflow(aligned_ptr, bytes); allocated_ = reinterpret_cast(aligned_ptr) - data + bytes; return aligned_ptr; } diff --git a/tests/unit/bolt_session.cpp b/tests/unit/bolt_session.cpp index c9c8631d4..6bee34273 100644 --- a/tests/unit/bolt_session.cpp +++ b/tests/unit/bolt_session.cpp @@ -383,15 +383,16 @@ TEST(BoltSession, ExecuteRunWrongMarker) { } TEST(BoltSession, ExecuteRunMissingData) { + std::array run_req_without_parameters{ + run_req_header[0], run_req_header[1], run_req_header[2], 0x00, 0x00, 0x00}; // test lengths, they test the following situations: // missing header data, missing query data, missing parameters - int len[] = {1, 2, 37}; - + int len[] = {1, 2, run_req_without_parameters.size()}; for (int i = 0; i < 3; ++i) { INIT_VARS; ExecuteHandshake(input_stream, session, output); ExecuteInit(input_stream, session, output); - ASSERT_THROW(ExecuteCommand(input_stream, session, run_req_header, len[i]), SessionException); + ASSERT_THROW(ExecuteCommand(input_stream, session, run_req_without_parameters.data(), len[i]), SessionException); ASSERT_EQ(session.state_, State::Close); CheckFailureMessage(output); @@ -871,7 +872,7 @@ TEST(BoltSession, Noop) { CheckFailureMessage(output); session.state_ = State::Result; - ExecuteCommand(input_stream, session, pullall_req, sizeof(v4::pullall_req)); + ExecuteCommand(input_stream, session, pullall_req, sizeof(pullall_req)); CheckSuccessMessage(output); ASSERT_THROW(ExecuteCommand(input_stream, session, v4_1::noop, sizeof(v4_1::noop)), SessionException); diff --git a/tests/unit/query_plan.cpp b/tests/unit/query_plan.cpp index d15efc0af..f15cd0055 100644 --- a/tests/unit/query_plan.cpp +++ b/tests/unit/query_plan.cpp @@ -73,6 +73,12 @@ class TestPlanner : public ::testing::Test {}; using PlannerTypes = ::testing::Types; +void DeleteListContent(std::list *list) { + for (BaseOpChecker *ptr : *list) { + delete ptr; + } +} + TYPED_TEST_CASE(TestPlanner, PlannerTypes); TYPED_TEST(TestPlanner, MatchNodeReturn) { @@ -223,6 +229,7 @@ TYPED_TEST(TestPlanner, OptionalMatchNamedPatternReturn) { auto planner = MakePlanner(&dba, storage, symbol_table, query); std::list optional{new ExpectScanAll(), new ExpectExpand(), new ExpectConstructNamedPath()}; CheckPlan(planner.plan(), symbol_table, ExpectOptional(optional_symbols, optional), ExpectProduce()); + DeleteListContent(&optional); } TYPED_TEST(TestPlanner, MatchWhereReturn) { @@ -549,10 +556,8 @@ TYPED_TEST(TestPlanner, MatchMerge) { auto acc = ExpectAccumulate({symbol_table.at(*ident_n)}); auto planner = MakePlanner(&dba, storage, symbol_table, query); CheckPlan(planner.plan(), symbol_table, ExpectScanAll(), ExpectMerge(on_match, on_create), acc, ExpectProduce()); - for (auto &op : on_match) delete op; - on_match.clear(); - for (auto &op : on_create) delete op; - on_create.clear(); + DeleteListContent(&on_match); + DeleteListContent(&on_create); } TYPED_TEST(TestPlanner, MatchOptionalMatchWhereReturn) { @@ -564,6 +569,7 @@ TYPED_TEST(TestPlanner, MatchOptionalMatchWhereReturn) { WHERE(LESS(PROPERTY_LOOKUP("m", prop), LITERAL(42))), RETURN("r"))); std::list optional{new ExpectScanAll(), new ExpectExpand(), new ExpectFilter()}; CheckPlan(query, storage, ExpectScanAll(), ExpectOptional(optional), ExpectProduce()); + DeleteListContent(&optional); } TYPED_TEST(TestPlanner, MatchUnwindReturn) { @@ -705,6 +711,7 @@ TYPED_TEST(TestPlanner, MatchOptionalMatchWhere) { // optional ScanAll. std::list optional{new ExpectFilter(), new ExpectScanAll()}; CheckPlan(query, storage, ExpectScanAll(), ExpectExpand(), ExpectOptional(optional), ExpectProduce()); + DeleteListContent(&optional); } TYPED_TEST(TestPlanner, MatchReturnAsterisk) { @@ -763,8 +770,8 @@ TYPED_TEST(TestPlanner, UnwindMergeNodeProperty) { std::list on_match{new ExpectScanAll(), new ExpectFilter()}; std::list on_create{new ExpectCreateNode()}; CheckPlan(query, storage, ExpectUnwind(), ExpectMerge(on_match, on_create)); - for (auto &op : on_match) delete op; - for (auto &op : on_create) delete op; + DeleteListContent(&on_match); + DeleteListContent(&on_create); } TYPED_TEST(TestPlanner, MultipleOptionalMatchReturn) { @@ -774,6 +781,7 @@ TYPED_TEST(TestPlanner, MultipleOptionalMatchReturn) { QUERY(SINGLE_QUERY(OPTIONAL_MATCH(PATTERN(NODE("n"))), OPTIONAL_MATCH(PATTERN(NODE("m"))), RETURN("n"))); std::list optional{new ExpectScanAll()}; CheckPlan(query, storage, ExpectOptional(optional), ExpectOptional(optional), ExpectProduce()); + DeleteListContent(&optional); } TYPED_TEST(TestPlanner, FunctionAggregationReturn) { diff --git a/tests/unit/query_procedure_mgp_module.cpp b/tests/unit/query_procedure_mgp_module.cpp index 3da92bb5c..74e2da106 100644 --- a/tests/unit/query_procedure_mgp_module.cpp +++ b/tests/unit/query_procedure_mgp_module.cpp @@ -5,6 +5,8 @@ #include "query/procedure/mg_procedure_impl.hpp" +#include "test_utils.hpp" + static void DummyCallback(const mgp_list *, const mgp_graph *, mgp_result *, mgp_memory *) {} TEST(Module, InvalidProcedureRegistration) { @@ -53,7 +55,8 @@ TEST(Module, ProcedureSignature) { CheckSignature(proc, "proc() :: ()"); mgp_proc_add_arg(proc, "arg1", mgp_type_number()); CheckSignature(proc, "proc(arg1 :: NUMBER) :: ()"); - mgp_proc_add_opt_arg(proc, "opt1", mgp_type_nullable(mgp_type_any()), mgp_value_make_null(&memory)); + mgp_proc_add_opt_arg(proc, "opt1", mgp_type_nullable(mgp_type_any()), + test_utils::CreateValueOwningPtr(mgp_value_make_null(&memory)).get()); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: ()"); mgp_proc_add_result(proc, "res1", mgp_type_list(mgp_type_int())); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?) :: (res1 :: LIST OF INTEGER)"); @@ -69,7 +72,8 @@ TEST(Module, ProcedureSignature) { "(res1 :: LIST OF INTEGER, DEPRECATED res2 :: STRING)"); EXPECT_FALSE(mgp_proc_add_result(proc, "res2", mgp_type_any())); EXPECT_FALSE(mgp_proc_add_deprecated_result(proc, "res1", mgp_type_any())); - mgp_proc_add_opt_arg(proc, "opt2", mgp_type_string(), mgp_value_make_string("string=\"value\"", &memory)); + mgp_proc_add_opt_arg(proc, "opt2", mgp_type_string(), + test_utils::CreateValueOwningPtr(mgp_value_make_string("string=\"value\"", &memory)).get()); CheckSignature(proc, "proc(arg1 :: NUMBER, opt1 = Null :: ANY?, " "opt2 = \"string=\\\"value\\\"\" :: STRING) :: " @@ -80,6 +84,7 @@ TEST(Module, ProcedureSignatureOnlyOptArg) { mgp_memory memory{utils::NewDeleteResource()}; mgp_module module(utils::NewDeleteResource()); auto *proc = mgp_module_add_read_procedure(&module, "proc", DummyCallback); - mgp_proc_add_opt_arg(proc, "opt1", mgp_type_nullable(mgp_type_any()), mgp_value_make_null(&memory)); + mgp_proc_add_opt_arg(proc, "opt1", mgp_type_nullable(mgp_type_any()), + test_utils::CreateValueOwningPtr(mgp_value_make_null(&memory)).get()); CheckSignature(proc, "proc(opt1 = Null :: ANY?) :: ()"); } diff --git a/tests/unit/query_procedure_mgp_type.cpp b/tests/unit/query_procedure_mgp_type.cpp index 2cb1b0ce4..588a35a85 100644 --- a/tests/unit/query_procedure_mgp_type.cpp +++ b/tests/unit/query_procedure_mgp_type.cpp @@ -1,7 +1,13 @@ +#include +#include +#include + #include #include "query/procedure/mg_procedure_impl.hpp" +#include "test_utils.hpp" + TEST(CypherType, PresentableNameSimpleTypes) { EXPECT_EQ(mgp_type_any()->impl->GetPresentableName(), "ANY"); EXPECT_EQ(mgp_type_bool()->impl->GetPresentableName(), "BOOLEAN"); @@ -66,6 +72,7 @@ TEST(CypherType, NullSatisfiesType) { EXPECT_TRUE(null_type->impl->SatisfiesType(tv_null)); } } + mgp_value_destroy(mgp_null); } } @@ -101,6 +108,7 @@ TEST(CypherType, BoolSatisfiesType) { CheckNotSatisfiesTypesAndListAndNullable(mgp_bool, tv_bool, {mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + mgp_value_destroy(mgp_bool); } TEST(CypherType, IntSatisfiesType) { @@ -111,6 +119,7 @@ TEST(CypherType, IntSatisfiesType) { CheckNotSatisfiesTypesAndListAndNullable(mgp_int, tv_int, {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + mgp_value_destroy(mgp_int); } TEST(CypherType, DoubleSatisfiesType) { @@ -121,6 +130,7 @@ TEST(CypherType, DoubleSatisfiesType) { CheckNotSatisfiesTypesAndListAndNullable(mgp_double, tv_double, {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + mgp_value_destroy(mgp_double); } TEST(CypherType, StringSatisfiesType) { @@ -131,12 +141,13 @@ TEST(CypherType, StringSatisfiesType) { CheckNotSatisfiesTypesAndListAndNullable(mgp_string, tv_string, {mgp_type_bool(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + mgp_value_destroy(mgp_string); } TEST(CypherType, MapSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; auto *map = mgp_map_make_empty(&memory); - mgp_map_insert(map, "key", mgp_value_make_int(42, &memory)); + mgp_map_insert(map, "key", test_utils::CreateValueOwningPtr(mgp_value_make_int(42, &memory)).get()); auto *mgp_map_v = mgp_value_make_map(map); const query::TypedValue tv_map(std::map{{"key", query::TypedValue(42)}}); CheckSatisfiesTypesAndNullable(mgp_map_v, tv_map, {mgp_type_any(), mgp_type_map()}); @@ -144,6 +155,7 @@ TEST(CypherType, MapSatisfiesType) { mgp_map_v, tv_map, {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + mgp_value_destroy(mgp_map_v); } TEST(CypherType, VertexSatisfiesType) { @@ -160,6 +172,7 @@ TEST(CypherType, VertexSatisfiesType) { CheckNotSatisfiesTypesAndListAndNullable(mgp_vertex_v, tv_vertex, {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_relationship(), mgp_type_path()}); + mgp_value_destroy(mgp_vertex_v); } TEST(CypherType, EdgeSatisfiesType) { @@ -178,6 +191,7 @@ TEST(CypherType, EdgeSatisfiesType) { CheckNotSatisfiesTypesAndListAndNullable(mgp_edge_v, tv_edge, {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_node(), mgp_type_path()}); + mgp_value_destroy(mgp_edge_v); } TEST(CypherType, PathSatisfiesType) { @@ -190,9 +204,13 @@ TEST(CypherType, PathSatisfiesType) { mgp_memory memory{utils::NewDeleteResource()}; utils::Allocator alloc(memory.impl); mgp_graph graph{&dba, storage::View::NEW}; - auto *path = mgp_path_make_with_start(alloc.new_object(v1, &graph), &memory); + auto *mgp_vertex_v = alloc.new_object(v1, &graph); + auto path = mgp_path_make_with_start(mgp_vertex_v, &memory); ASSERT_TRUE(path); - ASSERT_TRUE(mgp_path_expand(path, alloc.new_object(edge, &graph))); + alloc.delete_object(mgp_vertex_v); + auto mgp_edge_v = alloc.new_object(edge, &graph); + ASSERT_TRUE(mgp_path_expand(path, mgp_edge_v)); + alloc.delete_object(mgp_edge_v); auto *mgp_path_v = mgp_value_make_path(path); const query::TypedValue tv_path(query::Path(v1, edge, v2)); CheckSatisfiesTypesAndNullable(mgp_path_v, tv_path, {mgp_type_any(), mgp_type_path()}); @@ -200,6 +218,7 @@ TEST(CypherType, PathSatisfiesType) { mgp_path_v, tv_path, {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_map(), mgp_type_node(), mgp_type_relationship()}); + mgp_value_destroy(mgp_path_v); } static std::vector MakeListTypes(const std::vector &element_types) { @@ -224,6 +243,7 @@ TEST(CypherType, EmptyListSatisfiesType) { auto all_types = MakeListTypes(primitive_types); all_types.push_back(mgp_type_any()); CheckSatisfiesTypesAndNullable(mgp_list_v, tv_list, all_types); + mgp_value_destroy(mgp_list_v); } TEST(CypherType, ListOfIntSatisfiesType) { @@ -233,7 +253,7 @@ TEST(CypherType, ListOfIntSatisfiesType) { auto *mgp_list_v = mgp_value_make_list(list); query::TypedValue tv_list(std::vector{}); for (int64_t i = 0; i < elem_count; ++i) { - ASSERT_TRUE(mgp_list_append(list, mgp_value_make_int(i, &memory))); + ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_int(i, &memory)).get())); tv_list.ValueList().emplace_back(i); auto valid_types = MakeListTypes({mgp_type_any(), mgp_type_int(), mgp_type_number()}); valid_types.push_back(mgp_type_any()); @@ -242,6 +262,7 @@ TEST(CypherType, ListOfIntSatisfiesType) { {mgp_type_bool(), mgp_type_string(), mgp_type_float(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); } + mgp_value_destroy(mgp_list_v); } TEST(CypherType, ListOfIntAndBoolSatisfiesType) { @@ -251,10 +272,10 @@ TEST(CypherType, ListOfIntAndBoolSatisfiesType) { auto *mgp_list_v = mgp_value_make_list(list); query::TypedValue tv_list(std::vector{}); // Add an int - ASSERT_TRUE(mgp_list_append(list, mgp_value_make_int(42, &memory))); + ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_int(42, &memory)).get())); tv_list.ValueList().emplace_back(42); // Add a boolean - ASSERT_TRUE(mgp_list_append(list, mgp_value_make_bool(1, &memory))); + ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_bool(1, &memory)).get())); tv_list.ValueList().emplace_back(true); auto valid_types = MakeListTypes({mgp_type_any()}); valid_types.push_back(mgp_type_any()); @@ -264,6 +285,7 @@ TEST(CypherType, ListOfIntAndBoolSatisfiesType) { mgp_list_v, tv_list, {mgp_type_bool(), mgp_type_string(), mgp_type_int(), mgp_type_float(), mgp_type_number(), mgp_type_map(), mgp_type_node(), mgp_type_relationship(), mgp_type_path()}); + mgp_value_destroy(mgp_list_v); } TEST(CypherType, ListOfNullSatisfiesType) { @@ -271,7 +293,7 @@ TEST(CypherType, ListOfNullSatisfiesType) { auto *list = mgp_list_make_empty(1, &memory); auto *mgp_list_v = mgp_value_make_list(list); query::TypedValue tv_list(std::vector{}); - ASSERT_TRUE(mgp_list_append(list, mgp_value_make_null(&memory))); + ASSERT_TRUE(mgp_list_append(list, test_utils::CreateValueOwningPtr(mgp_value_make_null(&memory)).get())); tv_list.ValueList().emplace_back(); // List with Null satisfies all nullable list element types std::vector primitive_types{ @@ -295,4 +317,5 @@ TEST(CypherType, ListOfNullSatisfiesType) { EXPECT_FALSE(null_type->impl->SatisfiesType(*mgp_list_v)) << null_type->impl->GetPresentableName(); EXPECT_FALSE(null_type->impl->SatisfiesType(tv_list)); } + mgp_value_destroy(mgp_list_v); } diff --git a/tests/unit/query_procedure_py_module.cpp b/tests/unit/query_procedure_py_module.cpp index ccc630842..95a7af2a1 100644 --- a/tests/unit/query_procedure_py_module.cpp +++ b/tests/unit/query_procedure_py_module.cpp @@ -254,6 +254,7 @@ TEST(PyModule, PyObjectToMgpValue) { const mgp_value *v2 = mgp_map_at(map, "four"); ASSERT_TRUE(mgp_value_is_double(v2)); EXPECT_EQ(mgp_value_get_double(v2), 4.0); + mgp_value_destroy(value); } int main(int argc, char **argv) { diff --git a/tests/unit/storage_v2_durability.cpp b/tests/unit/storage_v2_durability.cpp index c6c8a883e..dfe84f7ce 100644 --- a/tests/unit/storage_v2_durability.cpp +++ b/tests/unit/storage_v2_durability.cpp @@ -709,14 +709,15 @@ TEST_P(DurabilityTest, SnapshotFallback) { {.items = {.properties_on_edges = GetParam()}, .durability = {.storage_directory = storage_directory, .snapshot_wal_mode = storage::Config::Durability::SnapshotWalMode::PERIODIC_SNAPSHOT, - .snapshot_interval = std::chrono::milliseconds(2000)}}); + .snapshot_interval = std::chrono::milliseconds(3000)}}); CreateBaseDataset(&store, GetParam()); - std::this_thread::sleep_for(std::chrono::milliseconds(2500)); + std::this_thread::sleep_for(std::chrono::milliseconds(3500)); + ASSERT_EQ(GetSnapshotsList().size(), 1); CreateExtendedDataset(&store); - std::this_thread::sleep_for(std::chrono::milliseconds(2500)); + std::this_thread::sleep_for(std::chrono::milliseconds(3000)); } - ASSERT_GE(GetSnapshotsList().size(), 2); + ASSERT_EQ(GetSnapshotsList().size(), 2); ASSERT_EQ(GetBackupSnapshotsList().size(), 0); ASSERT_EQ(GetWalsList().size(), 0); ASSERT_EQ(GetBackupWalsList().size(), 0); @@ -724,7 +725,7 @@ TEST_P(DurabilityTest, SnapshotFallback) { // Destroy last snapshot. { auto snapshots = GetSnapshotsList(); - ASSERT_GE(snapshots.size(), 2); + ASSERT_EQ(snapshots.size(), 2); DestroySnapshot(*snapshots.begin()); } diff --git a/tests/unit/test_utils.hpp b/tests/unit/test_utils.hpp new file mode 100644 index 000000000..4efc0f4b2 --- /dev/null +++ b/tests/unit/test_utils.hpp @@ -0,0 +1,9 @@ +#include + +#include "query/procedure/mg_procedure_impl.hpp" + +namespace test_utils { +using MgpValueOwningPtr = std::unique_ptr; + +MgpValueOwningPtr CreateValueOwningPtr(mgp_value *value) { return MgpValueOwningPtr(value, &mgp_value_destroy); } +} // namespace test_utils diff --git a/tests/unit/typed_value.cpp b/tests/unit/typed_value.cpp index 9d848cde7..ffc28262a 100644 --- a/tests/unit/typed_value.cpp +++ b/tests/unit/typed_value.cpp @@ -397,8 +397,8 @@ TEST_F(TypedValueLogicTest, LogicalXor) { // NOLINTNEXTLINE(hicpp-special-member-functions) TEST_F(AllTypesFixture, ConstructionWithMemoryResource) { - std::vector values_with_custom_memory; utils::MonotonicBufferResource monotonic_memory(1024); + std::vector values_with_custom_memory; for (const auto &value : values_) { EXPECT_EQ(value.GetMemoryResource(), utils::NewDeleteResource()); TypedValue copy_constructed_value(value, &monotonic_memory); diff --git a/tests/unit/utils_memory.cpp b/tests/unit/utils_memory.cpp index adeb9e94a..04e3b0631 100644 --- a/tests/unit/utils_memory.cpp +++ b/tests/unit/utils_memory.cpp @@ -12,6 +12,7 @@ class TestMemory final : public utils::MemoryResource { size_t delete_count_{0}; private: + static constexpr size_t kPadSize = 32; void *DoAllocate(size_t bytes, size_t alignment) override { new_count_++; EXPECT_TRUE(alignment != 0U && (alignment & (alignment - 1U)) == 0U) << "Alignment must be power of 2"; @@ -20,11 +21,11 @@ class TestMemory final : public utils::MemoryResource { EXPECT_TRUE(bytes + pad_size > bytes) << "TestMemory size overflow"; EXPECT_TRUE(bytes + pad_size + alignment > bytes + alignment) << "TestMemory size overflow"; EXPECT_TRUE(2U * alignment > alignment) << "TestMemory alignment overflow"; - // Allocate a block containing extra alignment and pad_size bytes, but + // Allocate a block containing extra alignment and kPadSize bytes, but // aligned to 2 * alignment. Then we can offset the ptr so that it's never // aligned to 2 * alignment. This ought to make allocator alignment issues // more obvious. - void *ptr = utils::NewDeleteResource()->Allocate(alignment + bytes + pad_size, 2U * alignment); + void *ptr = utils::NewDeleteResource()->Allocate(alignment + bytes + kPadSize, 2U * alignment); // Clear allocated memory to 0xFF, marking the invalid region. memset(ptr, 0xFF, alignment + bytes + pad_size); // Offset the ptr so it's not aligned to 2 * alignment, but still aligned to @@ -39,7 +40,8 @@ class TestMemory final : public utils::MemoryResource { void DoDeallocate(void *ptr, size_t bytes, size_t alignment) override { delete_count_++; // Deallocate the original ptr, before alignment adjustment. - return utils::NewDeleteResource()->Deallocate(static_cast(ptr) - alignment, bytes, alignment); + return utils::NewDeleteResource()->Deallocate(static_cast(ptr) - alignment, alignment + bytes + kPadSize, + 2U * alignment); } bool DoIsEqual(const utils::MemoryResource &other) const noexcept override { return this == &other; } diff --git a/tools/github/clang-tidy/clang-tidy-diff.py b/tools/github/clang-tidy/clang-tidy-diff.py new file mode 100755 index 000000000..a20b1f1f4 --- /dev/null +++ b/tools/github/clang-tidy/clang-tidy-diff.py @@ -0,0 +1,269 @@ +#!/usr/bin/env python3 +# +#===- clang-tidy-diff.py - ClangTidy Diff Checker -----------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===-----------------------------------------------------------------------===# + +r""" +ClangTidy Diff Checker +====================== + +This script reads input from a unified diff, runs clang-tidy on all changed +files and outputs clang-tidy warnings in changed lines only. This is useful to +detect clang-tidy regressions in the lines touched by a specific patch. +Example usage for git/svn users: + + git diff -U0 HEAD^ | clang-tidy-diff.py -p1 + svn diff --diff-cmd=diff -x-U0 | \ + clang-tidy-diff.py -fix -checks=-*,modernize-use-override + +""" + +import argparse +import glob +import json +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import traceback + +try: + import yaml +except ImportError: + yaml = None + +is_py2 = sys.version[0] == '2' + +if is_py2: + import Queue as queue +else: + import queue as queue + + +def run_tidy(task_queue, lock, timeout): + watchdog = None + while True: + command = task_queue.get() + try: + proc = subprocess.Popen(command, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + + if timeout is not None: + watchdog = threading.Timer(timeout, proc.kill) + watchdog.start() + + stdout, stderr = proc.communicate() + + with lock: + sys.stdout.write(stdout.decode('utf-8') + '\n') + sys.stdout.flush() + if stderr: + sys.stderr.write(stderr.decode('utf-8') + '\n') + sys.stderr.flush() + except Exception as e: + with lock: + sys.stderr.write('Failed: ' + str(e) + ': '.join(command) + '\n') + finally: + with lock: + if not (timeout is None or watchdog is None): + if not watchdog.is_alive(): + sys.stderr.write('Terminated by timeout: ' + + ' '.join(command) + '\n') + watchdog.cancel() + task_queue.task_done() + + +def start_workers(max_tasks, tidy_caller, task_queue, lock, timeout): + for _ in range(max_tasks): + t = threading.Thread(target=tidy_caller, args=(task_queue, lock, timeout)) + t.daemon = True + t.start() + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged = [] + for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')): + content = yaml.safe_load(open(replacefile, 'r')) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {'MainSourceFile': '', mergekey: merged} + with open(mergefile, 'w') as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, 'w').close() + + +def main(): + parser = argparse.ArgumentParser(description= + 'Run clang-tidy against changed files, and ' + 'output diagnostics only for modified ' + 'lines.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + default='clang-tidy', + help='path to clang-tidy binary') + parser.add_argument('-p', metavar='NUM', default=0, + help='strip the smallest prefix containing P slashes') + parser.add_argument('-regex', metavar='PATTERN', default=None, + help='custom pattern selecting file paths to check ' + '(case sensitive, overrides -iregex)') + parser.add_argument('-iregex', metavar='PATTERN', default= + r'.*\.(cpp|cc|c\+\+|cxx|c|cl|h|hpp|m|mm|inc)', + help='custom pattern selecting file paths to check ' + '(case insensitive, overridden by -regex)') + parser.add_argument('-j', type=int, default=1, + help='number of tidy instances to be run in parallel.') + parser.add_argument('-timeout', type=int, default=None, + help='timeout per each file in seconds.') + parser.add_argument('-fix', action='store_true', default=False, + help='apply suggested fixes') + parser.add_argument('-checks', + help='checks filter, when not specified, use clang-tidy ' + 'default', + default='') + parser.add_argument('-path', dest='build_path', + help='Path used to read a compile command database.') + if yaml: + parser.add_argument('-export-fixes', metavar='FILE', dest='export_fixes', + help='Create a yaml file to store suggested fixes in, ' + 'which can be applied with clang-apply-replacements.') + parser.add_argument('-extra-arg', dest='extra_arg', + action='append', default=[], + help='Additional argument to append to the compiler ' + 'command line.') + parser.add_argument('-extra-arg-before', dest='extra_arg_before', + action='append', default=[], + help='Additional argument to prepend to the compiler ' + 'command line.') + parser.add_argument('-quiet', action='store_true', default=False, + help='Run clang-tidy in quiet mode') + clang_tidy_args = [] + argv = sys.argv[1:] + if '--' in argv: + clang_tidy_args.extend(argv[argv.index('--'):]) + argv = argv[:argv.index('--')] + + args = parser.parse_args(argv) + + # Extract changed lines for each file. + filename = None + lines_by_file = {} + for line in sys.stdin: + match = re.search('^\+\+\+\ \"?(.*?/){%s}([^ \t\n\"]*)' % args.p, line) + if match: + filename = match.group(2) + if filename is None: + continue + + if args.regex is not None: + if not re.match('^%s$' % args.regex, filename): + continue + else: + if not re.match('^%s$' % args.iregex, filename, re.IGNORECASE): + continue + + match = re.search('^@@.*\+(\d+)(,(\d+))?', line) + if match: + start_line = int(match.group(1)) + line_count = 1 + if match.group(3): + line_count = int(match.group(3)) + if line_count == 0: + continue + end_line = start_line + line_count - 1 + lines_by_file.setdefault(filename, []).append([start_line, end_line]) + + if not any(lines_by_file): + print("No relevant changes found.") + sys.exit(0) + + max_task_count = args.j + if max_task_count == 0: + max_task_count = multiprocessing.cpu_count() + max_task_count = min(len(lines_by_file), max_task_count) + + tmpdir = None + if yaml and args.export_fixes: + tmpdir = tempfile.mkdtemp() + + # Tasks for clang-tidy. + task_queue = queue.Queue(max_task_count) + # A lock for console output. + lock = threading.Lock() + + # Run a pool of clang-tidy workers. + start_workers(max_task_count, run_tidy, task_queue, lock, args.timeout) + + # Form the common args list. + common_clang_tidy_args = [] + if args.fix: + common_clang_tidy_args.append('-fix') + if args.checks != '': + common_clang_tidy_args.append('-checks=' + args.checks) + if args.quiet: + common_clang_tidy_args.append('-quiet') + if args.build_path is not None: + common_clang_tidy_args.append('-p=%s' % args.build_path) + for arg in args.extra_arg: + common_clang_tidy_args.append('-extra-arg=%s' % arg) + for arg in args.extra_arg_before: + common_clang_tidy_args.append('-extra-arg-before=%s' % arg) + + for name in lines_by_file: + line_filter_json = json.dumps( + [{"name": name, "lines": lines_by_file[name]}], + separators=(',', ':')) + + # Run clang-tidy on files containing changes. + command = [args.clang_tidy_binary] + command.append('-line-filter=' + line_filter_json) + if yaml and args.export_fixes: + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, tmp_name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + os.close(handle) + command.append('-export-fixes=' + tmp_name) + command.extend(common_clang_tidy_args) + command.append(name) + command.extend(clang_tidy_args) + + task_queue.put(command) + + # Wait for all threads to be done. + task_queue.join() + + if yaml and args.export_fixes: + print('Writing fixes to ' + args.export_fixes + ' ...') + try: + merge_replacement_files(tmpdir, args.export_fixes) + except: + sys.stderr.write('Error exporting fixes.\n') + traceback.print_exc() + + if tmpdir: + shutil.rmtree(tmpdir) + + +if __name__ == '__main__': + main() diff --git a/tools/github/clang-tidy/count_errors.sh b/tools/github/clang-tidy/count_errors.sh new file mode 100755 index 000000000..4237099eb --- /dev/null +++ b/tools/github/clang-tidy/count_errors.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +# the first sort | uniq is necessary, because the same occurrence of the same error +# can be reported from headers when they are included in multiple source files +`dirname ${BASH_SOURCE[0]}`/grep_error_lines.sh | + sort | uniq | + sed -E 's/.*\[(.*)\]\r?$/\1/g' | # extract the check name from [check-name] + sort | uniq -c | # count each type of check + sort -nr # sort them into descending order diff --git a/tools/github/clang-tidy/grep_error_lines.sh b/tools/github/clang-tidy/grep_error_lines.sh new file mode 100755 index 000000000..3dd0fd7f0 --- /dev/null +++ b/tools/github/clang-tidy/grep_error_lines.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +# Matches timestamp like "2021-03-25T17:06:42.2621697Z" +TIMESTAMP_PATTERN="\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{7}Z" + +# Matches absolute file pathes with line and column identifier like +# "/opt/actions-runner/_work/memgraph/memgraph/src/utils/exceptions.hpp:71:11:" +FILE_ABSOLUTE_PATH_PATTERN="/[^:]+:\d+:\d+:" + +ERROR_OR_WARNING_PATTERN="(error|warning):" + +grep -P "^($TIMESTAMP_PATTERN )?$FILE_ABSOLUTE_PATH_PATTERN $ERROR_OR_WARNING_PATTERN.*$" \ No newline at end of file diff --git a/tools/github/clang-tidy/run-clang-tidy.py b/tools/github/clang-tidy/run-clang-tidy.py new file mode 100755 index 000000000..0dbac0b25 --- /dev/null +++ b/tools/github/clang-tidy/run-clang-tidy.py @@ -0,0 +1,337 @@ +#!/usr/bin/env python3 +# +#===- run-clang-tidy.py - Parallel clang-tidy runner --------*- python -*--===# +# +# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +# See https://llvm.org/LICENSE.txt for license information. +# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +# +#===-----------------------------------------------------------------------===# +# FIXME: Integrate with clang-tidy-diff.py + + +""" +Parallel clang-tidy runner +========================== + +Runs clang-tidy over all files in a compilation database. Requires clang-tidy +and clang-apply-replacements in $PATH. + +Example invocations. +- Run clang-tidy on all files in the current working directory with a default + set of checks and show warnings in the cpp files and all project headers. + run-clang-tidy.py $PWD + +- Fix all header guards. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard + +- Fix all header guards included from clang-tidy and header guards + for clang-tidy headers. + run-clang-tidy.py -fix -checks=-*,llvm-header-guard extra/clang-tidy \ + -header-filter=extra/clang-tidy + +Compilation database setup: +http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html +""" + +from __future__ import print_function + +import argparse +import glob +import json +import multiprocessing +import os +import re +import shutil +import subprocess +import sys +import tempfile +import threading +import traceback + +try: + import yaml +except ImportError: + yaml = None + +is_py2 = sys.version[0] == '2' + +if is_py2: + import Queue as queue +else: + import queue as queue + + +def find_compilation_database(path): + """Adjusts the directory until a compilation database is found.""" + result = './' + while not os.path.isfile(os.path.join(result, path)): + if os.path.realpath(result) == '/': + print('Error: could not find compilation database.') + sys.exit(1) + result += '../' + return os.path.realpath(result) + + +def make_absolute(f, directory): + if os.path.isabs(f): + return f + return os.path.normpath(os.path.join(directory, f)) + + +def get_tidy_invocation(f, clang_tidy_binary, checks, tmpdir, build_path, + header_filter, allow_enabling_alpha_checkers, + extra_arg, extra_arg_before, quiet, config): + """Gets a command line for clang-tidy.""" + start = [clang_tidy_binary] + if allow_enabling_alpha_checkers: + start.append('-allow-enabling-analyzer-alpha-checkers') + if header_filter is not None: + start.append('-header-filter=' + header_filter) + if checks: + start.append('-checks=' + checks) + if tmpdir is not None: + start.append('-export-fixes') + # Get a temporary file. We immediately close the handle so clang-tidy can + # overwrite it. + (handle, name) = tempfile.mkstemp(suffix='.yaml', dir=tmpdir) + os.close(handle) + start.append(name) + for arg in extra_arg: + start.append('-extra-arg=%s' % arg) + for arg in extra_arg_before: + start.append('-extra-arg-before=%s' % arg) + start.append('-p=' + build_path) + if quiet: + start.append('-quiet') + if config: + start.append('-config=' + config) + start.append(f) + return start + + +def merge_replacement_files(tmpdir, mergefile): + """Merge all replacement files in a directory into a single file""" + # The fixes suggested by clang-tidy >= 4.0.0 are given under + # the top level key 'Diagnostics' in the output yaml files + mergekey = "Diagnostics" + merged=[] + for replacefile in glob.iglob(os.path.join(tmpdir, '*.yaml')): + content = yaml.safe_load(open(replacefile, 'r')) + if not content: + continue # Skip empty files. + merged.extend(content.get(mergekey, [])) + + if merged: + # MainSourceFile: The key is required by the definition inside + # include/clang/Tooling/ReplacementsYaml.h, but the value + # is actually never used inside clang-apply-replacements, + # so we set it to '' here. + output = {'MainSourceFile': '', mergekey: merged} + with open(mergefile, 'w') as out: + yaml.safe_dump(output, out) + else: + # Empty the file: + open(mergefile, 'w').close() + + +def check_clang_apply_replacements_binary(args): + """Checks if invoking supplied clang-apply-replacements binary works.""" + try: + subprocess.check_call([args.clang_apply_replacements_binary, '--version']) + except: + print('Unable to run clang-apply-replacements. Is clang-apply-replacements ' + 'binary correctly specified?', file=sys.stderr) + traceback.print_exc() + sys.exit(1) + + +def apply_fixes(args, tmpdir): + """Calls clang-apply-fixes on a given directory.""" + invocation = [args.clang_apply_replacements_binary] + if args.format: + invocation.append('-format') + if args.style: + invocation.append('-style=' + args.style) + invocation.append(tmpdir) + subprocess.call(invocation) + + +def run_tidy(args, tmpdir, build_path, queue, lock, failed_files): + """Takes filenames out of queue and runs clang-tidy on them.""" + while True: + name = queue.get() + invocation = get_tidy_invocation(name, args.clang_tidy_binary, args.checks, + tmpdir, build_path, args.header_filter, + args.allow_enabling_alpha_checkers, + args.extra_arg, args.extra_arg_before, + args.quiet, args.config) + + proc = subprocess.Popen(invocation, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + output, err = proc.communicate() + if proc.returncode != 0: + failed_files.append(name) + with lock: + sys.stdout.write(' '.join(invocation) + '\n' + output.decode('utf-8')) + if len(err) > 0: + sys.stdout.flush() + sys.stderr.write(err.decode('utf-8')) + queue.task_done() + + +def main(): + parser = argparse.ArgumentParser(description='Runs clang-tidy over all files ' + 'in a compilation database. Requires ' + 'clang-tidy and clang-apply-replacements in ' + '$PATH.') + parser.add_argument('-allow-enabling-alpha-checkers', + action='store_true', help='allow alpha checkers from ' + 'clang-analyzer.') + parser.add_argument('-clang-tidy-binary', metavar='PATH', + default='clang-tidy-11', + help='path to clang-tidy binary') + parser.add_argument('-clang-apply-replacements-binary', metavar='PATH', + default='clang-apply-replacements-11', + help='path to clang-apply-replacements binary') + parser.add_argument('-checks', default=None, + help='checks filter, when not specified, use clang-tidy ' + 'default') + parser.add_argument('-config', default=None, + help='Specifies a configuration in YAML/JSON format: ' + ' -config="{Checks: \'*\', ' + ' CheckOptions: [{key: x, ' + ' value: y}]}" ' + 'When the value is empty, clang-tidy will ' + 'attempt to find a file named .clang-tidy for ' + 'each source file in its parent directories.') + parser.add_argument('-header-filter', default=None, + help='regular expression matching the names of the ' + 'headers to output diagnostics from. Diagnostics from ' + 'the main file of each translation unit are always ' + 'displayed.') + if yaml: + parser.add_argument('-export-fixes', metavar='filename', dest='export_fixes', + help='Create a yaml file to store suggested fixes in, ' + 'which can be applied with clang-apply-replacements.') + parser.add_argument('-j', type=int, default=0, + help='number of tidy instances to be run in parallel.') + parser.add_argument('files', nargs='*', default=['.*'], + help='files to be processed (regex on path)') + parser.add_argument('-fix', action='store_true', help='apply fix-its') + parser.add_argument('-format', action='store_true', help='Reformat code ' + 'after applying fixes') + parser.add_argument('-style', default='file', help='The style of reformat ' + 'code after applying fixes') + parser.add_argument('-p', dest='build_path', + help='Path used to read a compile command database.') + parser.add_argument('-extra-arg', dest='extra_arg', + action='append', default=[], + help='Additional argument to append to the compiler ' + 'command line.') + parser.add_argument('-extra-arg-before', dest='extra_arg_before', + action='append', default=[], + help='Additional argument to prepend to the compiler ' + 'command line.') + parser.add_argument('-quiet', action='store_true', + help='Run clang-tidy in quiet mode') + args = parser.parse_args() + + db_path = 'compile_commands.json' + + if args.build_path is not None: + build_path = args.build_path + else: + # Find our database + build_path = find_compilation_database(db_path) + + try: + invocation = [args.clang_tidy_binary, '-list-checks'] + if args.allow_enabling_alpha_checkers: + invocation.append('-allow-enabling-analyzer-alpha-checkers') + invocation.append('-p=' + build_path) + if args.checks: + invocation.append('-checks=' + args.checks) + invocation.append('-') + if args.quiet: + # Even with -quiet we still want to check if we can call clang-tidy. + with open(os.devnull, 'w') as dev_null: + subprocess.check_call(invocation, stdout=dev_null) + else: + subprocess.check_call(invocation) + except: + print("Unable to run clang-tidy.", file=sys.stderr) + sys.exit(1) + + # Load the database and extract all files. + database = json.load(open(os.path.join(build_path, db_path))) + files = [make_absolute(entry['file'], entry['directory']) + for entry in database] + + max_task = args.j + if max_task == 0: + max_task = multiprocessing.cpu_count() + + tmpdir = None + if args.fix or (yaml and args.export_fixes): + check_clang_apply_replacements_binary(args) + tmpdir = tempfile.mkdtemp() + + # Build up a big regexy filter from all command line arguments. + file_name_re = re.compile('|'.join(args.files)) + + return_code = 0 + try: + # Spin up a bunch of tidy-launching threads. + task_queue = queue.Queue(max_task) + # List of files with a non-zero return code. + failed_files = [] + lock = threading.Lock() + for _ in range(max_task): + t = threading.Thread(target=run_tidy, + args=(args, tmpdir, build_path, task_queue, lock, failed_files)) + t.daemon = True + t.start() + + # Fill the queue with files. + for name in files: + if file_name_re.search(name): + task_queue.put(name) + + # Wait for all threads to be done. + task_queue.join() + if len(failed_files): + return_code = 1 + + except KeyboardInterrupt: + # This is a sad hack. Unfortunately subprocess goes + # bonkers with ctrl-c and we start forking merrily. + print('\nCtrl-C detected, goodbye.') + if tmpdir: + shutil.rmtree(tmpdir) + os.kill(0, 9) + + if yaml and args.export_fixes: + print('Writing fixes to ' + args.export_fixes + ' ...') + try: + merge_replacement_files(tmpdir, args.export_fixes) + except: + print('Error exporting fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code=1 + + if args.fix: + print('Applying fixes ...') + try: + apply_fixes(args, tmpdir) + except: + print('Error applying fixes.\n', file=sys.stderr) + traceback.print_exc() + return_code = 1 + + if tmpdir: + shutil.rmtree(tmpdir) + sys.exit(return_code) + + +if __name__ == '__main__': + main() diff --git a/tools/lsan.supp b/tools/lsan.supp new file mode 100644 index 000000000..daf07ed1a --- /dev/null +++ b/tools/lsan.supp @@ -0,0 +1,12 @@ +leak:antlr4::atn::ArrayPredictionContext::ArrayPredictionContext +leak:std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count, std::weak_ptr&, unsigned long&>(antlr4::atn::SingletonPredictionContext*&, std::_Sp_alloc_shared_tag >, std::weak_ptr&, unsigned long&) +leak:antlr4::atn::PredictionContext::mergeSingletons(std::shared_ptr const&, std::shared_ptr const&, bool, std::map, std::shared_ptr >, std::shared_ptr, std::less, std::shared_ptr > >, std::allocator, std::shared_ptr > const, std::shared_ptr > > >*) +leak:void std::vector, std::allocator > >::_M_realloc_insert >(__gnu_cxx::__normal_iterator*, std::vector, std::allocator > > >, std::shared_ptr&&) +leak:antlr4::atn::ParserATNSimulator::closureCheckingStopState(std::shared_ptr const&, antlr4::atn::ATNConfigSet*, std::unordered_set, antlr4::atn::ATNConfig::Hasher, antlr4::atn::ATNConfig::Comparer, std::allocator > >&, bool, bool, int, bool) +leak:antlr4::atn::ParserATNSimulator::computeReachSet(antlr4::atn::ATNConfigSet*, unsigned long, bool) +leak:std::_Hashtable, std::allocator >, std::__detail::_Select1st, std::equal_to, std::hash, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits >::_M_insert_unique_node(unsigned long const&, unsigned long, unsigned long, std::__detail::_Hash_node, false>*, unsigned long) +leak:void std::vector, std::allocator > >::_M_realloc_insert const&>(__gnu_cxx::__normal_iterator*, std::vector, std::allocator > > >, std::shared_ptr const&) +leak:antlr4::atn::ATNConfigSet::add(std::shared_ptr const&, std::map, std::shared_ptr >, std::shared_ptr, std::less, std::shared_ptr > >, std::allocator, std::shared_ptr > const, std::shared_ptr > > >*) +leak:antlr4::atn::ParserATNSimulator::getEpsilonTarget(std::shared_ptr const&, antlr4::atn::Transition*, bool, bool, bool, bool) +leak:antlr4::atn::PredictionContext::mergeArrays(std::shared_ptr const&, std::shared_ptr const&, bool, std::map, std::shared_ptr >, std::shared_ptr, std::less, std::shared_ptr > >, std::allocator, std::shared_ptr > const, std::shared_ptr > > >*) +leak:/lib/x86_64-linux-gnu/libpython3.