Clang-tidy:
путешествие внутрь AST С++
Юрий Ефимочев
Обо мне
Solarwinds
Lead Developer
MSP Backup & Recovery
Что такое clang-tidy?
Инструмент для статического анализа кода и
поиска типичных ошибок программирования
Встроенные правила(~200)
● ClangAnalyzer
● Readability/Modernize/Performance
● CppCoreGuidelines
● LLVM/Google-style
Пример запуска
class TestClass
{
public:
TestClass() :
m_B(),
m_C()
{
}
private:
int m_A;
int m_B;
int m_C;
};
Пример запуска
$ 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(),
Code review
Цели code review
● Соответствие реализации задаче
● Поиск ошибок и неоптимальностей реализации
● Соответствие guidelines и best practices
Clang-tidy и code review?
Платформа для построения собственных
инструментов статического анализа кода
Расширяемость
● Полный доступ к AST и препроцессору
● Модульная архитектура
● Инфраструктура для разработки и
тестирования
Что необходимо для использования?
● Код должен собираться clang
● Необходима compilation database
Алгоритм разработки правила
1. Подготовить пример
2. Посмотреть в AST
3. ???
4. Profit
Пример 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
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'
AST Nodes
● Decl
● Stmt
● Type
AST Nodes
● Decl
CXXRecordDecl
CXXMethodDecl
VarDecl
● Stmt
● Type
AST Nodes
● Decl
● Stmt
ifStmt
CXXTryStmt
BinaryOperator
● Type
AST Nodes
● Decl
● Stmt
● Type
PointerType
ReferenceType
LValueReferenceType
Пример 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'
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()))
Пример 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.
Пример 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.
Пример 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.
Добавление правила
$ ./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!
Пример 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
{
}
};
Пример 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");
}
};
Пример 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;
^
Пример 2
int Function(int a)
{
// ...
a = something;
// ...
return a;
}
// style: mutable parameter
Пример 2
struct Test
{
virtual int VirtualMethod(int a) = 0;
int Method(int a, int const b, int& c, int* d)
{
a = b;
return a;
}
};
Пример 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'
Пример 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 ");
}
Пример 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.
Пример 2: результат
struct Test
{
virtual int VirtualMethod(int a) = 0;
int Method(int a, int const b, int& c, int* d)
{
a = b;
return a;
}
};
Пример 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;
}
};
Пример 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;
};
Пример 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;
Пример 4
Можно ли бросать исключение из
деструктора?
Пример 4
class Test
{
public:
~Test()
{
throw Exception();
}
};
// c++11: terminate
Пример 4
class Test
{
public:
~Test() noexcept(true)
{
throw Exception();
}
};
// c++11: terminate
Пример 4
class Test
{
public:
~Test() noexcept(false)
{
throw Exception();
}
};
Пример 4
class Test
{
public:
~Test() noexcept(false)
{
throw Exception();
}
};
int main()
{
std::unique_ptr<Test> test(new Test());
test.reset();
}
// c++11: terminate
Пример 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
Пример 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
Пример 4
● Не бросать исключения из деструкторов
● Обозначить бросающие деструкторы
‘noexcept(false)’
● Не использовать ‘noexcept(true)’ для
деструкторов
Пример 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)'");
}
}
Пример 4: результат
class Parent
{
public:
~Parent() noexcept(false);
};
class Child : public Parent
{
public:
~Child() noexcept(true)
{
}
};
// do not use ‘noexept(true)’
Пример 4: результат
class Parent
{
public:
~Parent() noexcept(false);
};
class Child : public Parent
{
public:
~Child()
{
}
};
// can throw, mark with ‘noexcept(false)’
Пример 4: результат
class Parent
{
public:
~Parent() noexcept(false);
};
class Child : public Parent
{
public:
~Child() noexcept(false)
{
}
};
Пример 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);
}
};
Пример 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");
}
}
Пример 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
Пример 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();
}
}
};
Clang-tidy: итоги
Clang-tidy: итоги
● Простая реализация сложных проверок
● Автоматизация рутинных проверок
● Отличный способ лучше узнать C++
Полезные ссылки
http://clang.llvm.org/extra/clang-tidy
http://clang.llvm.org/docs/LibASTMatchersReference.html
http://clang.llvm.org/docs/IntroductionToTheClangAST.html
?
efimyury@gmail.com
yury.efimochev@solarwinds.com

Clang tidy

  • 1.
  • 2.
  • 3.
    Что такое clang-tidy? Инструментдля статического анализа кода и поиска типичных ошибок программирования
  • 4.
    Встроенные правила(~200) ● ClangAnalyzer ●Readability/Modernize/Performance ● CppCoreGuidelines ● LLVM/Google-style
  • 5.
    Пример запуска class TestClass { public: TestClass(): m_B(), m_C() { } private: int m_A; int m_B; int m_C; };
  • 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.
  • 8.
    Цели code review ●Соответствие реализации задаче ● Поиск ошибок и неоптимальностей реализации ● Соответствие guidelines и best practices
  • 9.
    Clang-tidy и codereview? Платформа для построения собственных инструментов статического анализа кода
  • 10.
    Расширяемость ● Полный доступк AST и препроцессору ● Модульная архитектура ● Инфраструктура для разработки и тестирования
  • 11.
    Что необходимо дляиспользования? ● Код должен собираться clang ● Необходима compilation database
  • 12.
    Алгоритм разработки правила 1.Подготовить пример 2. Посмотреть в AST 3. ??? 4. Profit
  • 13.
    Пример 1 #include <iostream> classTestClass { public: static int const Constant; int m_public; private: int m_private; }; struct TestStruct { int Field; }; // style: class public field
  • 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.
  • 16.
  • 17.
    AST Nodes ● Decl ●Stmt ifStmt CXXTryStmt BinaryOperator ● Type
  • 18.
    AST Nodes ● Decl ●Stmt ● Type PointerType ReferenceType LValueReferenceType
  • 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.
    AST Matchers ● NodeMatchers cxxRecordDecl, cxxMethodDecl, namespaceDecl, ifStmt, ... ● Narrowing Matchers isConstant, isFinal, hasName, matchesName, unless, ... ● Traversal Matchers hasDescendant, hasParent, hasBody, ... cxxMethodDecl(matchesName(“Get.*”), hasParent(cxxRecordDecl(isStruct()))
  • 21.
    Пример 1 clang-query> matchfieldDecl() 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.
    Пример 1 clang-query> matchfieldDecl(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.
    Пример 1 clang-query> matchfieldDecl(isPublic(), hasParent(cxxRecordDecl(isClass()))) Match #1: /home/yury/Projects/test.cpp:8:5: note: "root" binds here int m_public; ^~~~~~~~~~~~ 1 match.
  • 24.
    Добавление правила $ ./add_new_check.pymisc 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.
    Пример 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.
    Пример 1: реализация classFieldVisibilityCheck : 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.
    Пример 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.
    Пример 2 int Function(inta) { // ... a = something; // ... return a; } // style: mutable parameter
  • 29.
    Пример 2 struct Test { virtualint VirtualMethod(int a) = 0; int Method(int a, int const b, int& c, int* d) { a = b; return a; } };
  • 30.
    Пример 2: AST DumpingTest: 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.
    Пример 2: реализация voidImmutableParamsCheck::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.
    Пример 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.
    Пример 2: результат structTest { virtual int VirtualMethod(int a) = 0; int Method(int a, int const b, int& c, int* d) { a = b; return a; } };
  • 34.
    Пример 2: результат structTest { virtual int VirtualMethod(int a) = 0; int Method(int const a, int const b, int& c, int* d) { a = b; return a; } };
  • 35.
    Пример 3 // cxxRecordDecl(unless(matchesName("::[A-Z][a-zA-Z0-9]*$"))) classTestClass { 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.
    Пример 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.
    Пример 4 Можно либросать исключение из деструктора?
  • 38.
    Пример 4 class Test { public: ~Test() { throwException(); } }; // c++11: terminate
  • 39.
    Пример 4 class Test { public: ~Test()noexcept(true) { throw Exception(); } }; // c++11: terminate
  • 40.
    Пример 4 class Test { public: ~Test()noexcept(false) { throw Exception(); } };
  • 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.
    Пример 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.
    Пример 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.
    Пример 4 ● Небросать исключения из деструкторов ● Обозначить бросающие деструкторы ‘noexcept(false)’ ● Не использовать ‘noexcept(true)’ для деструкторов
  • 45.
    Пример 4: реализация voidExplicitThrowSpecificationCheck::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.
    Пример 4: результат classParent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() noexcept(true) { } }; // do not use ‘noexept(true)’
  • 47.
    Пример 4: результат classParent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() { } }; // can throw, mark with ‘noexcept(false)’
  • 48.
    Пример 4: результат classParent { public: ~Parent() noexcept(false); }; class Child : public Parent { public: ~Child() noexcept(false) { } };
  • 49.
    Пример 4 void Function(); structObject { Object(); bool operator<(Object const& right) const; }; struct Test { Test() { Object a, b; if (a < b) { Function(); } int* p = new int(42); } };
  • 50.
    Пример 4: реализация voidThrowFromDestructorCheck::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.
    Пример 4: результат voidFunction(); 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.
    Пример 4: результат voidFunction() 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.
  • 54.
    Clang-tidy: итоги ● Простаяреализация сложных проверок ● Автоматизация рутинных проверок ● Отличный способ лучше узнать C++
  • 55.
  • 56.