375 lines
12 KiB
C++
Raw Normal View History

/* -*- 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 "plugin.hxx"
#include "check.hxx"
#include <cassert>
#include <string>
#include <iostream>
#include <fstream>
// The SAL_CALL function annotation is only necessary on our outward
// facing C++ ABI, anywhere else it is just cargo-cult.
//
namespace
{
//static bool startswith(const std::string& rStr, const char* pSubStr)
//{
// return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
//}
class SalCall final : public RecursiveASTVisitor<SalCall>, public loplugin::RewritePlugin
{
public:
explicit SalCall(loplugin::InstantiationData const& data)
: RewritePlugin(data)
{
}
virtual void run() override
{
std::string fn(compiler.getSourceManager()
.getFileEntryForID(compiler.getSourceManager().getMainFileID())
->getName());
loplugin::normalizeDotDotInFilePath(fn);
// ignore this one. I can't get accurate source code from getCharacterData() for it.
if (fn == SRCDIR "/sal/rtl/string.cxx")
return;
m_phase = PluginPhase::FindAddressOf;
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
m_phase = PluginPhase::Warning;
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
}
bool VisitFunctionDecl(FunctionDecl const*);
bool VisitUnaryAddrOf(UnaryOperator const*);
bool VisitInitListExpr(InitListExpr const*);
bool VisitCallExpr(CallExpr const*);
bool VisitBinAssign(BinaryOperator const*);
bool VisitCXXConstructExpr(CXXConstructExpr const*);
private:
void checkForFunctionDecl(Expr const*, bool bCheckOnly = false);
#if 0
bool rewrite(SourceLocation);
bool checkOverlap(SourceRange);
#endif
bool isSalCallFunction(FunctionDecl const* functionDecl, SourceLocation* pLoc = nullptr);
std::set<FunctionDecl const*> m_addressOfSet;
enum class PluginPhase
{
FindAddressOf,
Warning
};
PluginPhase m_phase;
std::vector<std::pair<char const*, char const*>> mvModifiedRanges;
};
bool SalCall::VisitUnaryAddrOf(UnaryOperator const* op)
{
if (m_phase != PluginPhase::FindAddressOf)
return true;
checkForFunctionDecl(op->getSubExpr());
return true;
}
bool SalCall::VisitBinAssign(BinaryOperator const* binaryOperator)
{
if (m_phase != PluginPhase::FindAddressOf)
return true;
checkForFunctionDecl(binaryOperator->getRHS());
return true;
}
bool SalCall::VisitCallExpr(CallExpr const* callExpr)
{
if (m_phase != PluginPhase::FindAddressOf)
return true;
for (auto arg : callExpr->arguments())
checkForFunctionDecl(arg);
return true;
}
bool SalCall::VisitCXXConstructExpr(CXXConstructExpr const* constructExpr)
{
if (m_phase != PluginPhase::FindAddressOf)
return true;
for (auto arg : constructExpr->arguments())
checkForFunctionDecl(arg);
return true;
}
bool SalCall::VisitInitListExpr(InitListExpr const* initListExpr)
{
if (m_phase != PluginPhase::FindAddressOf)
return true;
for (auto subStmt : *initListExpr)
checkForFunctionDecl(dyn_cast<Expr>(subStmt));
return true;
}
void SalCall::checkForFunctionDecl(Expr const* expr, bool bCheckOnly)
{
auto e1 = expr->IgnoreParenCasts();
auto declRef = dyn_cast<DeclRefExpr>(e1);
if (!declRef)
return;
auto functionDecl = dyn_cast<FunctionDecl>(declRef->getDecl());
if (!functionDecl)
return;
if (bCheckOnly)
getParentStmt(expr)->dump();
else
m_addressOfSet.insert(functionDecl->getCanonicalDecl());
}
bool SalCall::VisitFunctionDecl(FunctionDecl const* decl)
{
if (m_phase != PluginPhase::Warning)
return true;
if (ignoreLocation(decl))
return true;
// ignore template stuff
if (decl->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate)
return true;
auto recordDecl = dyn_cast<CXXRecordDecl>(decl->getDeclContext());
if (recordDecl
&& (recordDecl->getTemplateSpecializationKind() != TSK_Undeclared
|| recordDecl->isDependentContext()))
{
return true;
}
auto canonicalDecl = decl->getCanonicalDecl();
// ignore UNO implementations
if (isInUnoIncludeFile(
compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocation())))
return true;
// macros make getCharacterData() extremely unreliable
if (compiler.getSourceManager().isMacroArgExpansion(decl->getLocation())
|| compiler.getSourceManager().isMacroBodyExpansion(decl->getLocation()))
return true;
SourceLocation rewriteLoc;
SourceLocation rewriteCanonicalLoc;
bool bDeclIsSalCall = isSalCallFunction(decl, &rewriteLoc);
bool bCanonicalDeclIsSalCall = isSalCallFunction(canonicalDecl, &rewriteCanonicalLoc);
// first, check for consistency, so we don't trip ourselves up on Linux, where we normally run the plugin
if (canonicalDecl != decl)
{
if (bCanonicalDeclIsSalCall)
; // this is fine, the actual definition have or not have SAL_CALL, and MSVC is fine with it
else if (bDeclIsSalCall)
{
// not fine
report(DiagnosticsEngine::Warning, "SAL_CALL inconsistency",
canonicalDecl->getLocation())
<< canonicalDecl->getSourceRange();
report(DiagnosticsEngine::Note, "SAL_CALL inconsistency", decl->getLocation())
<< decl->getSourceRange();
return true;
}
}
auto methodDecl = dyn_cast<CXXMethodDecl>(canonicalDecl);
if (methodDecl)
{
for (auto iter = methodDecl->begin_overridden_methods();
iter != methodDecl->end_overridden_methods(); ++iter)
{
const CXXMethodDecl* overriddenMethod = (*iter)->getCanonicalDecl();
if (bCanonicalDeclIsSalCall != isSalCallFunction(overriddenMethod))
{
report(DiagnosticsEngine::Warning, "SAL_CALL inconsistency",
methodDecl->getLocation())
<< methodDecl->getSourceRange();
report(DiagnosticsEngine::Note, "SAL_CALL inconsistency",
overriddenMethod->getLocation())
<< overriddenMethod->getSourceRange();
return true;
}
}
}
if (!bDeclIsSalCall)
return true;
// @TODO For now, I am ignore free functions, since those are most likely to have their address taken.
// I'll do these later. They are harder to verify since MSVC does not verify when assigning to function pointers
// that the calling convention of the function matches the calling convention of the function pointer!
if (!methodDecl || methodDecl->isStatic())
return true;
// can only check when we have a definition since this is the most likely time
// when the address of the method will be taken
if (!(methodDecl && methodDecl->isPure()) && !decl->isThisDeclarationADefinition())
return true;
if (m_addressOfSet.find(decl->getCanonicalDecl()) != m_addressOfSet.end())
return true;
// ignore extern "C" UNO factory constructor functions
if (decl->isExternC())
{
if (loplugin::TypeCheck(decl->getReturnType())
.Pointer()
.Class("XInterface")
.Namespace("uno")
.Namespace("star")
.Namespace("sun")
.Namespace("com")
.GlobalNamespace())
return true;
if (loplugin::TypeCheck(decl->getReturnType()).Pointer().Void())
return true;
}
// some base classes are overridden by sub-classes which override both the base-class and an UNO class
if (recordDecl)
{
if (loplugin::DeclCheck(recordDecl)
.Class("OProxyAggregation")
.Namespace("comphelper")
.GlobalNamespace()
|| loplugin::DeclCheck(recordDecl)
.Class("OComponentProxyAggregationHelper")
.Namespace("comphelper")
.GlobalNamespace()
|| loplugin::DeclCheck(recordDecl).Class("SvxShapeMaster").GlobalNamespace())
return true;
}
if (methodDecl)
{
for (auto iter = methodDecl->begin_overridden_methods();
iter != methodDecl->end_overridden_methods(); ++iter)
{
const CXXMethodDecl* overriddenMethod = (*iter)->getCanonicalDecl();
if (isSalCallFunction(overriddenMethod))
return true;
}
}
/*
bool bOK = rewrite(rewriteLoc);
if (bOK && canonicalDecl != decl)
{
bOK = rewrite(rewriteCanonicalLoc);
}
if (bOK)
return true;
//std::cout << "xxx:" << std::string(p1, leftBracket - p1) << std::endl;
report(DiagnosticsEngine::Warning, "SAL_CALL unnecessary here", rewriteLoc)
<< decl->getSourceRange();
if (canonicalDecl != decl)
report(DiagnosticsEngine::Warning, "SAL_CALL unnecessary here", rewriteCanonicalLoc)
<< canonicalDecl->getSourceRange();
*/
return true;
}
bool SalCall::isSalCallFunction(FunctionDecl const* functionDecl, SourceLocation* pLoc)
{
// In certain situations, in header files, clang will return bogus range data
// from decl->getSourceRange().
// Specifically, for the
// LNG_DLLPUBLIC CapType SAL_CALL capitalType(const OUString&, CharClass const *);
// declaration in
// include/linguistic/misc.hxx
// it looks like it is returning data from definition of the LNG_DLLPUBLIC macro.
// I suspect something inside clang is calling getSpellingLoc() once too often.
//
// So I use getReturnTypeSourceRange() for the start of the range
// instead, and search for the "(" in the function decl.
SourceRange range = functionDecl->getSourceRange();
SourceManager& SM = compiler.getSourceManager();
SourceLocation startLoc = functionDecl->getReturnTypeSourceRange().getBegin();
SourceLocation endLoc = range.getEnd();
if (!startLoc.isValid() || !endLoc.isValid())
return false;
char const* p1 = SM.getCharacterData(startLoc);
char const* p2 = SM.getCharacterData(endLoc);
// if (functionDecl->getIdentifier() && functionDecl->getName() == "capitalType")
// {
// std::cout << "xxxx " << (long)p1 << " " << (long)p2 << " " << (int)(p2-p1) << std::endl;
// std::cout << " " << std::string(p1, 80) << std::endl;
// report(DiagnosticsEngine::Warning, "jhjkahdashdkash", functionDecl->getLocation())
// << functionDecl->getSourceRange();
// }
//
static const char* SAL_CALL = "SAL_CALL";
char const* leftBracket = static_cast<char const*>(memchr(p1, '(', p2 - p1));
if (!leftBracket)
return false;
char const* found = std::search(p1, leftBracket, SAL_CALL, SAL_CALL + strlen(SAL_CALL));
if (found >= leftBracket)
return false;
if (pLoc)
// the -1 is to remove the space before the SAL_CALL
*pLoc = startLoc.getLocWithOffset(found - p1 - 1);
return true;
}
#if 0
bool SalCall::rewrite(SourceLocation locBegin)
{
if (!rewriter)
return false;
auto locEnd = locBegin.getLocWithOffset(8);
SourceRange range(locBegin, locEnd);
// If we overlap with a previous area we modified, we cannot perform this change
// without corrupting the source
if (!checkOverlap(range))
return false;
if (!replaceText(locBegin, 9, ""))
return false;
return true;
}
// If we overlap with a previous area we modified, we cannot perform this change
// without corrupting the source
bool SalCall::checkOverlap(SourceRange range)
{
SourceManager& SM = compiler.getSourceManager();
char const* p1 = SM.getCharacterData(range.getBegin());
char const* p2 = SM.getCharacterData(range.getEnd());
for (std::pair<char const*, char const*> const& rPair : mvModifiedRanges)
{
if (rPair.first <= p1 && p1 <= rPair.second)
return false;
if (p1 <= rPair.second && rPair.first <= p2)
return false;
}
mvModifiedRanges.emplace_back(p1, p2);
return true;
}
#endif
static loplugin::Plugin::Registration<SalCall> reg("salcall", true);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */