office-gobmx/compilerplugins/clang/singlevalfields.cxx
Stephan Bergmann 09aa5a9be8 Adapt to changed clang::ASTContext::getParents behavior on Clang 11 trunk
...since <https://github.com/llvm/llvm-project/commit/
d0da5d2bbe8305d06dc01a98706fd73e11e24a9f> "Change default traversal in AST
Matchers to ignore invisible nodes".  This caused failures

> [CPT] compilerplugins/clang/test/constparams.cxx
> ParmVarDecl 0x11d76c730 <compilerplugins/clang/test/constparams.cxx:15:12, col:18> col:18 used f1 'int *'
> DeclRefExpr 0x11d76c948 'int *' lvalue ParmVar 0x11d76c730 'f1' 'int *'
> ParmVarDecl 0x11d76cc80 <compilerplugins/clang/test/constparams.cxx:21:12, col:18> col:18 used f2 'int *'
> DeclRefExpr 0x11d76ce60 'int *' lvalue ParmVar 0x11d76cc80 'f2' 'int *'
> error: 'error' diagnostics expected but not seen:
>   File compilerplugins/clang/test/constparams.cxx Line 15: this parameter can be const Class1::Class1 [loplugin:constparams]
> error: 'error' diagnostics seen but not expected:
>   File compilerplugins/clang/test/constparams.cxx Line 15: no parent? [loplugin:constparams]
>   File compilerplugins/clang/test/constparams.cxx Line 21: no parent? [loplugin:constparams]
> 3 errors generated.
[...]
> [CPT] compilerplugins/clang/test/unusedenumconstants.cxx
> error: 'error' diagnostics expected but not seen:
>   File compilerplugins/clang/test/unusedenumconstants.cxx Line 30: read Bottom [loplugin:unusedenumconstants]
> error: 'error' diagnostics seen but not expected:
>   File compilerplugins/clang/test/unusedenumconstants.cxx Line 30: write Bottom [loplugin:unusedenumconstants]
> 2 errors generated.
[...]
> [CPT] compilerplugins/clang/test/unusedfields.cxx
> error: 'error' diagnostics expected but not seen:
>   File compilerplugins/clang/test/unusedfields.cxx Line 156 (directive at compilerplugins/clang/test/unusedfields.cxx:164): write m_f5 [loplugin:unusedfields]
>   File compilerplugins/clang/test/unusedfields.cxx Line 210 (directive at compilerplugins/clang/test/unusedfields.cxx:211): read m_f1 [loplugin:unusedfields]
> 2 errors generated.

For compilerplugins/clang/test/constparams.cxx at least it would have worked to
fix that locally with

> diff --git a/compilerplugins/clang/constparams.cxx b/compilerplugins/clang/constparams.cxx
> index 95c8184009d7..70f056fa5a69 100644
> --- a/compilerplugins/clang/constparams.cxx
> +++ b/compilerplugins/clang/constparams.cxx
> @@ -274,7 +274,7 @@ bool ConstParams::checkIfCanBeConst(const Stmt* stmt, const ParmVarDecl* parmVar
>              {
>                  for ( auto cxxCtorInitializer : cxxConstructorDecl->inits())
>                  {
> -                    if ( cxxCtorInitializer->getInit() == stmt)
> +                    if ( cxxCtorInitializer->getInit()->IgnoreImpCasts() == stmt)
>                      {
>                          if (cxxCtorInitializer->isAnyMemberInitializer())
>                          {

(somewhat unintuitively, given the Clang change is apparently about ignoring
more implicit nodes), but overall it appears better---at least for now---to use
a getParents variant that keeps the old traversal behavior.

For that, instead of using the clang::ASTContext::ParentMapCtx, we create our
own loplugin::Plugin::parentMapContext_.  There appear to be no uses of
ASTContext::getParent across the Clang codebase itself, outside of ASTMatcher
code, so it looks unlikely that creating our own ParentMapContext instance would
degrade performance by no longer sharing cached data between Clang's internals
and our plugin.  (And given that ASTContext::getParents is deprecated with the
note "New callers should use ParentMapContext::getParents() directly", this may
well be the correct way in the long run, anyway.)

Change-Id: I46c7912f2737e7c224fd45ab41441f69e2f10bd4
Reviewed-on: https://gerrit.libreoffice.org/c/core/+/94795
Tested-by: Jenkins
Reviewed-by: Stephan Bergmann <sbergman@redhat.com>
2020-05-25 20:40:25 +02:00

576 lines
21 KiB
C++

/* -*- 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 <fstream>
#include <set>
#include "config_clang.h"
#include "plugin.hxx"
#if CLANG_VERSION >= 110000
#include "clang/AST/ParentMapContext.h"
#endif
/**
Look for fields that are only ever assigned a single constant value.
We dmp a list of values assigned to fields, and a list of field definitions.
Then we will post-process the 2 lists and find the set of interesting fields.
Be warned that it produces around 5G of log file.
The process goes something like this:
$ make check
$ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='singlevalfields' check
$ ./compilerplugins/clang/singlevalfields.py
Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
to get it to work :-)
@TODO we don't spot fields that have been zero-initialised via calloc or rtl_allocateZeroMemory or memset
@TODO calls to lambdas where a reference to the field is taken
*/
namespace {
struct MyFieldInfo
{
FieldDecl const * fieldDecl;
std::string parentClass;
std::string fieldName;
std::string fieldType;
std::string sourceLocation;
};
bool operator < (const MyFieldInfo &lhs, const MyFieldInfo &rhs)
{
return std::tie(lhs.parentClass, lhs.fieldName)
< std::tie(rhs.parentClass, rhs.fieldName);
}
struct MyFieldAssignmentInfo : public MyFieldInfo
{
std::string value;
};
bool operator < (const MyFieldAssignmentInfo &lhs, const MyFieldAssignmentInfo &rhs)
{
return std::tie(lhs.parentClass, lhs.fieldName, lhs.value)
< std::tie(rhs.parentClass, rhs.fieldName, rhs.value);
}
// try to limit the voluminous output a little
static std::set<MyFieldAssignmentInfo> assignedSet;
static std::set<MyFieldInfo> definitionSet;
class SingleValFields:
public RecursiveASTVisitor<SingleValFields>, public loplugin::Plugin
{
public:
explicit SingleValFields(loplugin::InstantiationData const & data):
Plugin(data) {}
virtual void run() override
{
TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
if (!isUnitTestMode())
{
// dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
// writing to the same logfile
std::string output;
for (const MyFieldAssignmentInfo & s : assignedSet)
output += "asgn:\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.value + "\n";
for (const MyFieldInfo & s : definitionSet)
output += "defn:\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.fieldType + "\t" + s.sourceLocation + "\n";
std::ofstream myfile;
myfile.open( WORKDIR "/loplugin.singlevalfields.log", std::ios::app | std::ios::out);
myfile << output;
myfile.close();
}
else
{
for (const MyFieldAssignmentInfo & s : assignedSet)
if (s.fieldDecl && compiler.getSourceManager().isInMainFile(compat::getBeginLoc(s.fieldDecl)))
report(
DiagnosticsEngine::Warning,
"assign %0",
compat::getBeginLoc(s.fieldDecl))
<< s.value;
}
}
bool shouldVisitTemplateInstantiations () const { return true; }
// to catch compiler-generated constructors
bool shouldVisitImplicitCode() const { return true; }
bool VisitFieldDecl( const FieldDecl* );
bool VisitVarDecl( const VarDecl* );
bool VisitMemberExpr( const MemberExpr* );
bool VisitDeclRefExpr( const DeclRefExpr* );
bool VisitCXXConstructorDecl( const CXXConstructorDecl* );
// bool VisitUnaryExprOrTypeTraitExpr( const UnaryExprOrTypeTraitExpr* );
private:
void niceName(const DeclaratorDecl*, MyFieldInfo&);
void walkPotentialAssign( const DeclaratorDecl* fieldOrVarDecl, const Stmt* stmt );
std::string getExprValue(const Expr*);
const FunctionDecl* get_top_FunctionDecl_from_Stmt(const Stmt&);
void checkCallExpr(const Stmt* child, const CallExpr* callExpr, std::string& assignValue, bool& bPotentiallyAssignedTo);
};
void SingleValFields::niceName(const DeclaratorDecl* fieldOrVarDecl, MyFieldInfo& aInfo)
{
const VarDecl* varDecl = dyn_cast<VarDecl>(fieldOrVarDecl);
const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(fieldOrVarDecl);
aInfo.fieldDecl = fieldDecl;
if (fieldDecl)
aInfo.parentClass = fieldDecl->getParent()->getQualifiedNameAsString();
else
{
if (auto parentRecordDecl = dyn_cast<CXXRecordDecl>(varDecl->getDeclContext()))
aInfo.parentClass = parentRecordDecl->getQualifiedNameAsString();
else if (auto parentMethodDecl = dyn_cast<CXXMethodDecl>(varDecl->getDeclContext()))
aInfo.parentClass = parentMethodDecl->getQualifiedNameAsString();
else if (auto parentFunctionDecl = dyn_cast<FunctionDecl>(varDecl->getDeclContext()))
aInfo.parentClass = parentFunctionDecl->getQualifiedNameAsString();
else if (isa<TranslationUnitDecl>(varDecl->getDeclContext()))
aInfo.parentClass = handler.getMainFileName().str();
else if (auto parentNamespaceDecl = dyn_cast<NamespaceDecl>(varDecl->getDeclContext()))
aInfo.parentClass = parentNamespaceDecl->getQualifiedNameAsString();
else if (isa<LinkageSpecDecl>(varDecl->getDeclContext()))
aInfo.parentClass = "extern"; // what to do here?
else
{
std::cout << "what is this? " << varDecl->getDeclContext()->getDeclKindName() << std::endl;
exit(1);
}
}
aInfo.fieldName = fieldOrVarDecl->getNameAsString();
aInfo.fieldType = fieldOrVarDecl->getType().getAsString();
SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( fieldOrVarDecl->getLocation() );
StringRef name = getFilenameOfLocation(expansionLoc);
aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
}
bool SingleValFields::VisitFieldDecl( const FieldDecl* fieldDecl )
{
auto canonicalDecl = fieldDecl->getCanonicalDecl();
if( ignoreLocation( canonicalDecl )
|| isInUnoIncludeFile( compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocation())) )
return true;
MyFieldInfo aInfo;
niceName(canonicalDecl, aInfo);
definitionSet.insert(aInfo);
if (fieldDecl->getInClassInitializer())
{
MyFieldAssignmentInfo aInfo;
niceName(canonicalDecl, aInfo);
aInfo.value = getExprValue(fieldDecl->getInClassInitializer());
assignedSet.insert(aInfo);
}
return true;
}
bool SingleValFields::VisitVarDecl( const VarDecl* varDecl )
{
if (isa<ParmVarDecl>(varDecl))
return true;
if (varDecl->getType().isConstQualified())
return true;
if (!(varDecl->isStaticLocal() || varDecl->isStaticDataMember() || varDecl->hasGlobalStorage()))
return true;
auto canonicalDecl = varDecl->getCanonicalDecl();
if (!canonicalDecl->getLocation().isValid())
return true;
if( ignoreLocation( canonicalDecl )
|| isInUnoIncludeFile( compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocation())) )
return true;
MyFieldInfo aInfo;
niceName(canonicalDecl, aInfo);
definitionSet.insert(aInfo);
if (varDecl->getInit())
{
MyFieldAssignmentInfo aInfo;
niceName(canonicalDecl, aInfo);
aInfo.value = getExprValue(varDecl->getInit());
assignedSet.insert(aInfo);
}
return true;
}
bool SingleValFields::VisitCXXConstructorDecl( const CXXConstructorDecl* decl )
{
if( ignoreLocation( decl ) )
return true;
// doesn't count as a write to fields because it's self->self
if (decl->isCopyOrMoveConstructor())
return true;
for(auto it = decl->init_begin(); it != decl->init_end(); ++it)
{
const CXXCtorInitializer* init = *it;
const FieldDecl* fieldDecl = init->getMember();
if( !fieldDecl )
continue;
MyFieldAssignmentInfo aInfo;
niceName(fieldDecl, aInfo);
const Expr * expr = init->getInit();
// unwrap any single-arg constructors, this helps to find smart pointers
// that are only assigned nullptr
if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(expr))
if (cxxConstructExpr->getNumArgs() == 1)
expr = cxxConstructExpr->getArg(0);
aInfo.value = getExprValue(expr);
assignedSet.insert(aInfo);
}
return true;
}
bool SingleValFields::VisitMemberExpr( const MemberExpr* memberExpr )
{
const ValueDecl* decl = memberExpr->getMemberDecl();
const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
if (!fieldDecl)
return true;
if (ignoreLocation(memberExpr))
return true;
walkPotentialAssign(fieldDecl, memberExpr);
return true;
}
bool SingleValFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
{
const VarDecl* varDecl = dyn_cast_or_null<VarDecl>(declRefExpr->getDecl());
if (!varDecl)
return true;
if (isa<ParmVarDecl>(varDecl))
return true;
if (varDecl->getType().isConstQualified())
return true;
if (!(varDecl->isStaticLocal() || varDecl->isStaticDataMember() || varDecl->hasGlobalStorage()))
return true;
if (ignoreLocation(declRefExpr))
return true;
walkPotentialAssign(varDecl, declRefExpr);
return true;
}
void SingleValFields::walkPotentialAssign( const DeclaratorDecl* fieldOrVarDecl, const Stmt* memberExpr )
{
const FunctionDecl* parentFunction = getParentFunctionDecl(memberExpr);
if (parentFunction)
{
auto methodDecl = dyn_cast<CXXMethodDecl>(parentFunction);
if (methodDecl && (methodDecl->isCopyAssignmentOperator() || methodDecl->isMoveAssignmentOperator()))
return;
if (methodDecl && methodDecl->getIdentifier()
&& (methodDecl->getName().startswith("Clone") || methodDecl->getName().startswith("clone")))
return;
auto cxxConstructorDecl = dyn_cast<CXXConstructorDecl>(parentFunction);
if (cxxConstructorDecl && cxxConstructorDecl->isCopyOrMoveConstructor())
return;
}
// walk up the tree until we find something interesting
const Stmt* child = memberExpr;
const Stmt* parent = getParentStmt(memberExpr);
bool bPotentiallyAssignedTo = false;
bool bDump = false;
std::string assignValue = "?";
// check for field being returned by non-const ref eg. Foo& getFoo() { return f; }
if (parentFunction && parent && isa<ReturnStmt>(parent)) {
const Stmt* parent2 = getParentStmt(parent);
if (parent2 && isa<CompoundStmt>(parent2)) {
QualType qt = parentFunction->getReturnType().getDesugaredType(compiler.getASTContext());
if (!qt.isConstQualified() && qt->isReferenceType()) {
bPotentiallyAssignedTo = true;
}
}
}
while (!bPotentiallyAssignedTo) {
// check for field being accessed by a reference variable e.g. Foo& f = m.foo;
auto parentsList = getParents(*child);
auto it = parentsList.begin();
if (it != parentsList.end()) {
const VarDecl *varDecl = it->get<VarDecl>();
if (varDecl) {
QualType qt = varDecl->getType().getDesugaredType(compiler.getASTContext());
if (!qt.isConstQualified() && qt->isReferenceType()) {
bPotentiallyAssignedTo = true;
break;
}
}
}
if (!parent) {
return;
}
if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
|| isa<ExprWithCleanups>(parent))
{
child = parent;
parent = getParentStmt(parent);
}
else if (isa<UnaryOperator>(parent))
{
const UnaryOperator* unaryOperator = dyn_cast<UnaryOperator>(parent);
int x = unaryOperator->getOpcode();
if (x == UO_AddrOf || x == UO_PostInc || x == UO_PostDec || x == UO_PreInc || x == UO_PreDec) {
assignValue = "?";
bPotentiallyAssignedTo = true;
break;
}
// cannot be assigned to anymore
break;
}
else if (auto callExpr = dyn_cast<CallExpr>(parent))
{
checkCallExpr(child, callExpr, assignValue, bPotentiallyAssignedTo);
break;
}
else if (isa<CXXConstructExpr>(parent))
{
const CXXConstructExpr* consExpr = dyn_cast<CXXConstructExpr>(parent);
const CXXConstructorDecl* consDecl = consExpr->getConstructor();
for (unsigned i = 0; i < consExpr->getNumArgs(); ++i) {
if (i >= consDecl->getNumParams()) // can happen in template code
break;
if (consExpr->getArg(i) == child) {
const ParmVarDecl* parmVarDecl = consDecl->getParamDecl(i);
QualType qt = parmVarDecl->getType().getDesugaredType(compiler.getASTContext());
if (!qt.isConstQualified() && qt->isReferenceType()) {
bPotentiallyAssignedTo = true;
}
break;
}
}
break;
}
else if (isa<BinaryOperator>(parent))
{
const BinaryOperator* binaryOp = dyn_cast<BinaryOperator>(parent);
auto op = binaryOp->getOpcode();
if ( binaryOp->getLHS() != child ) {
// if the expr is on the RHS, do nothing
}
else if ( op == BO_Assign ) {
assignValue = getExprValue(binaryOp->getRHS());
bPotentiallyAssignedTo = true;
} else if ( op == BO_MulAssign || op == BO_DivAssign
|| op == BO_RemAssign || op == BO_AddAssign
|| op == BO_SubAssign || op == BO_ShlAssign
|| op == BO_ShrAssign || op == BO_AndAssign
|| op == BO_XorAssign || op == BO_OrAssign )
{
bPotentiallyAssignedTo = true;
}
break;
}
else if ( isa<CompoundStmt>(parent)
|| isa<SwitchStmt>(parent) || isa<CaseStmt>(parent) || isa<DefaultStmt>(parent)
|| isa<DoStmt>(parent) || isa<WhileStmt>(parent)
|| isa<IfStmt>(parent)
|| isa<ForStmt>(parent)
|| isa<ReturnStmt>(parent)
|| isa<CXXNewExpr>(parent)
|| isa<CXXDeleteExpr>(parent)
|| isa<ConditionalOperator>(parent)
|| isa<CXXTypeidExpr>(parent)
|| isa<ArraySubscriptExpr>(parent)
|| isa<CXXDependentScopeMemberExpr>(parent)
|| isa<DeclStmt>(parent)
|| isa<UnaryExprOrTypeTraitExpr>(parent)
|| isa<UnresolvedMemberExpr>(parent)
|| isa<MaterializeTemporaryExpr>(parent) //???
|| isa<InitListExpr>(parent)
|| isa<CXXUnresolvedConstructExpr>(parent)
|| isa<LambdaExpr>(parent)
|| isa<PackExpansionExpr>(parent)
|| isa<CXXPseudoDestructorExpr>(parent)
)
{
break;
}
else if ( isa<ArrayInitLoopExpr>(parent) || isa<GCCAsmStmt>(parent) || isa<VAArgExpr>(parent))
{
bPotentiallyAssignedTo = true;
break;
}
else {
bPotentiallyAssignedTo = true;
bDump = true;
break;
}
}
if (bDump)
{
report(
DiagnosticsEngine::Warning,
"oh dear, what can the matter be?",
compat::getBeginLoc(memberExpr))
<< memberExpr->getSourceRange();
parent->dump();
}
if (bPotentiallyAssignedTo)
{
MyFieldAssignmentInfo aInfo;
niceName(fieldOrVarDecl, aInfo);
aInfo.value = assignValue;
assignedSet.insert(aInfo);
}
}
void SingleValFields::checkCallExpr(const Stmt* child, const CallExpr* callExpr, std::string& assignValue, bool& bPotentiallyAssignedTo)
{
if (callExpr->getCallee() == child) {
return;
}
const FunctionDecl* functionDecl;
if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(callExpr)) {
functionDecl = memberCallExpr->getMethodDecl();
} else {
functionDecl = callExpr->getDirectCallee();
}
if (functionDecl) {
if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(callExpr)) {
if (operatorCallExpr->getArg(0) == child) {
const CXXMethodDecl* calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(operatorCallExpr->getDirectCallee());
if (calleeMethodDecl) {
if (operatorCallExpr->getOperator() == OO_Equal) {
assignValue = getExprValue(operatorCallExpr->getArg(1));
bPotentiallyAssignedTo = true;
return;
}
}
}
}
for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
if (i >= functionDecl->getNumParams()) // can happen in template code
break;
if (callExpr->getArg(i) == child) {
const ParmVarDecl* parmVarDecl = functionDecl->getParamDecl(i);
QualType qt = parmVarDecl->getType().getDesugaredType(compiler.getASTContext());
if (!qt.isConstQualified() && qt->isReferenceType()) {
assignValue = "?";
bPotentiallyAssignedTo = true;
}
break;
}
}
return;
}
// check for function pointers
const FieldDecl* calleeFieldDecl = dyn_cast_or_null<FieldDecl>(callExpr->getCalleeDecl());
if (!calleeFieldDecl) {
return;
}
QualType qt = calleeFieldDecl->getType().getDesugaredType(compiler.getASTContext());
if (!qt->isPointerType()) {
return;
}
qt = qt->getPointeeType().getDesugaredType(compiler.getASTContext());
const FunctionProtoType* proto = qt->getAs<FunctionProtoType>();
if (!proto) {
return;
}
for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
if (i >= proto->getNumParams()) // can happen in template code
break;
if (callExpr->getArg(i) == child) {
QualType qt = proto->getParamType(i).getDesugaredType(compiler.getASTContext());
if (!qt.isConstQualified() && qt->isReferenceType()) {
assignValue = "?";
bPotentiallyAssignedTo = true;
}
break;
}
}
}
std::string SingleValFields::getExprValue(const Expr* arg)
{
if (!arg)
return "?";
arg = arg->IgnoreParenCasts();
arg = arg->IgnoreImplicit();
// ignore this, it seems to trigger an infinite recursion
if (isa<UnaryExprOrTypeTraitExpr>(arg))
return "?";
if (arg->isValueDependent())
return "?";
// for stuff like: OUString foo = "xxx";
if (auto stringLiteral = dyn_cast<clang::StringLiteral>(arg))
{
if (stringLiteral->getCharByteWidth() == 1)
return stringLiteral->getString().str();
return "?";
}
// ParenListExpr containing a CXXNullPtrLiteralExpr and has a NULL type pointer
if (auto parenListExpr = dyn_cast<ParenListExpr>(arg))
{
if (parenListExpr->getNumExprs() == 1)
return getExprValue(parenListExpr->getExpr(0));
return "?";
}
if (auto constructExpr = dyn_cast<CXXConstructExpr>(arg))
{
if (constructExpr->getNumArgs() >= 1
&& isa<clang::StringLiteral>(constructExpr->getArg(0)))
{
auto stringLiteral = dyn_cast<clang::StringLiteral>(constructExpr->getArg(0));
if (stringLiteral->getCharByteWidth() == 1)
return stringLiteral->getString().str();
return "?";
}
}
if (arg->getType()->isFloatingType())
{
APFloat x1(0.0f);
if (arg->EvaluateAsFloat(x1, compiler.getASTContext()))
{
std::string s;
llvm::raw_string_ostream os(s);
x1.print(os);
return os.str();
}
}
APSInt x1;
if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
return x1.toString(10);
if (isa<CXXNullPtrLiteralExpr>(arg))
return "0";
return "?";
}
loplugin::Plugin::Registration< SingleValFields > X("singlevalfields", false);
}
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */