56
Clang-tidy: путешествие внутрь AST С++ Юрий Ефимочев

Clang tidy

Embed Size (px)

Citation preview

Page 1: Clang tidy

Clang-tidy:путешествие внутрь AST С++

Юрий Ефимочев

Page 2: Clang tidy

Обо мне

Solarwinds

Lead Developer

MSP Backup & Recovery

Page 3: Clang tidy

Что такое clang-tidy?

Инструмент для статического анализа кода и поиска типичных ошибок программирования

Page 4: Clang tidy

Встроенные правила(~200)

● ClangAnalyzer● Readability/Modernize/Performance● CppCoreGuidelines● LLVM/Google-style

Page 5: Clang tidy

Пример запуска

class TestClass{public: TestClass() : m_B(), m_C() { }

private: int m_A; int m_B; int m_C;};

Page 6: Clang tidy

Пример запуска

$ 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(),

Page 7: Clang tidy

Code review

Page 8: Clang tidy

Цели code review

● Соответствие реализации задаче

● Поиск ошибок и неоптимальностей реализации

● Соответствие guidelines и best practices

Page 9: Clang tidy

Clang-tidy и code review?

Платформа для построения собственных инструментов статического анализа кода

Page 10: Clang tidy

Расширяемость

● Полный доступ к AST и препроцессору● Модульная архитектура● Инфраструктура для разработки и

тестирования

Page 11: Clang tidy

Что необходимо для использования?

● Код должен собираться clang● Необходима compilation database

Page 12: Clang tidy

Алгоритм разработки правила

1. Подготовить пример2. Посмотреть в AST3. ???4. Profit

Page 13: Clang tidy

Пример 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

Page 14: Clang tidy

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'

Page 15: Clang tidy

AST Nodes

● Decl

● Stmt

● Type

Page 16: Clang tidy

AST Nodes

● Decl

CXXRecordDeclCXXMethodDeclVarDecl

● Stmt● Type

Page 17: Clang tidy

AST Nodes

● Decl

● Stmt

ifStmtCXXTryStmtBinaryOperator

● Type

Page 18: Clang tidy

AST Nodes

● Decl

● Stmt

● Type

PointerTypeReferenceTypeLValueReferenceType

Page 19: Clang tidy

Пример 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'

Page 20: Clang tidy

AST Matchers

● Node MatcherscxxRecordDecl, cxxMethodDecl, namespaceDecl, ifStmt, ...

● Narrowing MatchersisConstant, isFinal, hasName, matchesName, unless, ...

● Traversal MatchershasDescendant, hasParent, hasBody, ...

cxxMethodDecl(matchesName(“Get.*”), hasParent(cxxRecordDecl(isStruct()))

Page 21: Clang tidy

Пример 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.

Page 22: Clang tidy

Пример 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.

Page 23: Clang tidy

Пример 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.

Page 24: Clang tidy

Добавление правила

$ ./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!

Page 25: Clang tidy

Пример 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 { }};

Page 26: Clang tidy

Пример 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"); }};

Page 27: Clang tidy

Пример 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;^

Page 28: Clang tidy

Пример 2

int Function(int a){ // ...

a = something;

// ...

return a;}

// style: mutable parameter

Page 29: Clang tidy

Пример 2

struct Test{ virtual int VirtualMethod(int a) = 0;

int Method(int a, int const b, int& c, int* d) { a = b; return a; }};

Page 30: Clang tidy

Пример 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'

Page 31: Clang tidy

Пример 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 ");}

Page 32: Clang tidy

Пример 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 changesint Method(int a, int const b, int& c, int* d)

^

clang-tidy applied 1 of 1 suggested fixes.

Page 33: Clang tidy

Пример 2: результат

struct Test{ virtual int VirtualMethod(int a) = 0;

int Method(int a, int const b, int& c, int* d) { a = b; return a; }};

Page 34: Clang tidy

Пример 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; }};

Page 35: Clang tidy

Пример 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;};

Page 36: Clang tidy

Пример 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;

Page 37: Clang tidy

Пример 4

Можно ли бросать исключение из деструктора?

Page 38: Clang tidy

Пример 4

class Test{public: ~Test() { throw Exception(); }};

// c++11: terminate

Page 39: Clang tidy

Пример 4

class Test{public: ~Test() noexcept(true) { throw Exception(); }};

// c++11: terminate

Page 40: Clang tidy

Пример 4

class Test{public: ~Test() noexcept(false) { throw Exception(); }};

Page 41: Clang tidy

Пример 4

class Test{public: ~Test() noexcept(false) { throw Exception(); }};

int main(){ std::unique_ptr<Test> test(new Test()); test.reset();}

// c++11: terminate

Page 42: Clang tidy

Пример 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

Page 43: Clang tidy

Пример 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

Page 44: Clang tidy

Пример 4

● Не бросать исключения из деструкторов● Обозначить бросающие деструкторы

‘noexcept(false)’● Не использовать ‘noexcept(true)’ для

деструкторов

Page 45: Clang tidy

Пример 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)'"); }}

Page 46: Clang tidy

Пример 4: результат

class Parent{public: ~Parent() noexcept(false);};

class Child : public Parent{public: ~Child() noexcept(true) { }};

// do not use ‘noexept(true)’

Page 47: Clang tidy

Пример 4: результат

class Parent{public: ~Parent() noexcept(false);};

class Child : public Parent{public: ~Child() { }};

// can throw, mark with ‘noexcept(false)’

Page 48: Clang tidy

Пример 4: результат

class Parent{public: ~Parent() noexcept(false);};

class Child : public Parent{public: ~Child() noexcept(false) { }};

Page 49: Clang tidy

Пример 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); }};

Page 50: Clang tidy

Пример 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"); }}

Page 51: Clang tidy

Пример 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

Page 52: Clang tidy

Пример 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(); } }};

Page 53: Clang tidy

Clang-tidy: итоги

Page 54: Clang tidy

Clang-tidy: итоги

● Простая реализация сложных проверок● Автоматизация рутинных проверок● Отличный способ лучше узнать C++