Compare commits

...

4 Commits

Author SHA1 Message Date
6e27e99735 Initial unit tests 2021-03-07 15:58:45 +02:00
326decd3de Unit tests for basic operation 2021-03-07 15:32:02 +02:00
7b294045bf Unit test framework integration
Along with conan
2021-03-07 14:31:43 +02:00
53c24c6d5e Separate op-finder into library and executable 2021-03-07 13:51:29 +02:00
28 changed files with 431 additions and 25505 deletions

View File

@ -1,8 +1,16 @@
cmake_minimum_required(VERSION 3.17) cmake_minimum_required(VERSION 3.17)
project("C Analyzer") project("C Analyzer")
set(CMAKE_MODULE_PATH ${CMAKE_BINARY_DIR})
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter")
option(WITH_TESTS "Build unit tests as well." OFF)
add_subdirectory(op-finder-lib)
add_subdirectory(op-finder) add_subdirectory(op-finder)
if(WITH_TESTS)
enable_testing()
add_subdirectory(op-finder-tests)
endif()

View File

@ -1,2 +1,13 @@
# masters-thesis # masters-thesis
## Building
Conan is recommended. Otherwise you have to provide `Catch2_ROOT`
and `nlohmann_json_ROOT` yourself.
```shell
> mkdir build
> cd build
> conan install .. --build=missing
> cmake .. -DWITH_TESTS=ON -DClang_ROOT=${CLANG_ROOT} -DLLVM_ROOT=${LLVM_ROOT}
```

6
conanfile.txt Normal file
View File

@ -0,0 +1,6 @@
[requires]
catch2/2.13.4
nlohmann_json/3.9.1
[generators]
cmake_find_package

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,49 @@
cmake_minimum_required(VERSION 3.17)
find_package(nlohmann_json REQUIRED)
find_package(Clang REQUIRED)
find_package(LLVM REQUIRED COMPONENTS Support Option Core)
# LLVM is typically compiled without RTTI. Weird linker errors ensue if
# you keep RTTI on and try to link.
if (NOT LLVM_ENABLE_RTTI)
if (MSVC)
string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-")
else ()
string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
endif ()
endif ()
llvm_map_components_to_libnames(llvm_libs support option core)
add_library(op-finder-lib STATIC
src/OperationFinder.cpp
src/OperationStorage.cpp
src/OperationAstMatcher.cpp
src/OperationFinderAstVisitor.cpp
src/OperationFinderAstConsumer.cpp
src/OperationFinderAstAction.cpp
src/OperationLog.cpp)
target_include_directories(op-finder-lib
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${LLVM_INCLUDE_DIRS}
)
target_compile_definitions(op-finder-lib
PUBLIC
${LLVM_DEFINITIONS}
)
target_link_libraries(op-finder-lib
PUBLIC
nlohmann_json::nlohmann_json
PRIVATE
${llvm_libs}
clangTooling
clangBasic
clangASTMatchers
)

View File

@ -14,22 +14,21 @@
class OperationStorage : public IOperationOutput class OperationStorage : public IOperationOutput
{ {
public: public:
OperationStorage() = delete; OperationStorage() = default;
OperationStorage(const OperationStorage&) = delete; OperationStorage(const OperationStorage&) = delete;
explicit OperationStorage(const std::string& output_filename);
~OperationStorage() override; ~OperationStorage() override;
void enablePrettyPrint(); void enablePrettyPrint();
void toStream(std::ostream& stream);
void toFile(const std::string& output_filename);
void pushOperation(const std::string& filename, OperationLog&& op) override; void pushOperation(const std::string& filename, OperationLog&& op) override;
[[nodiscard]] const std::unordered_map<std::string, std::vector<OperationLog>>& getOperations() const; [[nodiscard]] const std::unordered_map<std::string, std::vector<OperationLog>>& getOperations() const;
private: private:
std::unordered_map<std::string, std::vector<OperationLog>> _operations; std::unordered_map<std::string, std::vector<OperationLog>> _operations;
std::string _output_filename;
bool _pretty_print = false; bool _pretty_print = false;
void _dumpToFile();
}; };
#endif //C_ANALYZER_OPERATIONSTORAGE_HPP #endif //C_ANALYZER_OPERATIONSTORAGE_HPP

View File

@ -1,4 +1,4 @@
// //src
// Created by erki on 02.03.21. // Created by erki on 02.03.21.
// //

View File

@ -9,20 +9,32 @@
#include <llvm/Support/CommandLine.h> #include <llvm/Support/CommandLine.h>
OperationStorage::OperationStorage(const std::string& output_filename)
: _output_filename(output_filename)
{ }
OperationStorage::~OperationStorage() OperationStorage::~OperationStorage()
{ { }
_dumpToFile();
}
void OperationStorage::enablePrettyPrint() void OperationStorage::enablePrettyPrint()
{ {
_pretty_print = true; _pretty_print = true;
} }
void OperationStorage::toStream(std::ostream& stream)
{
nlohmann::json json = _operations;
if (_pretty_print)
stream << std::setw(4) << json;
else
stream << json;
}
void OperationStorage::toFile(const std::string& output_filename)
{
std::ofstream file(output_filename);
toStream(file);
}
void OperationStorage::pushOperation(const std::string& filename, OperationLog&& op) void OperationStorage::pushOperation(const std::string& filename, OperationLog&& op)
{ {
auto it = _operations.find(filename); auto it = _operations.find(filename);
@ -37,15 +49,3 @@ const std::unordered_map<std::string, std::vector<OperationLog>>& OperationStora
{ {
return _operations; return _operations;
} }
void OperationStorage::_dumpToFile()
{
nlohmann::json json = _operations;
std::ofstream file(_output_filename);
if (_pretty_print)
file << std::setw(4) << json;
else
file << json;
}

View File

@ -0,0 +1,30 @@
cmake_minimum_required(VERSION 3.17)
find_package(Catch2 REQUIRED)
# LLVM is typically compiled without RTTI. Weird linker errors ensue if
# you keep RTTI on and try to link.
if (NOT LLVM_ENABLE_RTTI)
if (MSVC)
string(REGEX REPLACE "/GR" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /GR-")
else ()
string(REGEX REPLACE "-frtti" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti")
endif ()
endif ()
add_executable(op-finder-tests
main.cpp
fixtures/RunOnCodeFixture.cpp
basic_operations.cpp
branches.cpp)
target_link_libraries(op-finder-tests
PUBLIC
op-finder-lib
Catch2::Catch2)
include(CTest)
include(Catch)
catch_discover_tests(op-finder-tests)

View File

@ -0,0 +1,95 @@
//
// Created by erki on 07.03.21.
//
#include <catch2/catch.hpp>
#include "fixtures/RunOnCodeFixture.hpp"
TEST_CASE("Finds binary operations", "[basic_operation]")
{
auto binary_operand = GENERATE(std::string("="), std::string("+"), std::string("-"), std::string("/"),
std::string("*"), std::string("<<"), std::string(">>"),
std::string("^"), std::string("=="), std::string("|"), std::string("&"));
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; (void)(a " + binary_operand + " 4); }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 1);
const OperationLog& log = operations.front();
REQUIRE(log.entry_type == OperationLog::BasicOperation::TYPE_NAME);
const OperationLog::BasicOperation* op = (OperationLog::BasicOperation*)(log.entry.get());
REQUIRE(op->operation_name == binary_operand);
REQUIRE(op->type_lhs == "int");
REQUIRE(op->type_rhs == "int");
REQUIRE(op->type_result == "int");
}
TEST_CASE("Find unary operations", "[basic_operation]")
{
auto unary_operand = GENERATE(std::string("++"), std::string("--"), std::string("~"), std::string("!"));
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; (void)(" + unary_operand + "a); }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 1);
const OperationLog& log = operations.front();
REQUIRE(log.entry_type == OperationLog::BasicOperation::TYPE_NAME);
const OperationLog::BasicOperation* op = (OperationLog::BasicOperation*)(log.entry.get());
REQUIRE(op->operation_name == unary_operand);
REQUIRE(op->type_lhs == "int");
REQUIRE(op->type_rhs.empty());
REQUIRE(op->type_result == "int");
}
TEST_CASE("Find subscript operation", "[basic_operation]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a[4]; (void)a[4]; }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 1);
const OperationLog& log = operations.front();
REQUIRE(log.entry_type == OperationLog::BasicOperation::TYPE_NAME);
const OperationLog::BasicOperation* op = (OperationLog::BasicOperation*)(log.entry.get());
REQUIRE(op->operation_name == "subscript");
REQUIRE(op->type_lhs == "int *");
REQUIRE(op->type_rhs == "int");
REQUIRE(op->type_result == "int");
}
TEST_CASE("Find subscript operation reversed", "[basic_operation]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a[4]; (void)4[a]; }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 1);
const OperationLog& log = operations.front();
REQUIRE(log.entry_type == OperationLog::BasicOperation::TYPE_NAME);
const OperationLog::BasicOperation* op = (OperationLog::BasicOperation*)(log.entry.get());
REQUIRE(op->operation_name == "subscript");
REQUIRE(op->type_lhs == "int *");
REQUIRE(op->type_rhs == "int");
REQUIRE(op->type_result == "int");
}

View File

@ -0,0 +1,125 @@
//
// Created by erki on 07.03.21.
//
#include <catch2/catch.hpp>
#include "fixtures/RunOnCodeFixture.hpp"
TEST_CASE("For loop without header", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (;;) {} }";
REQUIRE(fixture.runCode(code));
REQUIRE(fixture.storage.getOperations().empty());
}
TEST_CASE("For loop with init only.", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (a = 4;;) {} }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 1);
const OperationLog& log = operations.front();
REQUIRE(log.branch_number == 1);
}
TEST_CASE("For loop with init & cond.", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (a = 4; a < 4;) {} }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 2);
const OperationLog& log_init = operations.at(0);
REQUIRE(log_init.branch_number == 1);
const OperationLog& log_cond = operations.at(1);
REQUIRE(log_cond.branch_number == 2);
}
TEST_CASE("For loop with init & inc.", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (a = 4;;a++) {} }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 2);
const OperationLog& log_init = operations.at(0);
REQUIRE(log_init.branch_number == 1);
const OperationLog& log_inc = operations.at(1);
REQUIRE(log_inc.branch_number == 2);
}
TEST_CASE("For loop with full header.", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (a = 4; a < 4 ;a++) {} }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 3);
const OperationLog& log_init = operations.at(0);
REQUIRE(log_init.branch_number == 1);
const OperationLog& log_cond = operations.at(1);
REQUIRE(log_cond.branch_number == 2);
const OperationLog& log_inc = operations.at(2);
REQUIRE(log_inc.branch_number == 2);
}
TEST_CASE("For loop without init.", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (;a < 4 ;a++) {} }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 2);
const OperationLog& log_cond = operations.at(0);
REQUIRE(log_cond.branch_number == 0);
const OperationLog& log_inc = operations.at(1);
REQUIRE(log_inc.branch_number == 0);
}
TEST_CASE("For loop closes branches inside the loop.", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (a = 4; a < 4 ; a++) { a = 5; } }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 4);
const OperationLog& log_inner = operations.back();
REQUIRE(log_inner.branch_number == 0);
}
TEST_CASE("For loop closes branches outside the loop.", "[branches][for_loops]")
{
RunOnCodeFixture fixture;
const std::string code = "int main() { int a; for (a = 4; a < 4 ; a++) {} a = 5; }";
const auto& operations = fixture(code);
REQUIRE(operations.size() == 4);
const OperationLog& log_outer = operations.back();
REQUIRE(log_outer.branch_number == 0);
}

View File

@ -0,0 +1,37 @@
//
// Created by erki on 07.03.21.
//
#include "RunOnCodeFixture.hpp"
#include <catch2/catch.hpp>
#include <clang/Tooling/Tooling.h>
#include <clang/Frontend/FrontendActions.h>
RunOnCodeFixture::RunOnCodeFixture()
: finder(&storage)
, action(&finder)
{ }
bool RunOnCodeFixture::runCode(const std::string& code)
{
return clang::tooling::runToolOnCode(
clang::tooling::newFrontendActionFactory(&action)->create(),
code,
RunOnCodeFixture::INPUT_FILE
);
}
const std::vector<OperationLog>& RunOnCodeFixture::operator()(const std::string& code)
{
const bool success = runCode(code);
REQUIRE(success);
const auto& operations = storage.getOperations();
REQUIRE(operations.count(RunOnCodeFixture::INPUT_FILE) == 1);
return operations.at(RunOnCodeFixture::INPUT_FILE);
}

View File

@ -0,0 +1,27 @@
//
// Created by erki on 07.03.21.
//
#ifndef C_ANALYZER_RUNONCODEFIXTURE_HPP
#define C_ANALYZER_RUNONCODEFIXTURE_HPP
#include <OperationStorage.hpp>
#include <OperationFinder.hpp>
#include <OperationFinderAstAction.hpp>
struct RunOnCodeFixture
{
constexpr static char INPUT_FILE[] = "input.c";
OperationStorage storage;
OperationFinder finder;
OperationFinderAstAction action;
RunOnCodeFixture();
bool runCode(const std::string& code);
const std::vector<OperationLog>& operator()(const std::string& code);
};
#endif //C_ANALYZER_RUNONCODEFIXTURE_HPP

6
op-finder-tests/main.cpp Normal file
View File

@ -0,0 +1,6 @@
//
// Created by erki on 07.03.21.
//
#define CATCH_CONFIG_MAIN // This tells Catch to provide a main() - only do this in one cpp file
#include <catch2/catch.hpp>

View File

@ -1,8 +1,5 @@
cmake_minimum_required(VERSION 3.17) cmake_minimum_required(VERSION 3.17)
find_package(Clang REQUIRED)
find_package(LLVM REQUIRED COMPONENTS Support Option Core)
# LLVM is typically compiled without RTTI. Weird linker errors ensue if # LLVM is typically compiled without RTTI. Weird linker errors ensue if
# you keep RTTI on and try to link. # you keep RTTI on and try to link.
if (NOT LLVM_ENABLE_RTTI) if (NOT LLVM_ENABLE_RTTI)
@ -15,33 +12,9 @@ if (NOT LLVM_ENABLE_RTTI)
endif () endif ()
endif () endif ()
llvm_map_components_to_libnames(llvm_libs support option core)
add_executable(op-finder add_executable(op-finder
main.cpp main.cpp)
OperationFinder.cpp
OperationStorage.cpp
OperationAstMatcher.cpp
OperationFinderAstVisitor.cpp
OperationFinderAstConsumer.cpp
OperationFinderAstAction.cpp
OperationLog.cpp)
target_include_directories(op-finder
PRIVATE
${LLVM_INCLUDE_DIRS}
${PROJECT_SOURCE_DIR}/dependencies
)
target_compile_definitions(op-finder
PRIVATE
${LLVM_DEFINITIONS}
)
target_link_libraries(op-finder target_link_libraries(op-finder
PRIVATE PUBLIC
${llvm_libs} op-finder-lib)
clangTooling
clangBasic
clangASTMatchers
)

View File

@ -5,6 +5,8 @@
// Declares llvm::cl::extrahelp. // Declares llvm::cl::extrahelp.
#include "llvm/Support/CommandLine.h" #include "llvm/Support/CommandLine.h"
#include <iostream>
#include "OperationAstMatcher.hpp" #include "OperationAstMatcher.hpp"
#include "OperationFinder.hpp" #include "OperationFinder.hpp"
#include "OperationStorage.hpp" #include "OperationStorage.hpp"
@ -50,7 +52,7 @@ int main(int argc, const char** argv)
ClangTool Tool(OptionsParser.getCompilations(), ClangTool Tool(OptionsParser.getCompilations(),
OptionsParser.getSourcePathList()); OptionsParser.getSourcePathList());
OperationStorage storage(OutputFile.getValue()); OperationStorage storage;
if (PrettyPrint.getValue()) if (PrettyPrint.getValue())
storage.enablePrettyPrint(); storage.enablePrettyPrint();
@ -70,5 +72,10 @@ int main(int argc, const char** argv)
OperationFinderAstAction action(&op_finder); OperationFinderAstAction action(&op_finder);
return Tool.run(newFrontendActionFactory(&action).get()); assert(!Tool.run(newFrontendActionFactory(&action).get()));
if (!OutputFile.getValue().empty())
storage.toFile(OutputFile.getValue());
else
storage.toStream(std::cout);
} }