new loplugin buriedassign
Change-Id: If6dd8033daf2103a81c3a7c3a44cf1e38d0a3744 Reviewed-on: https://gerrit.libreoffice.org/63466 Tested-by: Jenkins Reviewed-by: Noel Grandin <noel.grandin@collabora.co.uk>
This commit is contained in:
226
compilerplugins/clang/buriedassign.cxx
Normal file
226
compilerplugins/clang/buriedassign.cxx
Normal file
@@ -0,0 +1,226 @@
|
|||||||
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
||||||
|
/*
|
||||||
|
* This file is part of the LibreOffice project.
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <cassert>
|
||||||
|
#include <string>
|
||||||
|
#include <iostream>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
#include "plugin.hxx"
|
||||||
|
#include "check.hxx"
|
||||||
|
#include "clang/AST/CXXInheritance.h"
|
||||||
|
#include "clang/AST/StmtVisitor.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
TODO deal with C++ operator overload assign
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
//static bool startswith(const std::string& rStr, const char* pSubStr) {
|
||||||
|
// return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
|
||||||
|
//}
|
||||||
|
class BuriedAssign : public loplugin::FilteringPlugin<BuriedAssign>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit BuriedAssign(loplugin::InstantiationData const& data)
|
||||||
|
: FilteringPlugin(data)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual void run() override
|
||||||
|
{
|
||||||
|
std::string fn(handler.getMainFileName());
|
||||||
|
loplugin::normalizeDotDotInFilePath(fn);
|
||||||
|
// getParentStmt appears not to be working very well here
|
||||||
|
if (fn == SRCDIR "/stoc/source/inspect/introspection.cxx"
|
||||||
|
|| fn == SRCDIR "/stoc/source/corereflection/criface.cxx")
|
||||||
|
return;
|
||||||
|
// calling an acquire via function pointer
|
||||||
|
if (fn == SRCDIR "/cppu/source/uno/lbenv.cxx"
|
||||||
|
|| fn == SRCDIR "cppu/source/typelib/static_types.cxx")
|
||||||
|
return;
|
||||||
|
// false+, not sure why
|
||||||
|
if (fn == SRCDIR "/vcl/source/window/menu.cxx")
|
||||||
|
return;
|
||||||
|
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VisitBinaryOperator(BinaryOperator const*);
|
||||||
|
bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr const*);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void checkExpr(Expr const*);
|
||||||
|
};
|
||||||
|
|
||||||
|
static bool isAssignmentOp(clang::BinaryOperatorKind op)
|
||||||
|
{
|
||||||
|
// We ignore BO_ShrAssign i.e. >>= because we use that everywhere for
|
||||||
|
// extracting data from css::uno::Any
|
||||||
|
return op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign || op == BO_RemAssign
|
||||||
|
|| op == BO_AddAssign || op == BO_SubAssign || op == BO_ShlAssign || op == BO_AndAssign
|
||||||
|
|| op == BO_XorAssign || op == BO_OrAssign;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool isAssignmentOp(clang::OverloadedOperatorKind Opc)
|
||||||
|
{
|
||||||
|
// Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
|
||||||
|
// doesn't have yet.
|
||||||
|
// Except that we ignore OO_GreaterGreaterEqual i.e. >>= because we use that everywhere for
|
||||||
|
// extracting data from css::uno::Any
|
||||||
|
return Opc == OO_Equal || Opc == OO_StarEqual || Opc == OO_SlashEqual || Opc == OO_PercentEqual
|
||||||
|
|| Opc == OO_PlusEqual || Opc == OO_MinusEqual || Opc == OO_LessLessEqual
|
||||||
|
|| Opc == OO_AmpEqual || Opc == OO_CaretEqual || Opc == OO_PipeEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuriedAssign::VisitBinaryOperator(BinaryOperator const* binaryOp)
|
||||||
|
{
|
||||||
|
if (ignoreLocation(binaryOp))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (!isAssignmentOp(binaryOp->getOpcode()))
|
||||||
|
return true;
|
||||||
|
|
||||||
|
checkExpr(binaryOp);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BuriedAssign::VisitCXXOperatorCallExpr(CXXOperatorCallExpr const* cxxOper)
|
||||||
|
{
|
||||||
|
if (ignoreLocation(cxxOper))
|
||||||
|
return true;
|
||||||
|
if (!isAssignmentOp(cxxOper->getOperator()))
|
||||||
|
return true;
|
||||||
|
checkExpr(cxxOper);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuriedAssign::checkExpr(Expr const* binaryOp)
|
||||||
|
{
|
||||||
|
if (compiler.getSourceManager().isMacroBodyExpansion(compat::getBeginLoc(binaryOp)))
|
||||||
|
return;
|
||||||
|
if (compiler.getSourceManager().isMacroArgExpansion(compat::getBeginLoc(binaryOp)))
|
||||||
|
return;
|
||||||
|
|
||||||
|
/**
|
||||||
|
Note: I tried writing this plugin without getParentStmt, but in order to make that work, I had to
|
||||||
|
hack things like TraverseWhileStmt to call TraverseStmt on the child nodes myself, so I could know whether I was inside the body or the condition.
|
||||||
|
But I could not get that to work, so switched to this approach.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// look up past the temporary nodes
|
||||||
|
Stmt const* child = binaryOp;
|
||||||
|
Stmt const* parent = getParentStmt(binaryOp);
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
// This part is not ideal, but getParentStmt() appears to fail us in some cases, notably when the assignment
|
||||||
|
// is inside a decl like:
|
||||||
|
// int x = a = 1;
|
||||||
|
if (!parent)
|
||||||
|
return;
|
||||||
|
if (!(isa<MaterializeTemporaryExpr>(parent) || isa<CXXBindTemporaryExpr>(parent)
|
||||||
|
|| isa<ImplicitCastExpr>(parent) || isa<CXXConstructExpr>(parent)
|
||||||
|
|| isa<ParenExpr>(parent) || isa<ExprWithCleanups>(parent)))
|
||||||
|
break;
|
||||||
|
child = parent;
|
||||||
|
parent = getParentStmt(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isa<CompoundStmt>(parent))
|
||||||
|
return;
|
||||||
|
// ignore chained assignment like "a = b = 1;"
|
||||||
|
if (auto cxxOper = dyn_cast<CXXOperatorCallExpr>(parent))
|
||||||
|
{
|
||||||
|
if (cxxOper->getOperator() == OO_Equal)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ignore chained assignment like "a = b = 1;"
|
||||||
|
if (auto parentBinOp = dyn_cast<BinaryOperator>(parent))
|
||||||
|
{
|
||||||
|
if (parentBinOp->getOpcode() == BO_Assign)
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ignore chained assignment like "int a = b = 1;"
|
||||||
|
if (isa<DeclStmt>(parent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (isa<CaseStmt>(parent) || isa<DefaultStmt>(parent) || isa<LabelStmt>(parent)
|
||||||
|
|| isa<ForStmt>(parent) || isa<CXXForRangeStmt>(parent) || isa<IfStmt>(parent)
|
||||||
|
|| isa<DoStmt>(parent) || isa<WhileStmt>(parent) || isa<ReturnStmt>(parent))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// now check for the statements where we don't care at all if we see a buried assignment
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
if (isa<CompoundStmt>(parent))
|
||||||
|
break;
|
||||||
|
if (isa<CaseStmt>(parent) || isa<DefaultStmt>(parent) || isa<LabelStmt>(parent))
|
||||||
|
return;
|
||||||
|
// Ignore assign in these statements, just seems to be part of the "natural" idiom of C/C++
|
||||||
|
// TODO: perhaps include ReturnStmt?
|
||||||
|
if (auto forStmt = dyn_cast<ForStmt>(parent))
|
||||||
|
{
|
||||||
|
if (child == forStmt->getBody())
|
||||||
|
break;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto forRangeStmt = dyn_cast<CXXForRangeStmt>(parent))
|
||||||
|
{
|
||||||
|
if (child == forRangeStmt->getBody())
|
||||||
|
break;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto ifStmt = dyn_cast<IfStmt>(parent))
|
||||||
|
{
|
||||||
|
if (child == ifStmt->getCond())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto doStmt = dyn_cast<DoStmt>(parent))
|
||||||
|
{
|
||||||
|
if (child == doStmt->getCond())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (auto whileStmt = dyn_cast<WhileStmt>(parent))
|
||||||
|
{
|
||||||
|
if (child == whileStmt->getBody())
|
||||||
|
break;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (isa<ReturnStmt>(parent))
|
||||||
|
return;
|
||||||
|
// This appears to be a valid way of making it obvious that we need to call acquire when assigning such ref-counted
|
||||||
|
// stuff e.g.
|
||||||
|
// rtl_uString_acquire( a = b );
|
||||||
|
if (auto callExpr = dyn_cast<CallExpr>(parent))
|
||||||
|
{
|
||||||
|
if (callExpr->getDirectCallee() && callExpr->getDirectCallee()->getIdentifier())
|
||||||
|
{
|
||||||
|
auto name = callExpr->getDirectCallee()->getName();
|
||||||
|
if (name == "rtl_uString_acquire" || name == "_acquire"
|
||||||
|
|| name == "typelib_typedescriptionreference_acquire")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
child = parent;
|
||||||
|
parent = getParentStmt(parent);
|
||||||
|
if (!parent)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
report(DiagnosticsEngine::Warning, "buried assignment, very hard to read",
|
||||||
|
compat::getBeginLoc(binaryOp))
|
||||||
|
<< binaryOp->getSourceRange();
|
||||||
|
}
|
||||||
|
|
||||||
|
// off by default because it uses getParentStmt so it's more expensive to run
|
||||||
|
loplugin::Plugin::Registration<BuriedAssign> X("buriedassign", false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|
80
compilerplugins/clang/test/buriedassign.cxx
Normal file
80
compilerplugins/clang/test/buriedassign.cxx
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||||
|
/*
|
||||||
|
* This file is part of the LibreOffice project.
|
||||||
|
*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <map>
|
||||||
|
#include <rtl/ustring.hxx>
|
||||||
|
|
||||||
|
namespace test1
|
||||||
|
{
|
||||||
|
int foo(int);
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
int x = 1;
|
||||||
|
foo(x = 2); // expected-error {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
int y = x = 1; // no warning expected
|
||||||
|
(void)y;
|
||||||
|
int z = foo(
|
||||||
|
x = 1); // expected-error {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
(void)z;
|
||||||
|
switch (x = 1)
|
||||||
|
{ // expected-error@-1 {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
}
|
||||||
|
std::map<int, int> map1;
|
||||||
|
map1[x = 1]
|
||||||
|
= 1; // expected-error@-1 {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace test2
|
||||||
|
{
|
||||||
|
struct MyInt
|
||||||
|
{
|
||||||
|
int x;
|
||||||
|
MyInt(int i)
|
||||||
|
: x(i)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
MyInt& operator=(MyInt const&) = default;
|
||||||
|
MyInt& operator=(int) { return *this; }
|
||||||
|
bool operator<(MyInt const& other) const { return x < other.x; }
|
||||||
|
};
|
||||||
|
|
||||||
|
MyInt foo(MyInt);
|
||||||
|
|
||||||
|
void main()
|
||||||
|
{
|
||||||
|
MyInt x = 1;
|
||||||
|
foo(x = 2); // expected-error {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
MyInt y = x = 1; // no warning expected
|
||||||
|
(void)y;
|
||||||
|
MyInt z = foo(
|
||||||
|
x = 1); // expected-error {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
(void)z;
|
||||||
|
z = x; // no warning expected
|
||||||
|
std::map<MyInt, int> map1;
|
||||||
|
map1[x = 1]
|
||||||
|
= 1; // expected-error@-1 {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace test3
|
||||||
|
{
|
||||||
|
void main(OUString sUserAutoCorrFile, OUString sExt)
|
||||||
|
{
|
||||||
|
OUString sRet;
|
||||||
|
if (sUserAutoCorrFile == "xxx")
|
||||||
|
sRet = sUserAutoCorrFile; // no warning expected
|
||||||
|
if (sUserAutoCorrFile == "yyy")
|
||||||
|
(sRet = sUserAutoCorrFile)
|
||||||
|
+= sExt; // expected-error@-1 {{buried assignment, very hard to read [loplugin:buriedassign]}}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|
@@ -12,6 +12,7 @@ $(eval $(call gb_CompilerTest_CompilerTest,compilerplugins_clang))
|
|||||||
$(eval $(call gb_CompilerTest_add_exception_objects,compilerplugins_clang, \
|
$(eval $(call gb_CompilerTest_add_exception_objects,compilerplugins_clang, \
|
||||||
compilerplugins/clang/test/badstatics \
|
compilerplugins/clang/test/badstatics \
|
||||||
compilerplugins/clang/test/blockblock \
|
compilerplugins/clang/test/blockblock \
|
||||||
|
compilerplugins/clang/test/buriedassign \
|
||||||
compilerplugins/clang/test/casttovoid \
|
compilerplugins/clang/test/casttovoid \
|
||||||
compilerplugins/clang/test/collapseif \
|
compilerplugins/clang/test/collapseif \
|
||||||
compilerplugins/clang/test/commaoperator \
|
compilerplugins/clang/test/commaoperator \
|
||||||
|
Reference in New Issue
Block a user