1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
18 #include "functionaddress.hxx"
21 Find params on methods where the param is only ever passed as a single constant value.
23 The process goes something like this:
25 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='constantparam' check
26 $ ./compilerplugins/clang/constantparam.py
28 TODO look for OUString and OString params and check for call-params that are always either "" or default constructed
30 FIXME this plugin manages to trigger crashes inside clang, when calling EvaluateAsInt, so I end up disabling it for a handful of files
38 std::string returnType
;
39 std::string nameAndParams
;
40 std::string paramName
;
41 std::string paramType
;
42 int paramIndex
; // because in some declarations the names are empty
43 std::string callValue
;
44 std::string sourceLocation
;
46 bool operator < (const MyCallSiteInfo
&lhs
, const MyCallSiteInfo
&rhs
)
48 return std::tie(lhs
.sourceLocation
, lhs
.paramIndex
, lhs
.callValue
)
49 < std::tie(rhs
.sourceLocation
, rhs
.paramIndex
, rhs
.callValue
);
53 // try to limit the voluminous output a little
54 static std::set
<MyCallSiteInfo
> callSet
;
57 public loplugin::FunctionAddress
<loplugin::FilteringPlugin
<ConstantParam
>>
60 explicit ConstantParam(loplugin::InstantiationData
const & data
): FunctionAddress(data
) {}
62 virtual void run() override
64 handler
.enableTreeWideAnalysisMode();
66 // ignore some files that make clang crash inside EvaluateAsInt
67 std::string
fn(handler
.getMainFileName());
68 loplugin::normalizeDotDotInFilePath(fn
);
69 if (loplugin::isSamePathname(fn
, SRCDIR
"/basegfx/source/matrix/b2dhommatrix.cxx")
70 || loplugin::isSamePathname(fn
, SRCDIR
"/basegfx/source/matrix/b3dhommatrix.cxx"))
73 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
75 // this catches places that take the address of a method
76 for (auto functionDecl
: getFunctionsWithAddressTaken())
78 for (unsigned i
= 0; i
< functionDecl
->getNumParams(); ++i
)
79 addToCallSet(functionDecl
, i
, functionDecl
->getParamDecl(i
)->getName(), "unknown3");
82 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
83 // writing to the same logfile
86 for (const MyCallSiteInfo
& s
: callSet
)
87 output
+= s
.returnType
+ "\t" + s
.nameAndParams
+ "\t" + s
.sourceLocation
+ "\t"
88 + s
.paramName
+ "\t" + s
.paramType
+ "\t" + s
.callValue
+ "\n";
90 myfile
.open( WORKDIR
"/loplugin.constantparam.log", std::ios::app
| std::ios::out
);
95 bool shouldVisitTemplateInstantiations () const { return true; }
96 bool shouldVisitImplicitCode () const { return true; }
98 bool VisitCallExpr( const CallExpr
* );
99 bool VisitCXXConstructExpr( const CXXConstructExpr
* );
101 void addToCallSet(const FunctionDecl
* functionDecl
, int paramIndex
, llvm::StringRef paramName
, const std::string
& callValue
);
102 std::string
getCallValue(const Expr
* arg
);
105 void ConstantParam::addToCallSet(const FunctionDecl
* functionDecl
, int paramIndex
, llvm::StringRef paramName
, const std::string
& callValue
)
107 if (functionDecl
->getInstantiatedFromMemberFunction())
108 functionDecl
= functionDecl
->getInstantiatedFromMemberFunction();
109 else if (functionDecl
->getTemplateInstantiationPattern())
110 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
112 if (!functionDecl
->getNameInfo().getLoc().isValid())
114 if (functionDecl
->isVariadic())
116 // ignore stuff that forms part of the stable URE interface
117 if (isInUnoIncludeFile(functionDecl
))
120 SourceLocation expansionLoc
= compiler
.getSourceManager().getExpansionLoc( functionDecl
->getLocation() );
121 std::string filename
= getFilenameOfLocation(expansionLoc
).str();
122 loplugin::normalizeDotDotInFilePath(filename
);
123 if (!loplugin::hasPathnamePrefix(filename
, SRCDIR
"/"))
125 if (loplugin::hasPathnamePrefix(filename
, WORKDIR
"/"))
127 filename
= filename
.substr(strlen(SRCDIR
)+1);
129 MyCallSiteInfo aInfo
;
130 aInfo
.returnType
= functionDecl
->getReturnType().getCanonicalType().getAsString();
132 if (isa
<CXXMethodDecl
>(functionDecl
)) {
133 const CXXRecordDecl
* recordDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
)->getParent();
134 aInfo
.nameAndParams
+= recordDecl
->getQualifiedNameAsString();
135 aInfo
.nameAndParams
+= "::";
137 aInfo
.nameAndParams
+= functionDecl
->getNameAsString() + "(";
139 for (const ParmVarDecl
*pParmVarDecl
: functionDecl
->parameters()) {
143 aInfo
.nameAndParams
+= ",";
144 aInfo
.nameAndParams
+= pParmVarDecl
->getType().getCanonicalType().getAsString();
146 aInfo
.nameAndParams
+= ")";
147 if (isa
<CXXMethodDecl
>(functionDecl
) && dyn_cast
<CXXMethodDecl
>(functionDecl
)->isConst()) {
148 aInfo
.nameAndParams
+= " const";
150 aInfo
.paramName
= paramName
.str();
151 aInfo
.paramIndex
= paramIndex
;
152 if (paramIndex
< (int)functionDecl
->getNumParams())
153 aInfo
.paramType
= functionDecl
->getParamDecl(paramIndex
)->getType().getCanonicalType().getAsString();
155 aInfo
.callValue
= callValue
;
156 aInfo
.sourceLocation
= filename
+ ":" + std::to_string(compiler
.getSourceManager().getSpellingLineNumber(expansionLoc
));
158 callSet
.insert(aInfo
);
161 std::string
ConstantParam::getCallValue(const Expr
* arg
)
163 arg
= arg
->IgnoreParenCasts();
164 if (isa
<CXXDefaultArgExpr
>(arg
)) {
165 arg
= dyn_cast
<CXXDefaultArgExpr
>(arg
)->getExpr();
167 arg
= arg
->IgnoreParenCasts();
168 // ignore this, it seems to trigger an infinite recursion
169 if (isa
<UnaryExprOrTypeTraitExpr
>(arg
))
171 if (arg
->isValueDependent())
174 if (compat::EvaluateAsInt(arg
, x1
, compiler
.getASTContext()))
176 return compat::toString(x1
, 10);
178 if (isa
<CXXNullPtrLiteralExpr
>(arg
)) {
181 if (isa
<MaterializeTemporaryExpr
>(arg
))
183 const CXXBindTemporaryExpr
* strippedArg
= dyn_cast_or_null
<CXXBindTemporaryExpr
>(arg
->IgnoreParenCasts());
186 auto temp
= dyn_cast
<CXXTemporaryObjectExpr
>(strippedArg
->getSubExpr());
187 if (temp
->getNumArgs() == 0)
189 if (loplugin::TypeCheck(temp
->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
192 if (loplugin::TypeCheck(temp
->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
195 return "defaultConstruct";
200 // Get the expression contents.
201 // This helps us find params which are always initialised with something like "OUString()".
202 SourceManager
& SM
= compiler
.getSourceManager();
203 SourceLocation startLoc
= arg
->getBeginLoc();
204 SourceLocation endLoc
= arg
->getEndLoc();
205 const char *p1
= SM
.getCharacterData( startLoc
);
206 const char *p2
= SM
.getCharacterData( endLoc
);
207 if (!p1
|| !p2
|| (p2
- p1
) < 0 || (p2
- p1
) > 40) {
210 unsigned n
= Lexer::MeasureTokenLength( endLoc
, SM
, compiler
.getLangOpts());
211 std::string
s( p1
, p2
- p1
+ n
);
212 // sanitize call value, makes using command line tools (and python) much less error prone
213 for (auto const & ch
: s
)
217 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
218 if (s
== "OUString()")
220 else if (s
== "OString()")
225 bool ConstantParam::VisitCallExpr(const CallExpr
* callExpr
) {
226 if (ignoreLocation(callExpr
)) {
229 const FunctionDecl
* functionDecl
;
230 if (isa
<CXXMemberCallExpr
>(callExpr
)) {
231 functionDecl
= dyn_cast
<CXXMemberCallExpr
>(callExpr
)->getMethodDecl();
234 functionDecl
= callExpr
->getDirectCallee();
238 functionDecl
= functionDecl
->getCanonicalDecl();
239 // method overrides don't always specify the same default params (although they probably should)
240 // so we need to work our way up to the root method
241 while (isa
<CXXMethodDecl
>(functionDecl
)) {
242 const CXXMethodDecl
* methodDecl
= dyn_cast
<CXXMethodDecl
>(functionDecl
);
243 if (methodDecl
->size_overridden_methods()==0)
245 functionDecl
= *methodDecl
->begin_overridden_methods();
247 // work our way back to the root definition for template methods
248 if (functionDecl
->getInstantiatedFromMemberFunction())
249 functionDecl
= functionDecl
->getInstantiatedFromMemberFunction();
250 else if (functionDecl
->getTemplateInstantiationPattern())
251 functionDecl
= functionDecl
->getTemplateInstantiationPattern();
253 unsigned len
= std::max(callExpr
->getNumArgs(), functionDecl
->getNumParams());
254 for (unsigned i
= 0; i
< len
; ++i
) {
256 if (i
< callExpr
->getNumArgs())
257 valExpr
= callExpr
->getArg(i
);
258 else if (i
< functionDecl
->getNumParams() && functionDecl
->getParamDecl(i
)->hasDefaultArg())
259 valExpr
= functionDecl
->getParamDecl(i
)->getDefaultArg();
261 // can happen in template code
263 std::string callValue
= getCallValue(valExpr
);
264 std::string paramName
= i
< functionDecl
->getNumParams()
265 ? functionDecl
->getParamDecl(i
)->getName().str()
266 : "###" + std::to_string(i
);
267 addToCallSet(functionDecl
, i
, paramName
, callValue
);
272 bool ConstantParam::VisitCXXConstructExpr( const CXXConstructExpr
* constructExpr
)
274 const CXXConstructorDecl
* constructorDecl
= constructExpr
->getConstructor();
275 constructorDecl
= constructorDecl
->getCanonicalDecl();
277 unsigned len
= std::max(constructExpr
->getNumArgs(), constructorDecl
->getNumParams());
278 for (unsigned i
= 0; i
< len
; ++i
) {
280 if (i
< constructExpr
->getNumArgs())
281 valExpr
= constructExpr
->getArg(i
);
282 else if (i
< constructorDecl
->getNumParams() && constructorDecl
->getParamDecl(i
)->hasDefaultArg())
283 valExpr
= constructorDecl
->getParamDecl(i
)->getDefaultArg();
285 // can happen in template code
287 std::string callValue
= getCallValue(valExpr
);
288 std::string paramName
= i
< constructorDecl
->getNumParams()
289 ? constructorDecl
->getParamDecl(i
)->getName().str()
290 : "###" + std::to_string(i
);
291 addToCallSet(constructorDecl
, i
, paramName
, callValue
);
297 loplugin::Plugin::Registration
< ConstantParam
> X("constantparam", false);
301 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */