diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a05c534 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +oclint +build +.oclint +compile_commands.json diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..0ad8633 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,146 @@ +CMAKE_MINIMUM_REQUIRED(VERSION 2.13.4) +PROJECT(OCLINT_EXTENSIONS) + +SET(LLVM_ROOT ${CMAKE_SOURCE_DIR}/oclint/build/llvm-install) + +SET(CMAKE_DISABLE_SOURCE_CHANGES ON) +SET(CMAKE_DISABLE_IN_SOURCE_BUILD ON) +set(CMAKE_MACOSX_RPATH ON) +SET(CMAKE_BUILD_TYPE None) + +IF(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + SET(CMAKE_CXX_FLAGS "-fcolor-diagnostics") +ENDIF() +SET(CMAKE_CXX_FLAGS "-std=c++14 ${CMAKE_CXX_LINKER_FLAGS} -fno-rtti -fPIC ${CMAKE_CXX_FLAGS}") +SET(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_CXX_LINKER_FLAGS} -fno-rtti") + +IF(OCLINT_BUILD_TYPE STREQUAL "Release") + SET(CMAKE_CXX_FLAGS "-O3 -DNDEBUG ${CMAKE_CXX_FLAGS}") + SET(CMAKE_SHARED_LINKER_FLAGS "-s ${CMAKE_SHARED_LINKER_FLAGS}") +ELSE() + SET(CMAKE_CXX_FLAGS "-O0 -g ${CMAKE_CXX_FLAGS}") + SET(CMAKE_SHARED_LINKER_FLAGS "-g ${CMAKE_SHARED_LINKER_FLAGS}") +ENDIF() + +SET(OCLINT_VERSION_RELEASE "22.02") + +IF(NOT EXISTS ${LLVM_ROOT}/include/llvm) + MESSAGE(FATAL_ERROR "LLVM_ROOT (${LLVM_ROOT}) is not a valid LLVM install. Could not find ${LLVM_ROOT}/include/llvm") +ENDIF() +MESSAGE("LLVM_ROOT: ${LLVM_ROOT}") +IF(EXISTS ${LLVM_ROOT}/lib/cmake/llvm) + SET(LLVM_DIR ${LLVM_ROOT}/lib/cmake/llvm) +ELSE() + SET(LLVM_DIR ${LLVM_ROOT}/share/llvm/cmake) +ENDIF() +SET(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${LLVM_DIR}") +INCLUDE(LLVMConfig) + +INCLUDE_DIRECTORIES( ${LLVM_INCLUDE_DIRS} ) +LINK_DIRECTORIES( ${LLVM_LIBRARY_DIRS} ) +ADD_DEFINITIONS( ${LLVM_DEFINITIONS} ) + +STRING(REGEX MATCH "[0-9]+\\.[0-9]+(\\.[0-9]+)?" LLVM_VERSION_RELEASE ${LLVM_PACKAGE_VERSION}) + +MESSAGE(STATUS "Found LLVM LLVM_PACKAGE_VERSION: ${LLVM_PACKAGE_VERSION} - LLVM_VERSION_RELEASE: ${LLVM_VERSION_RELEASE}") +MESSAGE(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") +LLVM_MAP_COMPONENTS_TO_LIBNAMES(REQ_LLVM_LIBRARIES asmparser bitreader instrumentation mcparser option support frontendopenmp) + +SET(CLANG_LIBRARIES + clangToolingCore + clangTooling + clangFrontend + clangDriver + clangSerialization + clangParse + clangSema + clangAnalysis + clangEdit + clangASTMatchers + clangAST + clangLex + clangBasic) + +IF(TEST_BUILD) + ENABLE_TESTING() + IF(NOT APPLE) + ADD_DEFINITIONS( + --coverage + ) + ENDIF() + + INCLUDE_DIRECTORIES( + ${GOOGLETEST_SRC}/googlemock/include + ${GOOGLETEST_SRC}/googletest/include + ) + LINK_DIRECTORIES( + ${GOOGLETEST_BUILD} + ${GOOGLETEST_BUILD}/lib + ) + SET(GTEST_LIBS gmock gtest) + + # Find CUDA + FIND_PROGRAM(NVIDIA_NVCC_BIN "nvcc") + IF (NVIDIA_NVCC_BIN) + MESSAGE(STATUS "Enable tests for CUDA rules.") + SET(TEST_CUDA TRUE) + ELSE() + SET(TEST_CUDA FALSE) + ENDIF() + + # Setup the path for profile_rt library + STRING(TOLOWER ${CMAKE_SYSTEM_NAME} COMPILER_RT_SYSTEM_NAME) + LINK_DIRECTORIES(${LLVM_LIBRARY_DIRS}/clang/${LLVM_VERSION_RELEASE}/lib/${COMPILER_RT_SYSTEM_NAME}) + IF(APPLE) + SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fprofile-arcs -ftest-coverage") + ELSEIF(${CMAKE_SYSTEM_PROCESSOR} MATCHES "aarch64") + SET(PROFILE_RT_LIBS clang_rt.profile-aarch64 --coverage) + ELSE() + SET(PROFILE_RT_LIBS clang_rt.profile-x86_64 --coverage) + ENDIF() +ENDIF() + +IF(DOC_GEN_BUILD) + SET(CMAKE_CXX_FLAGS "-DDOCGEN ${CMAKE_CXX_FLAGS}") +ENDIF() + + + + +INCLUDE_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/oclint/oclint-core/include + ${CMAKE_SOURCE_DIR}/oclint/oclint-metrics/include + ${CMAKE_SOURCE_DIR}/oclint/oclint-rules/include + ${CMAKE_SOURCE_DIR}/oclint/oclint-rules + ) +LINK_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/oclint/build/oclint-core/lib + ${CMAKE_SOURCE_DIR}/oclint/build/oclint-metrics/lib + ${CMAKE_SOURCE_DIR}/oclint/build/oclint-rules/lib/helper + ${CMAKE_SOURCE_DIR}/oclint/build/oclint-rules/lib/util + ${CMAKE_SOURCE_DIR}/oclint/build/oclint-rules/lib + ) + +MACRO(build_dynamic_rule name) + ADD_LIBRARY(${name}Rule SHARED ${CMAKE_SOURCE_DIR}/rules/${name}Rule.cpp) + + TARGET_LINK_LIBRARIES(${name}Rule OCLintAbstractRule) + + TARGET_LINK_LIBRARIES(${name}Rule + clangASTMatchers + ) + + TARGET_LINK_LIBRARIES(${name}Rule + OCLintMetric + OCLintHelper + OCLintUtil + OCLintCore + ) + install(TARGETS ${name}Rule DESTINATION lib/oclint/rules) +ENDMACRO(build_dynamic_rule) + +BUILD_DYNAMIC_RULE(TooLongIfSequence) +BUILD_DYNAMIC_RULE(MallocSizeof) +BUILD_DYNAMIC_RULE(StringCompare) +BUILD_DYNAMIC_RULE(GlobalVariable) +BUILD_DYNAMIC_RULE(OnlyMainFunction) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..cd8e781 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM ubuntu:22.04 + +RUN apt update && apt install -y ninja-build cmake bear g++ git python3 curl xz-utils zlib1g-dev +WORKDIR /app + +COPY ./scripts/get_oclint.sh get_oclint.sh +RUN ./get_oclint.sh + +COPY ./rules ./rules +COPY ./CMakeLists.txt . +COPY ./scripts/build_rules.sh ./scripts/build_rules.sh +RUN ./scripts/build_rules.sh + +COPY . . + +ENTRYPOINT ["scripts/entry.sh"] diff --git a/README.md b/README.md index 46f6f2b..d3f757f 100644 --- a/README.md +++ b/README.md @@ -1,51 +1,21 @@ -# oclint_extensions +# OCLint extensions -## Что это? - -Кастомные правила для oclint. Список реализованных правил: -* слишком много последовательных конструкций if (TooManyConsecutiveIfStatementsRule) - -## Установка, сборка и использование - -### Создание заготовок для новых правил внутри репозитория oclint - -Для начала склонируйте куда-нибудь [репозиторий oclint](https://github.com/oclint/oclint). - -Далее необходимо добавить "заготовки" для новых правил. Другими словами, необходимо создать исходники для новых правил и внести их в CMakeLists oclint'а. Новые правила относятся к группе "etu". Для этого выполните следующее: +## Как собрать и установить? ```bash -cd oclint-scripts -./scaffoldRule -t ASTVisitor -c etu -n"too many consecutive if statements" -p 2 TooManyConsecutiveIfStatementsRule +./scripts/build.sh ``` -### Копирование исходников с новыми правилами в репозиторий oclint +Папка `examples` содержит примеры для проверки работоспособности критериев. -Из репозитория oclint_extensions выполните: +Примеры запуска проверки: ```bash -cp -r ./oclint-rules YOUR_OCLINT_DIRECTORY_GOES_HERE +docker run --rm -it -v ./examples/ex-goto:/app/solution:ro oclint_checker ``` -### Сборка и установка +Можно передавать аргументы: -Вернитесь в репозиторий oclint. Выполните следующее: ```bash -cd oclint-scripts -./make -release # этот скрипт начнет скачивание oclint-json-compilation-database размером в 514 МБ. имейте в виду - -cd .. -cd build/oclint-release -sudo cp ./bin/oclint* /usr/local/bin/ -sudo cp -rp ./lib/* /usr/local/lib/ +docker run --rm -it -v ./examples/ex-goto:/app/solution:ro oclint_checker --rule StringCompareRule --rule GotoStatementRule # проверяет код на эти два критерия ``` - -### Использование - -``` -oclint --rule=TooManyConsecutiveIfStatements examples/ex*.c -``` - -## P.S. - -Очень странный способ установки всего этого. В теории можно сделать форк oclint'а, ноооооо сомнительно. Должен же быть какой-то более простой способ. -Критикуйте, предлагайте. \ No newline at end of file diff --git a/examples/ex-global/main.cpp b/examples/ex-global/main.cpp new file mode 100644 index 0000000..2288045 --- /dev/null +++ b/examples/ex-global/main.cpp @@ -0,0 +1,24 @@ +#include +using namespace std; + +int bad_glob_var = 0; + +template +struct vec3 { + T x, y, z; +}; + +int main() +{ + static int bad_static_var1 = 0, bad_static_var2 = 0; + static const int bad_static_const = 13; + static vec3 bad_vec { 0.f, 0.f, 0.f }; + + cin >> bad_static_var1 >> bad_static_var2 >> bad_glob_var; + cin >> bad_vec.x >> bad_vec.y >> bad_vec.z; + + cout << bad_static_var1 * 2 << bad_static_var2 + bad_static_const << bad_glob_var / 4 << endl; + cout << bad_vec.x * 2 << bad_vec.y * 2 << bad_vec.z * 2 << endl; + + return 0; +} diff --git a/examples/ex-goto/main.cpp b/examples/ex-goto/main.cpp new file mode 100644 index 0000000..51721a9 --- /dev/null +++ b/examples/ex-goto/main.cpp @@ -0,0 +1,14 @@ +#include +using namespace std; + +int main() +{ + int x; + cin >> x; + + if (x != 2) goto failed; + cout << "Congrats! You typed 2!" << endl; + +failed: + return 0; +} diff --git a/examples/ex-if/main.c b/examples/ex-if/main.c new file mode 100644 index 0000000..15f8fda --- /dev/null +++ b/examples/ex-if/main.c @@ -0,0 +1,106 @@ +#include + +void simple_ifs() +{ + int option = 0; + scanf("%d", &option); + + if (option == 0) { + puts("Zero"); + } + if (option == 1) { + puts("One"); + } + if (option == 2) { + puts("Two"); + } + if (option == 3) { + puts("Three"); + } + if (option == 4) { + puts("Four"); + } + if (option == 5) { + puts("Five"); + } + if (option == 6) { + puts("Six"); + } + if (option == 7) { + puts("Seven"); + } + if (option == 8) { + puts("Eight"); + } + if (option == 9) { + puts("Nine"); + } + else { + puts("Something else"); + } +} + +void complex_ifs() +{ + int x = 0; + int param1 = 0; + float param2 = 0.0; + float param3 = 0.0; + float param4 = 0.0; + int opt = 0; + int value = -1; + scanf("%d %d %f %f %f %d %d", &x, ¶m1, ¶m2, ¶m3, ¶m4, &opt, &value); + + if (x == 0) { + puts("Zero"); + } + else if (param1 > 100){ + puts("Too large"); + } + else if (param2 < 0.0){ + puts("Negative"); + } + else if( param3 * param2 > 1e+5){ + puts("Invalid"); + } + else if (param4 / 2 < param1){ + puts("Unique case"); + } + else if (opt < 0 || opt > 6){ + puts("Invalid option"); + } + else if (value == -1){ + puts("By default"); + } +} + +int gcd(int a, int b); +int lcm(int a, int b); + +void almost_complex_ifs() +{ + int x, y; + scanf("%d %d", &x, &y); + + // different function names + if (gcd(x, y) == 1) printf("GCD is 1\n"); + else if (lcm(x, y) == 100) printf("LCM is 100\n"); + else if (gcd(x, y) == 1337) printf("GCD is 1337\n"); + else if (gcd(x, y) == 300) printf("GCD is 300\n"); + if (lcm(x, y) == 512) printf("LCM is 512\n"); + if (gcd(x, y) == 64) printf("GCD is 64\n"); + + // different binary operators + if (x > 100) printf("x is greater than 100\n"); + else if (x < 0) printf("x is less than 0\n"); + else if (x == 7) printf("x is 7\n"); + else if (x == 9) printf("x is 9\n"); + else if (x == 33) printf("x is 33\n"); + else if (x == 6) printf("x is 6\n"); +} + +int main() +{ + simple_ifs(); + complex_ifs(); +} diff --git a/examples/ex-nested-function/main.c b/examples/ex-nested-function/main.c new file mode 100644 index 0000000..96710d9 --- /dev/null +++ b/examples/ex-nested-function/main.c @@ -0,0 +1,17 @@ +#include + +int main() +{ + int main_scope_int = 42; + + void nested_func() + { + printf("NESTED FUNCTION %d\n", main_scope_int); + return; + } + + printf("MAIN FUNCTION\n"); + nested_func(); + + return 0; +} diff --git a/examples/ex-only-main/main.c b/examples/ex-only-main/main.c new file mode 100644 index 0000000..f126ee5 --- /dev/null +++ b/examples/ex-only-main/main.c @@ -0,0 +1,9 @@ +#include + +int main() +{ + int c = 0; + scanf("%d", &c); + printf("echoing %d\n", c); + return 0; +} diff --git a/examples/ex-sizeof/main.c b/examples/ex-sizeof/main.c new file mode 100644 index 0000000..1141297 --- /dev/null +++ b/examples/ex-sizeof/main.c @@ -0,0 +1,87 @@ +#include +#include + +int intcmp(const void* a, const void* b) { + int ia = *(const int*)a; + int ib = *(const int*)b; + return ia < ib; +} + +#define BLOCK_SIZE 8 + +int forgot_parens() +{ + size_t capacity = BLOCK_SIZE; + size_t size = 0; + + char *str = (char *)malloc(capacity * sizeof(char)); + while (1) { + char c = getchar(); + if (c == EOF) break; + + if (size == capacity) { + str = (char *)realloc(str, capacity + BLOCK_SIZE * sizeof(char)); + capacity += BLOCK_SIZE; + } + + str[size++] = c; + } +} + +int wrong_way() +{ + int len = 0; + int *arr = NULL; + + scanf("%d", &len); + arr = (int*)malloc(4 * len); // this is bad + + for (int i = 0; i < len; i++) { + scanf("%d", &arr[i]); + } + + qsort(arr, len, sizeof(int), intcmp); + + len /= 2; + arr = (int*)realloc(arr, len * 4); // this is bad + + for (int i = 0; i < len; i++) { + printf("%d", arr[i]); + } + putchar('\n'); + + free(arr); + return 0; +} + +int right_way() +{ + int len = 0; + int *arr = NULL; + + scanf("%d", &len); + arr = (int*)malloc(sizeof(int) * len); + + for (int i = 0; i < len; i++) { + scanf("%d", &arr[i]); + } + + qsort(arr, len, sizeof(int), intcmp); + + len /= 2; + arr = (int*)realloc(arr, len * sizeof(int)); + + for (int i = 0; i < len; i++) { + printf("%d", arr[i]); + } + putchar('\n'); + + free(arr); + return 0; +} + +int main() +{ + int result = right_way(); + return right_way(); +} diff --git a/examples/ex-string-compare/main.c b/examples/ex-string-compare/main.c new file mode 100644 index 0000000..0f10cbb --- /dev/null +++ b/examples/ex-string-compare/main.c @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +#define INITIAL_SIZE 8 + +void wide_strings() +{ + wchar_t str1[32] = L"Hello"; + wchar_t *str2 = wcsdup(L"World"); + + if (str2 == NULL) { + abort(); + } + + if (str1 == str2) { + puts("One"); + } + if (str1 == L"String") { + puts("Two"); + } + if (L"String" == str2) { + puts("Three"); + } + if (L"Str1" == L"Str2") { + puts("Four"); + } + + free(str2); +} + +void compare_const_and_const() +{ + if ("String1" == "String2") { + puts("It never happens"); + } + else if ("String3" != "String4") { + puts("It always happens"); + } +} + +void compare_const_and_not_const() +{ + char *str = (char*)malloc(1024 * sizeof(char)); + + if (str == NULL) { + abort(); + } + + scanf("%1023s", str); + if (str == "Bad!") { + puts("Something"); + } + else if ("Awful!" == str) { + puts("Something else"); + } + + free(str); +} + +int main() +{ + char buf[128]; + int arr_size = 0; + char **arr = NULL; + + scanf("%d", &arr_size); + arr = (char**)malloc(arr_size * sizeof(char*)); + + for (int i = 0; i < arr_size; i++) { + scanf("%127s", buf); + arr[i] = strdup(buf); + } + + int cnt = 1; + for (int i = 1; i < arr_size; i++) { + if (arr[0] == arr[i]) // bad + cnt++; + } + + printf("%d\n", cnt); + + for (int i = 0; i < arr_size; i++) { + free(arr[i]); + } + free(arr); + + return 0; +} diff --git a/examples/ex1.c b/examples/ex1.c deleted file mode 100644 index 31d7535..0000000 --- a/examples/ex1.c +++ /dev/null @@ -1,41 +0,0 @@ -#include - -int main() -{ - int x = 0; - scanf("%d", &x); - - if (x == 0) { - puts("Zero"); - } - if (x == 1) { - puts("One"); - } - if (x == 2) { - puts("Two"); - } - if (x == 3) { - puts("Three"); - } - if (x == 4) { - puts("Four"); - } - if (x == 5) { - puts("Five"); - } - if (x == 6) { - puts("Six"); - } - if (x == 7) { - puts("Seven"); - } - if (x == 8) { - puts("Eight"); - } - if (x == 9) { - puts("Nine"); - } - else { - puts("Something else"); - } -} \ No newline at end of file diff --git a/examples/ex2.c b/examples/ex2.c deleted file mode 100644 index acb4c0f..0000000 --- a/examples/ex2.c +++ /dev/null @@ -1,41 +0,0 @@ -#include - -int main() -{ - int x = 0; - scanf("%d", &x); - - if (x == 0) { - puts("Zero"); - } - else if (x == 1) { - puts("One"); - } - else if (x == 2) { - puts("Two"); - } - else if (x == 3) { - puts("Three"); - } - else if (x == 4) { - puts("Four"); - } - else if (x == 5) { - puts("Five"); - } - else if (x == 6) { - puts("Six"); - } - else if (x == 7) { - puts("Seven"); - } - else if (x == 8) { - puts("Eight"); - } - else if (x == 9) { - puts("Nine"); - } - else { - puts("Something else"); - } -} \ No newline at end of file diff --git a/examples/make/ex-makefile/Makefile b/examples/make/ex-makefile/Makefile new file mode 100644 index 0000000..040d802 --- /dev/null +++ b/examples/make/ex-makefile/Makefile @@ -0,0 +1,17 @@ +.PHONY: all clean + +all: prog + +prog: stuff.o main.o + gcc -o prog stuff.o main.o + +stuff.o: src/stuff.c include/obfuscation.h include/stuff.h + gcc -c -I ./include src/stuff.c + +main.o: src/main.c include/stuff.h + gcc -c -I ./include src/main.c + +clean: + rm -f stuff.o + rm -f main.o + rm -f prog diff --git a/examples/make/ex-makefile/include/obfuscation.h b/examples/make/ex-makefile/include/obfuscation.h new file mode 100644 index 0000000..53c5fdf --- /dev/null +++ b/examples/make/ex-makefile/include/obfuscation.h @@ -0,0 +1 @@ +#include diff --git a/examples/make/ex-makefile/include/stuff.h b/examples/make/ex-makefile/include/stuff.h new file mode 100644 index 0000000..054a25f --- /dev/null +++ b/examples/make/ex-makefile/include/stuff.h @@ -0,0 +1,6 @@ +#ifndef TEST_STUFF_H +#define TEST_STUFF_H + +void do_stuff(int x); + +#endif diff --git a/examples/make/ex-makefile/src/main.c b/examples/make/ex-makefile/src/main.c new file mode 100644 index 0000000..8717854 --- /dev/null +++ b/examples/make/ex-makefile/src/main.c @@ -0,0 +1,10 @@ +#include +#include "stuff.h" + +int main() +{ + int x = 0; + scanf("%d", &x); + + do_stuff(x); +} \ No newline at end of file diff --git a/examples/ex3.c b/examples/make/ex-makefile/src/stuff.c similarity index 88% rename from examples/ex3.c rename to examples/make/ex-makefile/src/stuff.c index dc36117..9389813 100644 --- a/examples/ex3.c +++ b/examples/make/ex-makefile/src/stuff.c @@ -1,10 +1,7 @@ -#include - -int main() -{ - int x = 0; - scanf("%d", &x); +#include "obfuscation.h" +#include "stuff.h" +void do_stuff(int x) { if (x == 0) { puts("Zero"); } diff --git a/examples/test-gcc.sh b/examples/test-gcc.sh new file mode 100755 index 0000000..d6d96bf --- /dev/null +++ b/examples/test-gcc.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +if [ $# -eq 0 ]; then + echo "specify directory" + exit 1 +fi + +WORK_DIRECTORY=$1 +SRC_FILE=$(find $WORK_DIRECTORY -name "*.c" && find $WORK_DIRECTORY -name "*.cpp") + +oclint $SRC_FILE ${@:2} -- diff --git a/examples/test-makefile.sh b/examples/test-makefile.sh new file mode 100755 index 0000000..7a253a8 --- /dev/null +++ b/examples/test-makefile.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +if [ $# -eq 0 ]; then + echo "specify directory" + exit 1 +fi + +WORK_DIRECTORY=$1 +TMP_DIRECTORY=./tester-tmp + +mkdir $TMP_DIRECTORY +cp -r $WORK_DIRECTORY/* $TMP_DIRECTORY + +bear -- make -C $TMP_DIRECTORY > /dev/null + +oclint ${@:2} \ + $(find $TMP_DIRECTORY -name "*.c") \ + $(find $TMP_DIRECTORY -name "*.h") \ + $(find $TMP_DIRECTORY -name "*.cpp") + +rm -rf $TMP_DIRECTORY +rm -rf compile_commands.json diff --git a/install b/install deleted file mode 100755 index 70206d0..0000000 --- a/install +++ /dev/null @@ -1,7 +0,0 @@ -if [ $# -eq 0 ]; then - echo "usage: ./install [OCINT_REPO_DIRECTORY]" - exit -fi - -cp "$1"/build/oclint-release/bin/oclint* /usr/local/bin/ -cp -rp "$1"/build/oclint-release/lib/* /usr/local/lib/ \ No newline at end of file diff --git a/oclint-rules/rules/etu/CMakeLists.txt b/oclint-rules/rules/etu/CMakeLists.txt deleted file mode 100644 index 5f00b4c..0000000 --- a/oclint-rules/rules/etu/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ - -SET(BuildTooManyConsecutiveIfStatementsRule TooManyConsecutiveIfStatements) -BUILD_DYNAMIC_RULES("${BuildTooManyConsecutiveIfStatementsRule}") diff --git a/oclint-rules/rules/etu/TooManyConsecutiveIfStatementsRule.cpp b/oclint-rules/rules/etu/TooManyConsecutiveIfStatementsRule.cpp deleted file mode 100644 index d931ed4..0000000 --- a/oclint-rules/rules/etu/TooManyConsecutiveIfStatementsRule.cpp +++ /dev/null @@ -1,100 +0,0 @@ -#include "oclint/AbstractASTVisitorRule.h" -#include "oclint/RuleConfiguration.h" -#include "oclint/RuleSet.h" -#include "oclint/util/StdUtil.h" - -using namespace std; -using namespace clang; -using namespace oclint; - -class TooManyConsecutiveIfStatementsRule : public AbstractASTVisitorRule -{ -private: - size_t getIfComplexity(IfStmt *if_stmt) - { - Stmt *else_stmt = if_stmt->getElse(); - - if (else_stmt != nullptr && else_stmt->getStmtClass() == Stmt::IfStmtClass) - return 1 + getIfComplexity(reinterpret_cast(else_stmt)); - - return 1; - } - -public: - virtual void setUp() override {} - virtual void tearDown() override {} - - virtual const string name() const override - { - return "too many consecutive if statements"; - } - - virtual int priority() const override - { - return 2; - } - - virtual const string category() const override - { - return "etu"; - } - -#ifdef DOCGEN - virtual const std::string since() const override - { - return "22.02 etu"; - } - - virtual const std::string description() const override - { - return "..."; - } - - virtual const std::string example() const override - { - return R"rst( -.. code-block:: cpp - - void example() - { - // TODO: modify the example for this rule. - } - )rst"; - } -#endif - - bool VisitCompoundStmt(CompoundStmt *node) - { - Stmt *first_if = nullptr; - size_t cur_complexity = 0; - - for (Stmt::child_iterator it = node->child_begin(); it != node->child_end(); it++) { - Stmt *stmt = (*it); - - if (stmt->getStmtClass() == Stmt::IfStmtClass) { - if (first_if == nullptr) - first_if = stmt; - - cur_complexity += getIfComplexity(dyn_cast(stmt)); - } - else { - if (first_if == nullptr) continue; - - if (cur_complexity > 5) { - addViolation(first_if, this, toString(cur_complexity) + " consecutive if statements is too many."); - } - - first_if = nullptr; - cur_complexity = 0; - } - } - - if (first_if != nullptr && cur_complexity > 5) { - addViolation(first_if, this, toString(cur_complexity) + " consecutive if statements is too many."); - } - - return true; - } -}; - -static RuleSet rules(new TooManyConsecutiveIfStatementsRule()); diff --git a/oclint-rules/test/etu/CMakeLists.txt b/oclint-rules/test/etu/CMakeLists.txt deleted file mode 100644 index 410df54..0000000 --- a/oclint-rules/test/etu/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ - -BUILD_TEST(TooManyConsecutiveIfStatementsRuleTest -TooManyConsecutiveIfStatementsRuleTest.cpp) diff --git a/oclint-rules/test/etu/TooManyConsecutiveIfStatementsRuleTest.cpp b/oclint-rules/test/etu/TooManyConsecutiveIfStatementsRuleTest.cpp deleted file mode 100644 index 27bab3f..0000000 --- a/oclint-rules/test/etu/TooManyConsecutiveIfStatementsRuleTest.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#include "TestRuleOnCode.h" -#include "rules/etu/TooManyConsecutiveIfStatementsRule.cpp" - -TEST(TooManyConsecutiveIfStatementsRuleTest, PropertyTest) -{ - TooManyConsecutiveIfStatementsRule rule; - EXPECT_EQ(2, rule.priority()); - EXPECT_EQ("too many consecutive if statements", rule.name()); - EXPECT_EQ("etu", rule.category()); -} - -TEST(TooManyConsecutiveIfStatementsRuleTest, FirstFailingTest) -{ - EXPECT_FALSE("Start writing a new test"); -} diff --git a/rules/GlobalVariableRule.cpp b/rules/GlobalVariableRule.cpp new file mode 100644 index 0000000..2c45268 --- /dev/null +++ b/rules/GlobalVariableRule.cpp @@ -0,0 +1,53 @@ +#include "oclint/AbstractASTVisitorRule.h" +#include "oclint/RuleConfiguration.h" +#include "oclint/RuleSet.h" + +namespace oclint { + class GlobalVariableRule : public oclint::AbstractASTVisitorRule + { + public: + virtual void tearDown() override {} + + virtual const std::string name() const override + { + return "declaration of global variable"; + } + + virtual int priority() const override + { + return 2; + } + + virtual const std::string category() const override + { + return "etu"; + } + + #ifdef DOCGEN + virtual const std::string since() const override + { + return "22.02 etu"; + } + + virtual const std::string description() const override + { + return "..."; + } + + virtual const std::string example() const override + { + return R"rst(for example see examples/ex-global/main.c + )rst"; + } + #endif + + bool VisitVarDecl(clang::VarDecl *var_decl) + { + if (var_decl->hasGlobalStorage()) + addViolation(var_decl, this, "using static local or global variable '" + var_decl->getNameAsString() + '\''); + return true; + } + }; +} + +static oclint::RuleSet rules(new oclint::GlobalVariableRule()); diff --git a/rules/MallocSizeofRule.cpp b/rules/MallocSizeofRule.cpp new file mode 100644 index 0000000..d6b2ce7 --- /dev/null +++ b/rules/MallocSizeofRule.cpp @@ -0,0 +1,143 @@ +#include "oclint/AbstractASTVisitorRule.h" +#include "oclint/RuleConfiguration.h" +#include "oclint/RuleSet.h" +#include "clang/AST/Expr.h" +#include "clang/AST/Stmt.h" + +namespace oclint { + class MallocSizeofRule : public oclint::AbstractASTVisitorRule + { + private: + public: + virtual void setUp() override {} + virtual void tearDown() override {} + + virtual const std::string name() const override + { + return "no sizeof inside malloc/realloc"; + } + + virtual int priority() const override + { + return 2; + } + + virtual const std::string category() const override + { + return "etu"; + } + + #ifdef DOCGEN + virtual const std::string since() const override + { + return "22.02 etu"; + } + + virtual const std::string description() const override + { + return "You shouldn`t rely on fixed integers when allocating memory. sizeof(typename) is the right way to go."; + } + + virtual const std::string example() const override + { + return R"rst( + .. code-block:: cpp + #include + #include + + void example() + { + int len = 0; + int *arr = NULL; + + scanf("%d", &len); + arr = malloc(4 * len); // this is bad + + for (int i = 0; i < len; i++) { + scanf("%d", &arr[i]); + } + + // ... + + free(arr); + return 0; + } + )rst"; + } + #endif + + void ReportSizeofViolation(clang::CallExpr *call_expr) + { + addViolation(call_expr, this, "The size of the allocated memory should be \"sizeof(typename) * SOMETHING\""); + } + + bool CheckSizeof(clang::Expr *expr) + { + if (expr->getStmtClass() == clang::Stmt::ImplicitCastExprClass) { + expr = clang::dyn_cast(expr)->getSubExpr(); + } + + if (expr->getStmtClass() != clang::Stmt::UnaryExprOrTypeTraitExprClass) { + return false; + } + + auto *unary = clang::dyn_cast(expr); + clang::UnaryExprOrTypeTrait kind = unary->getKind(); + if (kind == clang::UnaryExprOrTypeTrait::UETT_SizeOf) + return true; + + return false; + } + + bool VisitCallExpr(clang::CallExpr *call_expr) + { + clang::Expr *node = call_expr->getCallee(); + + if (node == nullptr) // is it even possible? anyway + return true; + + if (node->getStmtClass() == clang::Stmt::ImplicitCastExprClass) { + node = clang::dyn_cast(node)->getSubExpr(); + } + + if (node->getStmtClass() != clang::Stmt::DeclRefExprClass) + return true; + + auto *decl_ref = clang::dyn_cast(node); + std::string name = decl_ref->getNameInfo().getName().getAsString(); + + int arg_index; + if (name == "malloc") + arg_index = 0; + else if (name == "realloc") + arg_index = 1; + else + return true; + + unsigned int n_args = call_expr->getNumArgs(); + if (arg_index >= n_args) // unlikely to happen. but check it anyway + return true; + + clang::Expr *arg = call_expr->getArg(arg_index); + + if (arg->getStmtClass() != clang::Stmt::BinaryOperatorClass) { + ReportSizeofViolation(call_expr); + return true; + } + + auto binop = clang::dyn_cast(arg); + clang::BinaryOperator::Opcode opcode = binop->getOpcode(); + if (opcode != clang::BinaryOperator::Opcode::BO_Mul) { + ReportSizeofViolation(call_expr); + return true; + } + + if (!CheckSizeof(binop->getLHS()) && !CheckSizeof(binop->getRHS())) + ReportSizeofViolation(call_expr); + + return true; + } + }; +} + +static oclint::RuleSet rules(new oclint::MallocSizeofRule()); diff --git a/rules/OnlyMainFunctionRule.cpp b/rules/OnlyMainFunctionRule.cpp new file mode 100644 index 0000000..00f4d1f --- /dev/null +++ b/rules/OnlyMainFunctionRule.cpp @@ -0,0 +1,61 @@ +#include "oclint/AbstractASTVisitorRule.h" +#include "oclint/RuleConfiguration.h" +#include "oclint/RuleSet.h" +#include "clang/AST/Decl.h" + +namespace oclint { + class OnlyMainFunctionRule : public oclint::AbstractASTVisitorRule + { + private: + std::vector decls; + public: + virtual void tearDown() override + { + if (decls.size() == 1) { + addViolation(decls[0], this, "all logic inside single function"); + } + } + + virtual const std::string name() const override + { + return "only main function"; + } + + virtual int priority() const override + { + return 2; + } + + virtual const std::string category() const override + { + return "etu"; + } + + #ifdef DOCGEN + virtual const std::string since() const override + { + return "22.02 etu"; + } + + virtual const std::string description() const override + { + return "consider breaking your main function into pieces"; + } + + virtual const std::string example() const override + { + return R"rst(for example see examples/ex-only-main/main.c + )rst"; + } + #endif + + bool VisitFunctionDecl(clang::FunctionDecl *func_decl) + { + decls.push_back(func_decl); + return true; + } + }; + +} + +static oclint::RuleSet rules(new oclint::OnlyMainFunctionRule()); diff --git a/rules/StringCompareRule.cpp b/rules/StringCompareRule.cpp new file mode 100644 index 0000000..97a6a2f --- /dev/null +++ b/rules/StringCompareRule.cpp @@ -0,0 +1,121 @@ +#include "oclint/AbstractASTVisitorRule.h" +#include "oclint/RuleConfiguration.h" +#include "oclint/RuleSet.h" +#include "clang/Basic/Specifiers.h" + +namespace oclint { + class StringCompareRule : public oclint::AbstractASTVisitorRule + { + private: + using Opcode = clang::BinaryOperator::Opcode; + + public: + virtual void setUp() override {} + virtual void tearDown() override {} + + virtual const std::string name() const override + { + return "using operators to compare strings"; + } + + virtual int priority() const override + { + return 2; + } + + virtual const std::string category() const override + { + return "etu"; + } + + #ifdef DOCGEN + virtual const std::string since() const override + { + return "22.02 etu"; + } + + virtual const std::string description() const override + { + return "In some cases, the '==' operator will give the correct result " + "(if a pointer to a constant string is written to a variable and " + "then compared with this constant string), but in a general " + "case it will inevitably lead to an error"; + } + + virtual const std::string example() const override + { + return R"rst( + .. code-block:: cpp + #include + #include + #include + + #define INITIAL_SIZE 8 + + void example() + { + int arr_size = 0; + char **arr = NULL; + + // ... (input arr and arr_size) + + int cnt = 1; + for (int i = 0; i < arr_size; i++) { + if (arr[0] == arr[i]) // bad + cnt++; + } + + printf("%d\n", cnt); + + // ... (free memory) + + return; + })rst"; + #endif + + bool IsString(clang::Expr *expr) + { + clang::QualType type = expr->getType(); + if (!type->isPointerType()) + return false; + + type = type->getPointeeType(); + return type->isAnyCharacterType() || type.getAsString() == "wchar_t"; + } + + bool IsNull(clang::Expr *expr) + { + return expr->isNullPointerConstant(*this->_carrier->getASTContext(), clang::Expr::NPC_ValueDependentIsNotNull); + } + + const char *OpcodeAsString(Opcode opcode) + { + switch (opcode) { + case Opcode::BO_EQ: + return "'=='"; + case Opcode::BO_NE: + return "'!='"; + default: + return "???"; + } + } + + bool VisitBinaryOperator(clang::BinaryOperator *binop) + { + Opcode opcode = binop->getOpcode(); + + if (opcode != Opcode::BO_EQ && opcode != Opcode::BO_NE) + return true; + + clang::Expr *left = binop->getLHS(); + clang::Expr *right = binop->getRHS(); + + if (!IsNull(left) && !IsNull(right) && (IsString(left) || IsString(right))) + addViolation(binop, this, std::string("comparing 2 strings by using ") + OpcodeAsString(opcode) + "."); + + return true; + } + }; +} + +static oclint::RuleSet rules(new oclint::StringCompareRule()); diff --git a/rules/TooLongIfSequenceRule.cpp b/rules/TooLongIfSequenceRule.cpp new file mode 100644 index 0000000..04166f7 --- /dev/null +++ b/rules/TooLongIfSequenceRule.cpp @@ -0,0 +1,173 @@ +#include "oclint/AbstractASTVisitorRule.h" +#include "oclint/RuleConfiguration.h" +#include "oclint/RuleSet.h" +#include + +namespace oclint { + class TooLongIfSequenceRule : public oclint::AbstractASTVisitorRule + { + private: + int max_if_sequence_len; + clang::Stmt *first_if; + size_t cur_complexity; + + struct { + bool binary_operator : 1; + bool function_call : 1; + } special_check_flags; + + public: + virtual void setUp() override + { + this->max_if_sequence_len = oclint::RuleConfiguration::intForKey("MAX_IF_SEQUENCE_LEN", 5); + this->special_check_flags.binary_operator = oclint::RuleConfiguration::intForKey("CHECK_BINARY_OPERATOR", 1); + this->special_check_flags.function_call = oclint::RuleConfiguration::intForKey("CHECK_FUNCTION_CALL", 1); + } + + virtual void tearDown() override {} + + virtual const std::string name() const override + { + return "too long if sequence"; + } + + virtual int priority() const override + { + return 2; + } + + virtual const std::string category() const override + { + return "etu"; + } + + #ifdef DOCGEN + virtual const std::string since() const override + { + return "22.02 etu"; + } + + virtual const std::string description() const override + { + return "Such if sequences are difficult to analyze and may be rewritten more efficiently."; + } + + virtual const std::string example() const override + { + return R"rst(See example in examples/ex-if)rst"; + } + #endif + + bool specialStatementCheck(clang::Stmt *first, clang::Stmt *second) + { + using clang::dyn_cast; + + switch (first->getStmtClass()) { + case clang::Stmt::BinaryOperatorClass: + if (!this->special_check_flags.binary_operator) return true; + return dyn_cast(first)->getOpcode() == dyn_cast(second)->getOpcode(); + + case clang::Stmt::CallExprClass: + if (!this->special_check_flags.function_call) return true; + return dyn_cast(first)->getCalleeDecl() == dyn_cast(second)->getCalleeDecl(); + default: + return true; + } + } + + bool areStatementsSimilar(clang::Stmt *first, clang::Stmt *second) + { + std::stack stack1; + std::stack stack2; + stack1.push(first); + stack2.push(second); + + while (!stack1.empty() &&!stack2.empty()) + { + clang::Stmt *stmt1 = stack1.top(); stack1.pop(); + clang::Stmt *stmt2 = stack2.top(); stack2.pop(); + + if (stmt1->getStmtClass() != stmt2->getStmtClass()) + return false; + if (!specialStatementCheck(stmt1, stmt2)) + return false; + + clang::Stmt::child_iterator it1 = stmt1->child_begin(); + clang::Stmt::child_iterator it2 = stmt2->child_begin(); + while (it1 != stmt1->child_end() && it2 != stmt2->child_end()) { + stack1.push(*it1); + stack2.push(*it2); + ++it1; + ++it2; + } + + if (it1 != stmt1->child_end() || it2 != stmt2->child_end()) + return false; + } + + return true; + } + + void endSequence() + { + if (first_if == nullptr) + return; + + if (cur_complexity > this->max_if_sequence_len) { + addViolation( + this->first_if, + this, + std::to_string(this->cur_complexity) + " consecutive if statements is too many." + ); + } + + first_if = nullptr; + cur_complexity = 0; + } + + void visitIfElseChain(clang::IfStmt *head) + { + clang::Stmt *chain = head; + while (true) { + if (first_if == nullptr) + first_if = chain; + + if (chain->getStmtClass() != clang::Stmt::IfStmtClass) { + break; + } + + clang::IfStmt *if_stmt = clang::dyn_cast(chain); + if (!areStatementsSimilar(clang::dyn_cast(first_if)->getCond(), if_stmt->getCond())) { + this->endSequence(); + } + + this->cur_complexity++; + + if (if_stmt->getElse() == nullptr) { + break; + } + chain = if_stmt->getElse(); + } + } + + bool VisitCompoundStmt(clang::CompoundStmt *node) + { + this->first_if = nullptr; + this->cur_complexity = 0; + + for (auto &stmt : node->children()) { + if (stmt->getStmtClass() == clang::Stmt::IfStmtClass) { + this->visitIfElseChain(clang::dyn_cast(stmt)); + } + else { + this->endSequence(); + } + } + + this->endSequence(); + return true; + } + }; +} + +static oclint::RuleSet rules(new oclint::TooLongIfSequenceRule()); diff --git a/scripts/build.sh b/scripts/build.sh new file mode 100755 index 0000000..af606bc --- /dev/null +++ b/scripts/build.sh @@ -0,0 +1,3 @@ +#!/bin/bash + +docker build -t oclint_checker ./ diff --git a/scripts/build_rules.sh b/scripts/build_rules.sh new file mode 100755 index 0000000..03435ad --- /dev/null +++ b/scripts/build_rules.sh @@ -0,0 +1,9 @@ +#!/bin/bash + +set -e + +mkdir build +cd build +cmake ../ +cmake --build . +cmake --install . diff --git a/scripts/entry.sh b/scripts/entry.sh new file mode 100755 index 0000000..7b2e4d1 --- /dev/null +++ b/scripts/entry.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +set -e + +PARAM_REPORT_TYPE="${PARAM_REPORT_TYPE:-text}" + +cp -r solution tmp +chmod -R +x tmp +cd tmp + +src_files=$(find . -name "*.c" && find . -name "*.cpp") +echo "Files to check:" +for file in $src_files; do + echo " * " $file +done + +makefile=$(find . -name "Makefile") + +if [ -z "$makefile" ]; then + oclint $src_files $@ --report-type=${PARAM_REPORT_TYPE} -- +else + bear -- make > /dev/null + oclint $src_files $@ --report-type=${PARAM_REPORT_TYPE} +fi diff --git a/scripts/get_oclint.sh b/scripts/get_oclint.sh new file mode 100755 index 0000000..78a50c0 --- /dev/null +++ b/scripts/get_oclint.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -e + +OCLINT_22_02_COMMIT=d776db51c8574df406b2b0dc1b43b0b9b2d86d34 +git clone https://github.com/oclint/oclint.git oclint +cd oclint +git reset --hard $OCLINT_22_02_COMMIT + +cd oclint-scripts +./make + +cd .. +cp -r build/oclint-release/lib/* /usr/local/lib/ +cp -r build/oclint-release/bin/* /usr/local/bin/