From 78c3b595283b00605a73440c1fdda455666153fb Mon Sep 17 00:00:00 2001 From: eidheim Date: Fri, 7 Aug 2020 11:00:06 +0200 Subject: [PATCH] Made use of libfuzzer, and fixed a few edge cases identified by libfuzzer --- CMakeLists.txt | 7 +- src/cmake.cpp | 3 +- src/ctags.cpp | 111 +++++++++++------------- src/grep.cpp | 2 + tests/CMakeLists.txt | 168 +++++++++++++++++++++--------------- tests/fuzzers/README.md | 6 ++ tests/fuzzers/cmake.cpp | 12 +++ tests/fuzzers/ctags.cpp | 13 +++ tests/fuzzers/docstring.cpp | 28 ++++++ tests/fuzzers/doxygen.cpp | 28 ++++++ tests/fuzzers/grep.cpp | 12 +++ tests/fuzzers/markdown.cpp | 28 ++++++ 12 files changed, 285 insertions(+), 133 deletions(-) create mode 100644 tests/fuzzers/README.md create mode 100644 tests/fuzzers/cmake.cpp create mode 100644 tests/fuzzers/ctags.cpp create mode 100644 tests/fuzzers/docstring.cpp create mode 100644 tests/fuzzers/doxygen.cpp create mode 100644 tests/fuzzers/grep.cpp create mode 100644 tests/fuzzers/markdown.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index a54af48..03e4573 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES FreeBSD) endif() option(BUILD_TESTING "Build tests") +option(BUILD_FUZZING "Build tests") option(LIBCLANG_PATH "Use custom path for libclang") option(LIBLLDB_PATH "Use custom path for liblldb") @@ -115,7 +116,9 @@ include_directories( add_subdirectory("src") -if(BUILD_TESTING) - enable_testing() +if(BUILD_TESTING OR BUILD_FUZZING) + if(BUILD_TESTING) + enable_testing() + endif() add_subdirectory(tests) endif() diff --git a/src/cmake.cpp b/src/cmake.cpp index c1327e7..31e4293 100644 --- a/src/cmake.cpp +++ b/src/cmake.cpp @@ -311,7 +311,8 @@ void CMake::parse_file(const std::string &src, std::mapparameters); } } - on_function(std::move(*function)); + if(on_function) + on_function(std::move(*function)); } } } diff --git a/src/ctags.cpp b/src/ctags.cpp index 87e25e1..ccde85e 100644 --- a/src/ctags.cpp +++ b/src/ctags.cpp @@ -8,6 +8,8 @@ #include Ctags::Ctags(const boost::filesystem::path &path, bool enable_scope, bool enable_kind, const std::string &languages) : enable_scope(enable_scope), enable_kind(enable_kind) { + if(path.empty()) + return; // TODO: When universal ctags is available on all platforms (Ubuntu 20.04 LTS does), add: --pattern-length-limit=0 auto options = " --sort=foldcase -I \"override noexcept\" -f -" + (!languages.empty() ? " --languages=" + languages : std::string()); std::string fields(" --fields=n"); @@ -57,10 +59,8 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b auto &line = line_; #endif auto symbol_end = line.find('\t'); - if(symbol_end == std::string::npos) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(symbol_end == std::string::npos) return location; - } location.symbol = line.substr(0, symbol_end); if(9 < location.symbol.size() && location.symbol[8] == ' ' && starts_with(location.symbol, "operator")) { auto &chr = location.symbol[9]; @@ -69,68 +69,56 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b } auto file_start = symbol_end + 1; - if(file_start >= line.size()) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(file_start >= line.size()) return location; - } auto file_end = line.find('\t', file_start); - if(file_end == std::string::npos) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(file_end == std::string::npos) return location; - } location.file_path = line.substr(file_start, file_end - file_start); auto source_start = file_end + 3; - if(source_start >= line.size()) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(source_start >= line.size()) return location; - } location.index = 0; while(source_start < line.size() && (line[source_start] == ' ' || line[source_start] == '\t')) { ++source_start; ++location.index; } auto source_end = line.find("/;\"\t", source_start); - if(source_end == std::string::npos) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(source_end == std::string::npos) return location; - } // Unescape source - auto end = source_end - (line[source_end - 1] == '$' ? 1 : 0); - location.source.reserve(end - source_start); - bool escaped = false; - for(auto i = source_start; i < end; ++i) { - if(!escaped && line[i] == '\\') { - escaped = true; - continue; + if(source_end > source_start) { + auto end = source_end - (line[source_end - 1] == '$' ? 1 : 0); + location.source.reserve(end - source_start); + bool escaped = false; + for(auto i = source_start; i < end; ++i) { + if(!escaped && line[i] == '\\') { + escaped = true; + continue; + } + escaped = false; + location.source += line[i]; } - escaped = false; - location.source += line[i]; } size_t line_start; if(enable_kind) { auto kind_start = source_end + 4; - if(kind_start >= line.size()) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(kind_start >= line.size()) return location; - } auto kind_end = line.find('\t', kind_start); - if(kind_end == std::string::npos) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(kind_end == std::string::npos) return location; - } location.kind = line.substr(kind_start, kind_end - kind_start); line_start = kind_start + location.kind.size() + 6; } else line_start = source_end + 9; - if(line_start >= line.size()) { - std::cerr << "Warning (ctags): could not parse line: " << line << std::endl; + if(line_start >= line.size()) return location; - } auto line_end = line.find('\t', line_start); size_t line_size = line_end == std::string::npos ? std::string::npos : line_end - line_start; try { @@ -146,39 +134,40 @@ Ctags::Location Ctags::get_location(const std::string &line_, bool add_markup, b location.scope = line.substr(scope_start + 1); } - if(add_markup) { - location.source = Glib::Markup::escape_text(location.source); - std::string symbol = Glib::Markup::escape_text(location.symbol); - if(symbol_ends_with_open_parenthesis) - symbol += '('; - bool first = true; - bool escaped = false; - for(size_t i = 0; i < location.source.size(); i++) { - if(!escaped) { - if(starts_with(location.source, i, symbol)) { - location.source.insert(i + symbol.size(), ""); - location.source.insert(i, ""); - i += 7 + symbol.size() - 1; - if(first) + if(!location.symbol.empty()) { + if(add_markup) { + location.source = Glib::Markup::escape_text(location.source); + std::string symbol = Glib::Markup::escape_text(location.symbol); + if(symbol_ends_with_open_parenthesis) + symbol += '('; + bool first = true; + bool escaped = false; + for(size_t i = 0; i < location.source.size(); i++) { + if(!escaped) { + if(starts_with(location.source, i, symbol)) { + location.source.insert(i + symbol.size(), ""); + location.source.insert(i, ""); + i += 7 + symbol.size() - 1; first = false; - } - else { - if(location.source[i] == '&') { - escaped = true; - i += 2; // Minimum character entities: < and > } - if(first) - location.index++; + else { + if(location.source[i] == '&') { + escaped = true; + i += 2; // Minimum character entities: < and > + } + if(first) + location.index++; + } } + else if(location.source[i] == ';') + escaped = false; } - else if(location.source[i] == ';') - escaped = false; } - } - else { - auto pos = location.source.find(location.symbol); - if(pos != std::string::npos) - location.index += pos; + else { + auto pos = location.source.find(location.symbol); + if(pos != std::string::npos) + location.index += pos; + } } return location; diff --git a/src/grep.cpp b/src/grep.cpp index 7701342..270e7ca 100644 --- a/src/grep.cpp +++ b/src/grep.cpp @@ -6,6 +6,8 @@ #include "utility.hpp" Grep::Grep(const boost::filesystem::path &path, const std::string &pattern, bool case_sensitive, bool extended_regex) { + if(path.empty()) + return; auto build = Project::Build::create(path); std::string exclude; if(!build->project_path.empty()) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 6a88646..5428c91 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -5,11 +5,7 @@ endif() add_definitions(-DJUCI_BUILD_PATH="${CMAKE_BINARY_DIR}" -DJUCI_TESTS_PATH="${CMAKE_CURRENT_SOURCE_DIR}") -include_directories( - ${CMAKE_SOURCE_DIR}/src - ${CMAKE_SOURCE_DIR}/libclangmm/src - ${CMAKE_SOURCE_DIR}/tiny-process-library -) +include_directories(${CMAKE_SOURCE_DIR}/src) add_library(test_stubs OBJECT stubs/config.cpp @@ -21,69 +17,103 @@ add_library(test_stubs OBJECT stubs/selection_dialog.cpp ) -add_executable(process_test process_test.cpp $) -target_link_libraries(process_test juci_shared) -add_test(process_test process_test) - -add_executable(compile_commands_test compile_commands_test.cpp $) -target_link_libraries(compile_commands_test juci_shared) -add_test(compile_commands_test compile_commands_test) - -add_executable(filesystem_test filesystem_test.cpp $) -target_link_libraries(filesystem_test juci_shared) -add_test(filesystem_test filesystem_test) - -add_executable(cmake_build_test cmake_build_test.cpp $) -target_link_libraries(cmake_build_test juci_shared) -add_test(cmake_build_test cmake_build_test) - -add_executable(meson_build_test meson_build_test.cpp $) -target_link_libraries(meson_build_test juci_shared) -add_test(meson_build_test meson_build_test) - -add_executable(source_test source_test.cpp $) -target_link_libraries(source_test juci_shared) -add_test(source_test source_test) - -add_executable(source_clang_test source_clang_test.cpp $) -target_link_libraries(source_clang_test juci_shared) -add_test(source_clang_test source_clang_test) - -add_executable(source_generic_test source_generic_test.cpp $) -target_link_libraries(source_generic_test juci_shared) -add_test(source_generic_test source_generic_test) - -add_executable(source_key_test source_key_test.cpp $) -target_link_libraries(source_key_test juci_shared) -add_test(source_key_test source_key_test) - -add_executable(terminal_test terminal_test.cpp $) -target_link_libraries(terminal_test juci_shared) -add_test(terminal_test terminal_test) - -add_executable(usages_clang_test usages_clang_test.cpp $) -target_link_libraries(usages_clang_test juci_shared) -add_test(usages_clang_test usages_clang_test) - -if(LIBLLDB_FOUND) - add_executable(lldb_test lldb_test.cpp $) - target_link_libraries(lldb_test juci_shared) - add_test(lldb_test lldb_test) - add_subdirectory("lldb_test_files") +if(BUILD_TESTING) + add_executable(process_test process_test.cpp $) + target_link_libraries(process_test juci_shared) + add_test(process_test process_test) + + add_executable(compile_commands_test compile_commands_test.cpp $) + target_link_libraries(compile_commands_test juci_shared) + add_test(compile_commands_test compile_commands_test) + + add_executable(filesystem_test filesystem_test.cpp $) + target_link_libraries(filesystem_test juci_shared) + add_test(filesystem_test filesystem_test) + + add_executable(cmake_build_test cmake_build_test.cpp $) + target_link_libraries(cmake_build_test juci_shared) + add_test(cmake_build_test cmake_build_test) + + add_executable(meson_build_test meson_build_test.cpp $) + target_link_libraries(meson_build_test juci_shared) + add_test(meson_build_test meson_build_test) + + add_executable(source_test source_test.cpp $) + target_link_libraries(source_test juci_shared) + add_test(source_test source_test) + + add_executable(source_clang_test source_clang_test.cpp $) + target_link_libraries(source_clang_test juci_shared) + add_test(source_clang_test source_clang_test) + + add_executable(source_generic_test source_generic_test.cpp $) + target_link_libraries(source_generic_test juci_shared) + add_test(source_generic_test source_generic_test) + + add_executable(source_key_test source_key_test.cpp $) + target_link_libraries(source_key_test juci_shared) + add_test(source_key_test source_key_test) + + add_executable(terminal_test terminal_test.cpp $) + target_link_libraries(terminal_test juci_shared) + add_test(terminal_test terminal_test) + + add_executable(usages_clang_test usages_clang_test.cpp $) + target_link_libraries(usages_clang_test juci_shared) + add_test(usages_clang_test usages_clang_test) + + if(LIBLLDB_FOUND) + add_executable(lldb_test lldb_test.cpp $) + target_link_libraries(lldb_test juci_shared) + add_test(lldb_test lldb_test) + add_subdirectory("lldb_test_files") + endif() + + add_executable(git_test git_test.cpp $) + target_link_libraries(git_test juci_shared) + add_test(git_test git_test) + + add_executable(ctags_grep_test ctags_grep_test.cpp $) + target_link_libraries(ctags_grep_test juci_shared) + add_test(ctags_grep_test ctags_grep_test) + + add_executable(tooltips_test tooltips_test.cpp $) + target_link_libraries(tooltips_test juci_shared) + add_test(tooltips_test tooltips_test) + + add_executable(utility_test utility_test.cpp $) + target_link_libraries(utility_test juci_shared) + add_test(utility_test utility_test) endif() -add_executable(git_test git_test.cpp $) -target_link_libraries(git_test juci_shared) -add_test(git_test git_test) - -add_executable(ctags_grep_test ctags_grep_test.cpp $) -target_link_libraries(ctags_grep_test juci_shared) -add_test(ctags_grep_test ctags_grep_test) - -add_executable(tooltips_test tooltips_test.cpp $) -target_link_libraries(tooltips_test juci_shared) -add_test(tooltips_test tooltips_test) - -add_executable(utility_test utility_test.cpp $) -target_link_libraries(utility_test juci_shared) -add_test(utility_test utility_test) +if(BUILD_FUZZING) + add_executable(cmake_fuzzer fuzzers/cmake.cpp $) + target_compile_options(cmake_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_options(cmake_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_libraries(cmake_fuzzer juci_shared) + + add_executable(ctags_fuzzer fuzzers/ctags.cpp $) + target_compile_options(ctags_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_options(ctags_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_libraries(ctags_fuzzer juci_shared) + + add_executable(docstring_fuzzer fuzzers/docstring.cpp $) + target_compile_options(docstring_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_options(docstring_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_libraries(docstring_fuzzer juci_shared) + + add_executable(doxygen_fuzzer fuzzers/doxygen.cpp $) + target_compile_options(doxygen_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_options(doxygen_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_libraries(doxygen_fuzzer juci_shared) + + add_executable(grep_fuzzer fuzzers/grep.cpp $) + target_compile_options(grep_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_options(grep_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_libraries(grep_fuzzer juci_shared) + + add_executable(markdown_fuzzer fuzzers/markdown.cpp $) + target_compile_options(markdown_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_options(markdown_fuzzer PRIVATE -fsanitize=address,fuzzer) + target_link_libraries(markdown_fuzzer juci_shared) +endif() diff --git a/tests/fuzzers/README.md b/tests/fuzzers/README.md new file mode 100644 index 0000000..00d6837 --- /dev/null +++ b/tests/fuzzers/README.md @@ -0,0 +1,6 @@ +Prior to running the fuzzers, build and prepare for instance as follows: +```sh +CXX=clang++ cmake -DBUILD_FUZZING=1 .. +make +export LSAN_OPTIONS=detect_leaks=0 +``` \ No newline at end of file diff --git a/tests/fuzzers/cmake.cpp b/tests/fuzzers/cmake.cpp new file mode 100644 index 0000000..584c5ac --- /dev/null +++ b/tests/fuzzers/cmake.cpp @@ -0,0 +1,12 @@ +#include "cmake.hpp" +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + const gchar *end; + if(!g_utf8_validate(reinterpret_cast(data), size, &end)) + return 0; + + std::map> variables; + CMake::parse_file(std::string(reinterpret_cast(data), size), variables, [](CMake::Function) {}); + return 0; +} diff --git a/tests/fuzzers/ctags.cpp b/tests/fuzzers/ctags.cpp new file mode 100644 index 0000000..a203204 --- /dev/null +++ b/tests/fuzzers/ctags.cpp @@ -0,0 +1,13 @@ +#include "ctags.hpp" +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + const gchar *end; + if(!g_utf8_validate(reinterpret_cast(data), size, &end)) + return 0; + + std::map> variables; + Ctags ctags({}, true, true); + ctags.get_location(std::string(reinterpret_cast(data), size), true, false); + return 0; +} diff --git a/tests/fuzzers/docstring.cpp b/tests/fuzzers/docstring.cpp new file mode 100644 index 0000000..a709b23 --- /dev/null +++ b/tests/fuzzers/docstring.cpp @@ -0,0 +1,28 @@ +#include "tooltips.hpp" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + const gchar *end; + if(!g_utf8_validate(reinterpret_cast(data), size, &end)) + return 0; + + class Init { + Glib::RefPtr app; + + public: + Init() { + app = Gtk::Application::create(); + Gsv::init(); + } + }; + static Init init; + static auto get_docstring_tooltip = [](const std::string &input) { + auto tooltip = std::make_unique([&](Tooltip &tooltip) { + tooltip.insert_docstring(input); + }); + tooltip->show(); + return tooltip; + }; + + get_docstring_tooltip(std::string(reinterpret_cast(data), size)); + return 0; +} diff --git a/tests/fuzzers/doxygen.cpp b/tests/fuzzers/doxygen.cpp new file mode 100644 index 0000000..cc6ddea --- /dev/null +++ b/tests/fuzzers/doxygen.cpp @@ -0,0 +1,28 @@ +#include "tooltips.hpp" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + const gchar *end; + if(!g_utf8_validate(reinterpret_cast(data), size, &end)) + return 0; + + class Init { + Glib::RefPtr app; + + public: + Init() { + app = Gtk::Application::create(); + Gsv::init(); + } + }; + static Init init; + static auto get_doxygen_tooltip = [](const std::string &input) { + auto tooltip = std::make_unique([&](Tooltip &tooltip) { + tooltip.insert_doxygen(input, true); + }); + tooltip->show(); + return tooltip; + }; + + get_doxygen_tooltip(std::string(reinterpret_cast(data), size)); + return 0; +} diff --git a/tests/fuzzers/grep.cpp b/tests/fuzzers/grep.cpp new file mode 100644 index 0000000..f36d5b8 --- /dev/null +++ b/tests/fuzzers/grep.cpp @@ -0,0 +1,12 @@ +#include "grep.hpp" +#include + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + const gchar *end; + if(!g_utf8_validate(reinterpret_cast(data), size, &end)) + return 0; + + Grep grep({}, {}, false, false); + grep.get_location(std::string(reinterpret_cast(data), size), true, true); + return 0; +} diff --git a/tests/fuzzers/markdown.cpp b/tests/fuzzers/markdown.cpp new file mode 100644 index 0000000..f8c5294 --- /dev/null +++ b/tests/fuzzers/markdown.cpp @@ -0,0 +1,28 @@ +#include "tooltips.hpp" + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { + const gchar *end; + if(!g_utf8_validate(reinterpret_cast(data), size, &end)) + return 0; + + class Init { + Glib::RefPtr app; + + public: + Init() { + app = Gtk::Application::create(); + Gsv::init(); + } + }; + static Init init; + static auto get_markdown_tooltip = [](const std::string &input) { + auto tooltip = std::make_unique([&](Tooltip &tooltip) { + tooltip.insert_markdown(input); + }); + tooltip->show(); + return tooltip; + }; + + get_markdown_tooltip(std::string(reinterpret_cast(data), size)); + return 0; +}