bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / constparams.cxx
blob28179f30abae0d3957429a123c8fb1c11c158861
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <algorithm>
11 #include <string>
12 #include <unordered_set>
13 #include <unordered_map>
14 #include <iostream>
16 #include "plugin.hxx"
17 #include "compat.hxx"
18 #include "check.hxx"
19 #include "functionaddress.hxx"
21 /**
22 Find pointer and reference params that can be declared const.
24 This is not a sophisticated analysis. It deliberately skips all of the hard cases for now.
25 It is an exercise in getting the most benefit for the least effort.
27 namespace
30 class ConstParams:
31 public loplugin::FunctionAddress<loplugin::FilteringPlugin<ConstParams>>
33 public:
34 explicit ConstParams(loplugin::InstantiationData const & data): FunctionAddress(data) {}
36 virtual void run() override {
37 std::string fn(handler.getMainFileName());
38 loplugin::normalizeDotDotInFilePath(fn);
39 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/")
40 || fn == SRCDIR "/jurt/source/pipe/staticsalhack.cxx"
41 || loplugin::hasPathnamePrefix(fn, SRCDIR "/bridges/")
42 || loplugin::hasPathnamePrefix(fn, SRCDIR "/binaryurp/")
43 || loplugin::hasPathnamePrefix(fn, SRCDIR "/stoc/")
44 || loplugin::hasPathnamePrefix(fn, WORKDIR "/YaccTarget/unoidl/source/sourceprovider-parser.cxx")
45 // some weird calling through a function pointer
46 || loplugin::hasPathnamePrefix(fn, SRCDIR "/svtools/source/table/defaultinputhandler.cxx")
47 || loplugin::hasPathnamePrefix(fn, SRCDIR "/sdext/source/pdfimport/test/pdfunzip.cxx")
48 // windows only
49 || loplugin::hasPathnamePrefix(fn, SRCDIR "/basic/source/sbx/sbxdec.cxx")
50 || loplugin::hasPathnamePrefix(fn, SRCDIR "/sfx2/source/doc/syspath.cxx")
51 // ignore this for now
52 || loplugin::hasPathnamePrefix(fn, SRCDIR "/libreofficekit")
53 // FunctionAddress not working well enough here
54 || loplugin::hasPathnamePrefix(fn, SRCDIR "/pyuno/source/module/pyuno_struct.cxx")
55 || loplugin::hasPathnamePrefix(fn, SRCDIR "/pyuno/source/module/pyuno.cxx")
56 || loplugin::hasPathnamePrefix(fn, SRCDIR "/sw/source/filter/ascii/ascatr.cxx")
58 return;
60 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
62 for (const ParmVarDecl *pParmVarDecl : interestingParamSet) {
63 auto functionDecl = parmToFunction[pParmVarDecl];
64 auto canonicalDecl = functionDecl->getCanonicalDecl();
65 if (getFunctionsWithAddressTaken().find(canonicalDecl)
66 != getFunctionsWithAddressTaken().end())
68 continue;
70 std::string fname = functionDecl->getQualifiedNameAsString();
71 report(
72 DiagnosticsEngine::Warning,
73 "this parameter can be const %0",
74 compat::getBeginLoc(pParmVarDecl))
75 << fname << pParmVarDecl->getSourceRange();
76 if (canonicalDecl->getLocation() != functionDecl->getLocation()) {
77 unsigned idx = pParmVarDecl->getFunctionScopeIndex();
78 const ParmVarDecl* pOther = canonicalDecl->getParamDecl(idx);
79 report(
80 DiagnosticsEngine::Note,
81 "canonical parameter declaration here",
82 compat::getBeginLoc(pOther))
83 << pOther->getSourceRange();
85 //functionDecl->dump();
89 bool TraverseFunctionDecl(FunctionDecl *);
90 bool TraverseCXXMethodDecl(CXXMethodDecl * f);
91 bool TraverseCXXConstructorDecl(CXXConstructorDecl * f);
92 bool VisitDeclRefExpr(const DeclRefExpr *);
94 private:
95 bool CheckTraverseFunctionDecl(FunctionDecl *);
96 bool checkIfCanBeConst(const Stmt*, const ParmVarDecl*);
97 // integral or enumeration or const * or const &
98 bool isOkForParameter(const QualType& qt);
99 bool isPointerOrReferenceToNonConst(const QualType& qt);
101 std::unordered_set<const ParmVarDecl*> interestingParamSet;
102 std::unordered_map<const ParmVarDecl*, const FunctionDecl*> parmToFunction;
103 FunctionDecl* currentFunctionDecl = nullptr;
106 bool ConstParams::TraverseFunctionDecl(FunctionDecl * functionDecl)
108 // We cannot short-circuit the traverse here entirely without breaking the
109 // loplugin::FunctionAddress stuff.
110 auto prev = currentFunctionDecl;
111 if (CheckTraverseFunctionDecl(functionDecl))
112 currentFunctionDecl = functionDecl;
113 auto rv = FunctionAddress::TraverseFunctionDecl(functionDecl);
114 currentFunctionDecl = prev;
115 return rv;
117 bool ConstParams::TraverseCXXMethodDecl(CXXMethodDecl * f)
119 auto prev = currentFunctionDecl;
120 if (CheckTraverseFunctionDecl(f))
121 currentFunctionDecl = f;
122 auto rv = FunctionAddress::TraverseCXXMethodDecl(f);
123 currentFunctionDecl = prev;
124 return rv;
126 bool ConstParams::TraverseCXXConstructorDecl(CXXConstructorDecl * f)
128 auto prev = currentFunctionDecl;
129 if (CheckTraverseFunctionDecl(f))
130 currentFunctionDecl = f;
131 auto rv = FunctionAddress::TraverseCXXConstructorDecl(f);
132 currentFunctionDecl = prev;
133 return rv;
136 bool ConstParams::CheckTraverseFunctionDecl(FunctionDecl * functionDecl)
138 if (ignoreLocation(functionDecl) || !functionDecl->isThisDeclarationADefinition()) {
139 return false;
141 // ignore stuff that forms part of the stable URE interface
142 if (isInUnoIncludeFile(functionDecl)) {
143 return false;
145 if (functionDecl->isDeleted())
146 return false;
147 // ignore virtual methods
148 if (isa<CXXMethodDecl>(functionDecl)
149 && dyn_cast<CXXMethodDecl>(functionDecl)->isVirtual() ) {
150 return false;
152 // ignore C main
153 if (functionDecl->isMain()) {
154 return false;
157 // ignore the macros from include/tools/link.hxx
158 auto canonicalDecl = functionDecl->getCanonicalDecl();
159 if (compiler.getSourceManager().isMacroBodyExpansion(compat::getBeginLoc(canonicalDecl))
160 || compiler.getSourceManager().isMacroArgExpansion(compat::getBeginLoc(canonicalDecl))) {
161 StringRef name { Lexer::getImmediateMacroName(
162 compat::getBeginLoc(canonicalDecl), compiler.getSourceManager(), compiler.getLangOpts()) };
163 if (name.startswith("DECL_LINK") || name.startswith("DECL_STATIC_LINK"))
164 return false;
165 auto loc2 = compat::getImmediateExpansionRange(compiler.getSourceManager(), compat::getBeginLoc(canonicalDecl)).first;
166 if (compiler.getSourceManager().isMacroBodyExpansion(loc2))
168 StringRef name2 { Lexer::getImmediateMacroName(
169 loc2, compiler.getSourceManager(), compiler.getLangOpts()) };
170 if (name2.startswith("DECL_DLLPRIVATE_LINK"))
171 return false;
175 if (functionDecl->getIdentifier())
177 StringRef name = functionDecl->getName();
178 if ( name == "file_write"
179 || name == "SalMainPipeExchangeSignal_impl"
180 || name.startswith("SbRtl_")
181 || name == "GoNext"
182 || name == "GoPrevious"
183 || name.startswith("Read_F_")
184 // UNO component entry points
185 || name.endswith("component_getFactory")
186 || name == "egiGraphicExport"
187 || name == "etiGraphicExport"
188 || name == "epsGraphicExport"
189 // callback for some external code?
190 || name == "ScAddInAsyncCallBack"
191 // used as function pointers
192 || name == "Read_Footnote"
193 || name == "Read_Field"
194 || name == "Read_And"
195 // passed as a LINK<> to another method
196 || name == "GlobalBasicErrorHdl_Impl"
197 // template
198 || name == "extract_throw" || name == "readProp"
199 // callbacks
200 || name == "signalDragDropReceived" || name == "signal_column_clicked" || name == "signal_key_press"
202 return false;
206 std::string fqn = functionDecl->getQualifiedNameAsString();
207 if ( fqn == "connectivity::jdbc::GlobalRef::set"
208 || fqn == "(anonymous namespace)::ReorderNotifier::operator()"
209 || fqn == "static_txtattr_cast")
210 return false;
212 // calculate the ones we want to check
213 bool foundInterestingParam = false;
214 for (const ParmVarDecl *pParmVarDecl : functionDecl->parameters()) {
215 // ignore unused params
216 if (pParmVarDecl->getName().empty()
217 || pParmVarDecl->hasAttr<UnusedAttr>())
218 continue;
219 auto const type = loplugin::TypeCheck(pParmVarDecl->getType());
220 if (!( type.Pointer().NonConst()
221 || type.LvalueReference().NonConst()))
222 continue;
223 // since we normally can't change typedefs, just ignore them
224 if (isa<TypedefType>(pParmVarDecl->getType()))
225 continue;
226 // some typedefs turn into these
227 if (isa<DecayedType>(pParmVarDecl->getType()))
228 continue;
229 // TODO ignore these for now, has some effects I don't understand
230 if (type.Pointer().Pointer())
231 continue;
232 // const is meaningless when applied to function pointer types
233 if (pParmVarDecl->getType()->isFunctionPointerType())
234 continue;
235 interestingParamSet.insert(pParmVarDecl);
236 parmToFunction[pParmVarDecl] = functionDecl;
237 foundInterestingParam = true;
239 return foundInterestingParam;
242 bool ConstParams::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
244 if (!currentFunctionDecl)
245 return true;
246 const ParmVarDecl* parmVarDecl = dyn_cast_or_null<ParmVarDecl>(declRefExpr->getDecl());
247 if (!parmVarDecl)
248 return true;
249 if (interestingParamSet.find(parmVarDecl) == interestingParamSet.end())
250 return true;
251 if (!checkIfCanBeConst(declRefExpr, parmVarDecl))
252 interestingParamSet.erase(parmVarDecl);
253 return true;
256 // Walk up from a statement that contains a DeclRefExpr, checking if the usage means that the
257 // related ParamVarDecl can be const.
258 bool ConstParams::checkIfCanBeConst(const Stmt* stmt, const ParmVarDecl* parmVarDecl)
260 const Stmt* parent = getParentStmt( stmt );
261 if (!parent)
263 // check if we're inside a CXXCtorInitializer
264 auto parentsRange = compiler.getASTContext().getParents(*stmt);
265 if ( parentsRange.begin() != parentsRange.end())
267 if (auto cxxConstructorDecl = dyn_cast_or_null<CXXConstructorDecl>(parentsRange.begin()->get<Decl>()))
269 for ( auto cxxCtorInitializer : cxxConstructorDecl->inits())
271 if ( cxxCtorInitializer->getInit() == stmt)
273 if (cxxCtorInitializer->isAnyMemberInitializer())
275 // if the member is not pointer-to-const or ref-to-const or value, we cannot make the param const
276 auto fieldDecl = cxxCtorInitializer->getAnyMember();
277 auto tc = loplugin::TypeCheck(fieldDecl->getType());
278 if (tc.Pointer() || tc.LvalueReference())
279 return tc.Pointer().Const() || tc.LvalueReference().Const();
280 else
281 return true;
283 else
285 // probably base initialiser, but no simple way to look up the relevant constructor decl
286 return false;
291 if (auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>()))
293 return isOkForParameter(varDecl->getType());
296 parmVarDecl->dump();
297 stmt->dump();
298 report(
299 DiagnosticsEngine::Warning,
300 "no parent?",
301 compat::getBeginLoc(stmt))
302 << stmt->getSourceRange();
303 return false;
306 if (auto unaryOperator = dyn_cast<UnaryOperator>(parent)) {
307 UnaryOperator::Opcode op = unaryOperator->getOpcode();
308 if (op == UO_AddrOf || op == UO_PreInc || op == UO_PostInc
309 || op == UO_PreDec || op == UO_PostDec) {
310 return false;
312 if (op == UO_Deref) {
313 return checkIfCanBeConst(parent, parmVarDecl);
315 return true;
316 } else if (auto binaryOp = dyn_cast<BinaryOperator>(parent)) {
317 BinaryOperator::Opcode op = binaryOp->getOpcode();
318 if (binaryOp->getRHS() == stmt && op == BO_Assign) {
319 return isOkForParameter(binaryOp->getLHS()->getType());
321 if (binaryOp->getRHS() == stmt) {
322 return true;
324 if (op == BO_Assign || op == BO_PtrMemD || op == BO_PtrMemI || op == BO_MulAssign
325 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
326 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
327 || op == BO_AndAssign || op == BO_XorAssign || op == BO_OrAssign) {
328 return false;
330 // for pointer arithmetic need to check parent
331 if (binaryOp->getType()->isPointerType()) {
332 return checkIfCanBeConst(parent, parmVarDecl);
334 return true;
335 } else if (auto constructExpr = dyn_cast<CXXConstructExpr>(parent)) {
336 const CXXConstructorDecl * constructorDecl = constructExpr->getConstructor();
337 for (unsigned i = 0; i < constructExpr->getNumArgs(); ++i) {
338 if (constructExpr->getArg(i) == stmt) {
339 return isOkForParameter(constructorDecl->getParamDecl(i)->getType());
342 } else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent)) {
343 const CXXMethodDecl* calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(operatorCallExpr->getDirectCallee());
344 if (calleeMethodDecl) {
345 // unary operator
346 if (calleeMethodDecl->getNumParams() == 0)
347 return calleeMethodDecl->isConst();
348 // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
349 // doesn't have yet.
350 auto Opc = operatorCallExpr->getOperator();
351 if (Opc == OO_Equal || Opc == OO_StarEqual ||
352 Opc == OO_SlashEqual || Opc == OO_PercentEqual ||
353 Opc == OO_PlusEqual || Opc == OO_MinusEqual ||
354 Opc == OO_LessLessEqual || Opc == OO_GreaterGreaterEqual ||
355 Opc == OO_AmpEqual || Opc == OO_CaretEqual ||
356 Opc == OO_PipeEqual)
358 if (operatorCallExpr->getArg(0) == stmt) // assigning to the param
359 return false;
360 // not all operator= take a const&
361 return isOkForParameter(calleeMethodDecl->getParamDecl(0)->getType());
363 if (operatorCallExpr->getOperator() == OO_Subscript && operatorCallExpr->getArg(1) == stmt)
364 return true;
365 if (operatorCallExpr->getOperator() == OO_EqualEqual || operatorCallExpr->getOperator() == OO_ExclaimEqual)
366 return true;
367 // binary operator
368 if (operatorCallExpr->getArg(0) == stmt)
369 return calleeMethodDecl->isConst();
370 unsigned const n = std::min(
371 operatorCallExpr->getNumArgs(),
372 calleeMethodDecl->getNumParams() + 1);
373 for (unsigned i = 1; i < n; ++i)
374 if (operatorCallExpr->getArg(i) == stmt) {
375 auto qt = calleeMethodDecl->getParamDecl(i - 1)->getType();
376 return isOkForParameter(qt);
378 } else {
379 const Expr* callee = operatorCallExpr->getCallee()->IgnoreParenImpCasts();
380 const DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
381 const FunctionDecl* calleeFunctionDecl = nullptr;
382 if (dr) {
383 calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
385 if (calleeFunctionDecl) {
386 for (unsigned i = 0; i < operatorCallExpr->getNumArgs(); ++i) {
387 if (operatorCallExpr->getArg(i) == stmt) {
388 return isOkForParameter(calleeFunctionDecl->getParamDecl(i)->getType());
393 return false;
394 } else if (auto callExpr = dyn_cast<CallExpr>(parent)) {
395 QualType functionType = callExpr->getCallee()->getType();
396 if (functionType->isFunctionPointerType()) {
397 functionType = functionType->getPointeeType();
399 if (const FunctionProtoType* prototype = functionType->getAs<FunctionProtoType>()) {
400 // TODO could do better
401 if (prototype->isVariadic()) {
402 return false;
404 if (callExpr->getCallee() == stmt) {
405 return true;
407 for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
408 if (callExpr->getArg(i) == stmt) {
409 return isOkForParameter(prototype->getParamType(i));
413 const FunctionDecl* calleeFunctionDecl = callExpr->getDirectCallee();
414 if (calleeFunctionDecl)
416 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(parent)) {
417 const MemberExpr* memberExpr = dyn_cast<MemberExpr>(stmt);
418 if (memberExpr && memberCallExpr->getImplicitObjectArgument() == memberExpr->getBase())
420 const CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
421 return calleeMethodDecl->isConst();
424 // TODO could do better
425 if (calleeFunctionDecl->isVariadic()) {
426 return false;
428 if (callExpr->getCallee() == stmt) {
429 return true;
431 for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
432 if (i >= calleeFunctionDecl->getNumParams()) // can happen in template code
433 return false;
434 if (callExpr->getArg(i) == stmt) {
435 return isOkForParameter(calleeFunctionDecl->getParamDecl(i)->getType());
439 return false;
440 } else if (auto callExpr = dyn_cast<ObjCMessageExpr>(parent)) {
441 if (callExpr->getInstanceReceiver() == stmt) {
442 return true;
444 if (auto const method = callExpr->getMethodDecl()) {
445 // TODO could do better
446 if (method->isVariadic()) {
447 return false;
449 assert(method->param_size() == callExpr->getNumArgs());
450 for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
451 if (callExpr->getArg(i) == stmt) {
452 return isOkForParameter(
453 method->param_begin()[i]->getType());
457 } else if (isa<CXXReinterpretCastExpr>(parent)) {
458 return false;
459 } else if (isa<CXXConstCastExpr>(parent)) {
460 return false;
461 } else if (isa<CastExpr>(parent)) { // all other cast expression subtypes
462 if (auto e = dyn_cast<ExplicitCastExpr>(parent)) {
463 if (loplugin::TypeCheck(e->getTypeAsWritten()).Void()) {
464 if (auto const sub = dyn_cast<DeclRefExpr>(
465 e->getSubExpr()->IgnoreParenImpCasts()))
467 if (sub->getDecl() == parmVarDecl)
468 return false;
472 return checkIfCanBeConst(parent, parmVarDecl);
473 } else if (isa<MemberExpr>(parent)) {
474 return checkIfCanBeConst(parent, parmVarDecl);
475 } else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent)) {
476 if (arraySubscriptExpr->getIdx() == stmt)
477 return true;
478 return checkIfCanBeConst(parent, parmVarDecl);
479 } else if (isa<ParenExpr>(parent)) {
480 return checkIfCanBeConst(parent, parmVarDecl);
481 } else if (isa<DeclStmt>(parent)) {
482 // TODO could do better here, but would require tracking the target(s)
483 //return false;
484 } else if (isa<ReturnStmt>(parent)) {
485 return !isPointerOrReferenceToNonConst(currentFunctionDecl->getReturnType());
486 } else if (isa<InitListExpr>(parent)) {
487 return false;
488 } else if (isa<IfStmt>(parent)) {
489 return true;
490 } else if (isa<WhileStmt>(parent)) {
491 return true;
492 } else if (isa<ForStmt>(parent)) {
493 return true;
494 } else if (isa<CompoundStmt>(parent)) {
495 return true;
496 } else if (isa<SwitchStmt>(parent)) {
497 return true;
498 } else if (isa<DoStmt>(parent)) {
499 return true;
500 } else if (isa<CXXDeleteExpr>(parent)) {
501 return false;
502 } else if (isa<VAArgExpr>(parent)) {
503 return false;
504 } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
505 return false;
506 } else if (isa<MaterializeTemporaryExpr>(parent)) {
507 return checkIfCanBeConst(parent, parmVarDecl);
508 } else if (auto conditionalExpr = dyn_cast<ConditionalOperator>(parent)) {
509 if (conditionalExpr->getCond() == stmt)
510 return true;
511 return checkIfCanBeConst(parent, parmVarDecl);
512 } else if (isa<UnaryExprOrTypeTraitExpr>(parent)) {
513 return false; // ???
514 } else if (auto cxxNewExpr = dyn_cast<CXXNewExpr>(parent)) {
515 for (unsigned i = 0; i < cxxNewExpr->getNumPlacementArgs(); ++i)
516 if (cxxNewExpr->getPlacementArg(i) == stmt)
517 return false;
518 return true; // ???
519 } else if (auto lambdaExpr = dyn_cast<LambdaExpr>(parent)) {
520 for (auto it = lambdaExpr->capture_begin(); it != lambdaExpr->capture_end(); ++it)
522 if (it->capturesVariable() && it->getCapturedVar() == parmVarDecl)
523 return it->getCaptureKind() != LCK_ByRef;
525 return false;
526 } else if (isa<CXXTypeidExpr>(parent)) {
527 return true;
528 } else if (isa<ParenListExpr>(parent)) {
529 return false; // could be improved, seen in constructors when calling base class constructor
530 } else if (isa<CXXUnresolvedConstructExpr>(parent)) {
531 return false;
532 } else if (isa<UnresolvedMemberExpr>(parent)) {
533 return false;
534 } else if (isa<PackExpansionExpr>(parent)) {
535 return false;
536 } else if (isa<ExprWithCleanups>(parent)) {
537 return checkIfCanBeConst(parent, parmVarDecl);
538 } else if (isa<CaseStmt>(parent)) {
539 return true;
540 } else if (isa<CXXPseudoDestructorExpr>(parent)) {
541 return false;
542 } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
543 return false;
544 } else if (isa<ObjCIvarRefExpr>(parent)) {
545 return checkIfCanBeConst(parent, parmVarDecl);
547 parent->dump();
548 parmVarDecl->dump();
549 report(
550 DiagnosticsEngine::Warning,
551 "oh dear, what can the matter be?",
552 compat::getBeginLoc(parent))
553 << parent->getSourceRange();
554 return true;
557 bool ConstParams::isOkForParameter(const QualType& qt) {
558 if (qt->isIntegralOrEnumerationType())
559 return true;
560 auto const type = loplugin::TypeCheck(qt);
561 if (type.Pointer()) {
562 return bool(type.Pointer().Const());
563 } else if (type.LvalueReference().Const().Pointer()) {
564 // If we have a method that takes (T* t) and it calls std::vector<T*>::push_back
565 // then the type of push_back is T * const &
566 // There is probably a more elegant way to check this, but it will probably require
567 // recalculating types while walking up the AST.
568 return false;
569 } else if (type.LvalueReference()) {
570 return bool(type.LvalueReference().Const());
572 return false;
575 bool ConstParams::isPointerOrReferenceToNonConst(const QualType& qt) {
576 auto const type = loplugin::TypeCheck(qt);
577 if (type.Pointer()) {
578 return !bool(type.Pointer().Const());
579 } else if (type.LvalueReference()) {
580 return !bool(type.LvalueReference().Const());
582 return false;
585 loplugin::Plugin::Registration< ConstParams > X("constparams", false);
589 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */