Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Clang tidy

6,372 views

Published on

cpprussia - clang-tidy

Published in: Engineering
  • Be the first to comment

  • Be the first to like this

Clang tidy

  1. 1. Clang-tidy: путешествие внутрь AST С++ Юрий Ефимочев
  2. 2. Обо мне Solarwinds Lead Developer MSP Backup & Recovery
  3. 3. Что такое clang-tidy? Инструмент для статического анализа кода и поиска типичных ошибок программирования
  4. 4. Встроенные правила(~200) ● ClangAnalyzer ● Readability/Modernize/Performance ● CppCoreGuidelines ● LLVM/Google-style
  5. 5. Пример запуска class TestClass { public: TestClass() : m_B(), m_C() { } private: int m_A; int m_B; int m_C; };
  6. 6. Пример запуска $ clang-tidy -checks="cppcoreguidelines-*" ./test.cpp 1 warning generated. ./test.cpp:4:5: warning: constructor does not initialize these fields: m_A [cppcoreguidelines-pro-type-member-init] TestClass() : ^ m_A(),
  7. 7. Code review
  8. 8. Цели code review ● Соответствие реализации задаче ● Поиск ошибок и неоптимальностей реализации ● Соответствие guidelines и best practices
  9. 9. Clang-tidy и code review? Платформа для построения собственных инструментов статического анализа кода
  10. 10. Расширяемость ● Полный доступ к AST и препроцессору ● Модульная архитектура ● Инфраструктура для разработки и тестирования
  11. 11. Что необходимо для использования? ● Код должен собираться clang ● Необходима compilation database
  12. 12. Алгоритм разработки правила 1. Подготовить пример 2. Посмотреть в AST 3. ??? 4. Profit
  13. 13. Пример 1 #include <iostream> class TestClass { public: static int const Constant; int m_public; private: int m_private; }; struct TestStruct { int Field; }; // style: class public field
  14. 14. Clang-check: визуализация AST $ clang-check -ast-dump -ast-dump-filter="Test" ./test.cpp Dumping TestClass: CXXRecordDecl 0xaf665da8 <test.cpp:3:1, line:12:1> line:3:7 class TestClass definition |-CXXRecordDecl 0xaf665ec0 <col:1, col:7> col:7 implicit class TestClass |-AccessSpecDecl 0xaf665f50 <line:5:1, col:7> col:1 public |-VarDecl 0xaf665f88 <line:6:5, col:22> col:22 Constant 'const int' static |-FieldDecl 0xaf665ff8 <line:8:5, col:9> col:9 m_public 'int' |-AccessSpecDecl 0xaf666040 <line:10:1, col:8> col:1 private `-FieldDecl 0xaf666078 <line:11:5, col:9> col:9 m_private 'int' Dumping TestStruct: CXXRecordDecl 0xaf6660c0 <test.cpp:14:1, line:17:1> line:14:8 struct TestStruct definition |-CXXRecordDecl 0xaf6661d0 <col:1, col:8> col:8 implicit struct TestStruct `-FieldDecl 0xaf666270 <line:16:5, col:9> col:9 Field 'int'
  15. 15. AST Nodes ● Decl ● Stmt ● Type
  16. 16. AST Nodes ● Decl CXXRecordDecl CXXMethodDecl VarDecl ● Stmt ● Type
  17. 17. AST Nodes ● Decl ● Stmt ifStmt CXXTryStmt BinaryOperator ● Type
  18. 18. AST Nodes ● Decl ● Stmt ● Type PointerType ReferenceType LValueReferenceType
  19. 19. Пример 1: AST $ clang-check -ast-dump -ast-dump-filter="Test" ./test.cpp Dumping TestClass: CXXRecordDecl 0xaf665da8 <test.cpp:3:1, line:12:1> line:3:7 class TestClass definition |-CXXRecordDecl 0xaf665ec0 <col:1, col:7> col:7 implicit class TestClass |-AccessSpecDecl 0xaf665f50 <line:5:1, col:7> col:1 public |-VarDecl 0xaf665f88 <line:6:5, col:22> col:22 Constant 'const int' static |-FieldDecl 0xaf665ff8 <line:8:5, col:9> col:9 m_public 'int' |-AccessSpecDecl 0xaf666040 <line:10:1, col:8> col:1 private `-FieldDecl 0xaf666078 <line:11:5, col:9> col:9 m_private 'int' Dumping TestStruct: CXXRecordDecl 0xaf6660c0 <test.cpp:14:1, line:17:1> line:14:8 struct TestStruct definition |-CXXRecordDecl 0xaf6661d0 <col:1, col:8> col:8 implicit struct TestStruct `-FieldDecl 0xaf666270 <line:16:5, col:9> col:9 Field 'int'
  20. 20. AST Matchers ● Node Matchers cxxRecordDecl, cxxMethodDecl, namespaceDecl, ifStmt, ... ● Narrowing Matchers isConstant, isFinal, hasName, matchesName, unless, ... ● Traversal Matchers hasDescendant, hasParent, hasBody, ... cxxMethodDecl(matchesName(“Get.*”), hasParent(cxxRecordDecl(isStruct()))
  21. 21. Пример 1 clang-query> match fieldDecl() Match #1: /home/yury/Projects/test.cpp:8:5: note: "root" binds here int m_public; ^~~~~~~~~~~~ Match #2: /home/yury/Projects/test.cpp:11:5: note: "root" binds here int m_private; ^~~~~~~~~~~~~ Match #3: /home/yury/Projects/test.cpp:16:5: note: "root" binds here int Field; ^~~~~~~~~ 3 matches.
  22. 22. Пример 1 clang-query> match fieldDecl(isPublic()) Match #1: /home/yury/Projects/test.cpp:8:5: note: "root" binds here int m_public; ^~~~~~~~~~~~ Match #2: /home/yury/Projects/test.cpp:16:5: note: "root" binds here int Field; ^~~~~~~~~ 2 matches.
  23. 23. Пример 1 clang-query> match fieldDecl(isPublic(), hasParent(cxxRecordDecl(isClass()))) Match #1: /home/yury/Projects/test.cpp:8:5: note: "root" binds here int m_public; ^~~~~~~~~~~~ 1 match.
  24. 24. Добавление правила $ ./add_new_check.py misc field-visibility Updating ./misc/CMakeLists.txt... Creating ./misc/FieldVisibilityCheck.h... Creating ./misc/FieldVisibilityCheck.cpp... Updating ./misc/MiscTidyModule.cpp... Creating ../test/clang-tidy/misc-field-visibility.cpp... Creating ../docs/clang-tidy/checks/misc-field-visibility.rst... Updating ../docs/clang-tidy/checks/list.rst... Done. Now it's your turn!
  25. 25. Пример 1: шаблон реализации class FieldVisibilityCheck : public ClangTidyCheck { public: FieldVisibilityCheck(StringRef name, ClangTidyContext* context) : ClangTidyCheck(name, context) { } private: void registerMatchers(ast_matchers::MatchFinder* finder) override { } void check(ast_matchers::MatchFinder::MatchResult const& result) override { } };
  26. 26. Пример 1: реализация class FieldVisibilityCheck : public ClangTidyCheck { public: FieldVisibilityCheck(StringRef name, ClangTidyContext* context) : ClangTidyCheck(name, context) { } private: void registerMatchers(ast_matchers::MatchFinder* finder) override { finder->addMatcher(fieldDecl(isPublic(), hasParent(cxxRecordDecl(isClass()))).bind("field"), this); } void check(ast_matchers::MatchFinder::MatchResult const& result) override { FieldDecl const& field = *result.Nodes.getNodeAs<FieldDecl const>("field"); diag(field.getLocStart(), "Class field should be private"); } };
  27. 27. Пример 1: результат $ clang-tidy -checks="misc-field-visibility-check" ./test.cpp 1 warning generated. ./test.cpp:8:5: warning: Class field should be private [misc-field-visibility-check] int m_public; ^
  28. 28. Пример 2 int Function(int a) { // ... a = something; // ... return a; } // style: mutable parameter
  29. 29. Пример 2 struct Test { virtual int VirtualMethod(int a) = 0; int Method(int a, int const b, int& c, int* d) { a = b; return a; } };
  30. 30. Пример 2: AST Dumping Test: CXXRecordDecl 0x46b1b20 <test.cpp:1:1, line:9:1> line:1:8 struct Test definition |-CXXRecordDecl 0x46b1c30 <col:1, col:8> col:8 implicit struct Test |-CXXMethodDecl 0x46b1d90 <line:3:5, col:32> col:9 VirtualMethod 'int (int)' | `-ParmVarDecl 0x46b1cd0 <col:23, col:27> col:27 a 'int' `-CXXMethodDecl 0x46b2140 <line:4:5, line:8:5> line:4:9 Method 'int (int, const int, int &, int *)' |-ParmVarDecl 0x46b1e50 <col:16, col:20> col:20 used a 'int' |-ParmVarDecl 0x46b1ec0 <col:23, col:33> col:33 used b 'const int' |-ParmVarDecl 0x46b1f60 <col:36, col:41> col:41 c 'int &' |-ParmVarDecl 0x46b2000 <col:44, col:49> col:49 d 'int *' `-CompoundStmt 0x46b2320 <line:5:5, line:8:5> |-BinaryOperator 0x46b22a0 <line:6:9, col:13> 'int' lvalue '=' | |-DeclRefExpr 0x46b2238 <col:9> 'int' lvalue ParmVar 0x46b1e50 'a' 'int' | `-ImplicitCastExpr 0x46b2288 <col:13> 'int' <LValueToRValue> | `-DeclRefExpr 0x46b2260 <col:13> 'const int' lvalue ParmVar 0x46b1ec0 'b' 'const int' `-ReturnStmt 0x46b2308 <line:7:9, col:16> `-ImplicitCastExpr 0x46b22f0 <col:16> 'int' <LValueToRValue> `-DeclRefExpr 0x46b22c8 <col:16> 'int' lvalue ParmVar 0x46b1e50 'a' 'int'
  31. 31. Пример 2: реализация void ImmutableParamsCheck::registerMatchers(MatchFinder* finder) { finder->addMatcher( parmVarDecl( hasAncestor(functionDecl(hasBody(stmt()))), unless(anyOf( hasType(isConstQualified()), hasType(referenceType()), hasType(pointerType())))).bind("parameter"), this); } void ImmutableParamsCheck::check(MatchFinder::MatchResult const& result) { ParmVarDecl const& parameter = *result.Nodes.getNodeAs<ParmVarDecl const>("parameter"); SourceLocation const location = parameter.getSourceRange().getEnd(); diag(location, "Consider making constant") << FixItHint::CreateInsertion(location, "const "); }
  32. 32. Пример 2: результат $ clang-tidy -checks="misc-immutable-parameters-check" -fix ./test.cpp 2 warnings generated. ./test.cpp:4:20: warning: Consider making constant [iaso-immutable-params] int Method(int a, int const b, int& c, int* d) ^ const ./test.cpp:4:20: note: FIX-IT applied suggested code changes int Method(int a, int const b, int& c, int* d) ^ clang-tidy applied 1 of 1 suggested fixes.
  33. 33. Пример 2: результат struct Test { virtual int VirtualMethod(int a) = 0; int Method(int a, int const b, int& c, int* d) { a = b; return a; } };
  34. 34. Пример 2: результат struct Test { virtual int VirtualMethod(int a) = 0; int Method(int const a, int const b, int& c, int* d) { a = b; return a; } };
  35. 35. Пример 3 // cxxRecordDecl(unless(matchesName("::[A-Z][a-zA-Z0-9]*$"))) class TestClass { public: // cxxMethodDecl(unless(matchesName("::[A-Z][a-zA-Z0-9]*$"))) // parmVarDecl(unless(matchesName("::[a-z][a-zA-Z0-9]*$"))) void Method(int arg); private: // fieldDecl(unless(matchesName("::m_[a-z][a-zA-Z0-9]*$")), // hasParent(cxxRecordDecl(isClass()))) int m_field; }; struct TestStruct { // fieldDecl(unless(matchesName("::[A-Z][a-zA-Z0-9]*$")), // hasParent(cxxRecordDecl(isStruct()))) int Field; };
  36. 36. Пример 3 // varDecl(hasLocalStorage(), unless(matchesName("::[a-z][a-zA-Z0-9]*$"))) int localVariable; // varDecl(hasGlobalStorage(), // unless(anyOf(matchesName("::s_[a-z][a-zA-Z0-9]*$"), hasType(isConstQualified())))) static int s_staticVariable; // varDecl(hasGlobalStorage(), // unless(anyOf(matchesName("::[A-Z][a-zA-Z0-9]*$"), unless(hasType(isConstQualified()))))) static int const Constant = 42;
  37. 37. Пример 4 Можно ли бросать исключение из деструктора?
  38. 38. Пример 4 class Test { public: ~Test() { throw Exception(); } }; // c++11: terminate
  39. 39. Пример 4 class Test { public: ~Test() noexcept(true) { throw Exception(); } }; // c++11: terminate
  40. 40. Пример 4 class Test { public: ~Test() noexcept(false) { throw Exception(); } };
  41. 41. Пример 4 class Test { public: ~Test() noexcept(false) { throw Exception(); } }; int main() { std::unique_ptr<Test> test(new Test()); test.reset(); } // c++11: terminate
  42. 42. Пример 4 class Parent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() { } }; int main() { std::unique_ptr<Child> test(new Child()); test.reset(); } // c++11: terminate
  43. 43. Пример 4 class Parent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() noexcept(true) { } }; int main() { std::unique_ptr<Child> test(new Child()); test.reset(); } // c++11: terminate
  44. 44. Пример 4 ● Не бросать исключения из деструкторов ● Обозначить бросающие деструкторы ‘noexcept(false)’ ● Не использовать ‘noexcept(true)’ для деструкторов
  45. 45. Пример 4: реализация void ExplicitThrowSpecificationCheck::registerMatchers(MatchFinder* finder) { finder->addMatcher(cxxDestructorDecl().bind("destructor"), this); } void ExplicitThrowSpecificationCheck::check(MatchFinder::MatchResult const& result) { FunctionDecl const& declaration = *result.Nodes.getNodeAs<FunctionDecl>("destructor"); FunctionProtoType const& prototype = *declaration.getType()->getAs<FunctionProtoType>(); bool const destructorCanThrow = prototype.getNoexceptSpec(*result.Context) == FunctionProtoType::NR_Throw; bool const isSpecificationExplicit = declaration.getExceptionSpecSourceRange().isValid(); if (destructorCanThrow && !isSpecificationExplicit) { diag(declaration.getSourceRange().getEnd(), "This destructor should be marked with 'noexcept(false)'"); } if (!destructorCanThrow && isSpecificationExplicit) { diag(declaration.getSourceRange().getEnd(), "Do not mark destructor with 'noexcept(true)'"); } }
  46. 46. Пример 4: результат class Parent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() noexcept(true) { } }; // do not use ‘noexept(true)’
  47. 47. Пример 4: результат class Parent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() { } }; // can throw, mark with ‘noexcept(false)’
  48. 48. Пример 4: результат class Parent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() noexcept(false) { } };
  49. 49. Пример 4 void Function(); struct Object { Object(); bool operator<(Object const& right) const; }; struct Test { Test() { Object a, b; if (a < b) { Function(); } int* p = new int(42); } };
  50. 50. Пример 4: реализация void ThrowFromDestructorCheck::registerMatchers(MatchFinder* finder) { finder->addMatcher(expr(hasAncestor(cxxDestructorDecl().bind("destructor"))).bind("expression"), this); } void ThrowFromDestructorCheck::check(MatchFinder::MatchResult const& result) { FunctionDecl const& declaration = *result.Nodes.getNodeAs<FunctionDecl>("destructor"); FunctionProtoType const& prototype = *declaration.getType()->getAs<FunctionProtoType>(); bool const destructorCanThrow = prototype.getNoexceptSpec(*result.Context) == FunctionProtoType::NR_Throw; if (destructorCanThrow) { return; } Expr const& expression = *result.Nodes.getNodeAs<Expr>("expression"); bool const expressionCanThrow = m_compiler->getSema().canThrow(&expression) != CT_Cannot; if (expressionCanThrow) { diag(expression.getExprLoc(), "Expression can throw exception"); } }
  51. 51. Пример 4: результат void Function(); struct Object { Object(); bool operator<(Object const& right) const; }; struct Test { Test() { Object a, b; if (a < b) { Function(); } int* p = new int(42); } }; // can throw: constructor // can throw: operator < // can throw: Function call // can throw: operator new
  52. 52. Пример 4: результат void Function() noexcept(true); struct Object { Object() noexcept(true); bool operator<(Object const& right) const noexcept(true); }; struct Test { Test() { Object a, b; if (a < b) { Function(); } } };
  53. 53. Clang-tidy: итоги
  54. 54. Clang-tidy: итоги ● Простая реализация сложных проверок ● Автоматизация рутинных проверок ● Отличный способ лучше узнать C++
  55. 55. Полезные ссылки http://clang.llvm.org/extra/clang-tidy http://clang.llvm.org/docs/LibASTMatchersReference.html http://clang.llvm.org/docs/IntroductionToTheClangAST.html
  56. 56. ? efimyury@gmail.com yury.efimochev@solarwinds.com

×