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/.
12 #include <unordered_set>
13 #include <unordered_map>
19 #include "functionaddress.hxx"
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.
31 public loplugin::FunctionAddress
<ConstParams
>
34 explicit ConstParams(loplugin::InstantiationData
const & data
): loplugin::FunctionAddress
<ConstParams
>(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")
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")
56 // BuiltinType '<bound member function type>'
57 // and the AST gives me no further useful information.
58 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sw/source/core/doc/docfly.cxx")
59 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sw/source/core/doc/DocumentContentOperationsManager.cxx")
60 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sw/source/core/fields/cellfml.cxx")
61 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sw/source/filter/ww8/ww8par6.cxx")
65 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
67 for (const ParmVarDecl
*pParmVarDecl
: interestingParamSet
) {
68 auto functionDecl
= parmToFunction
[pParmVarDecl
];
69 auto canonicalDecl
= functionDecl
->getCanonicalDecl();
70 if (getFunctionsWithAddressTaken().find(canonicalDecl
)
71 != getFunctionsWithAddressTaken().end())
75 std::string fname
= functionDecl
->getQualifiedNameAsString();
77 DiagnosticsEngine::Warning
,
78 "this parameter can be const %0",
79 compat::getBeginLoc(pParmVarDecl
))
80 << fname
<< pParmVarDecl
->getSourceRange();
81 if (canonicalDecl
->getLocation() != functionDecl
->getLocation()) {
82 unsigned idx
= pParmVarDecl
->getFunctionScopeIndex();
83 const ParmVarDecl
* pOther
= canonicalDecl
->getParamDecl(idx
);
85 DiagnosticsEngine::Note
,
86 "canonical parameter declaration here",
87 compat::getBeginLoc(pOther
))
88 << pOther
->getSourceRange();
90 //functionDecl->dump();
94 bool TraverseFunctionDecl(FunctionDecl
*);
95 bool TraverseCXXMethodDecl(CXXMethodDecl
* f
);
96 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* f
);
97 bool VisitDeclRefExpr(const DeclRefExpr
*);
100 bool CheckTraverseFunctionDecl(FunctionDecl
*);
101 bool checkIfCanBeConst(const Stmt
*, const ParmVarDecl
*);
102 // integral or enumeration or const * or const &
103 bool isOkForParameter(const QualType
& qt
);
104 bool isPointerOrReferenceToNonConst(const QualType
& qt
);
106 std::unordered_set
<const ParmVarDecl
*> interestingParamSet
;
107 std::unordered_map
<const ParmVarDecl
*, const FunctionDecl
*> parmToFunction
;
108 FunctionDecl
* currentFunctionDecl
= nullptr;
111 bool ConstParams::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
113 // We cannot short-circuit the traverse here entirely without breaking the
114 // loplugin::FunctionAddress stuff.
115 auto prev
= currentFunctionDecl
;
116 if (CheckTraverseFunctionDecl(functionDecl
))
117 currentFunctionDecl
= functionDecl
;
118 auto rv
= loplugin::FunctionAddress
<ConstParams
>::TraverseFunctionDecl(functionDecl
);
119 currentFunctionDecl
= prev
;
122 bool ConstParams::TraverseCXXMethodDecl(CXXMethodDecl
* f
)
124 auto prev
= currentFunctionDecl
;
125 if (CheckTraverseFunctionDecl(f
))
126 currentFunctionDecl
= f
;
127 auto rv
= loplugin::FunctionAddress
<ConstParams
>::TraverseCXXMethodDecl(f
);
128 currentFunctionDecl
= prev
;
131 bool ConstParams::TraverseCXXConstructorDecl(CXXConstructorDecl
* f
)
133 auto prev
= currentFunctionDecl
;
134 if (CheckTraverseFunctionDecl(f
))
135 currentFunctionDecl
= f
;
136 auto rv
= loplugin::FunctionAddress
<ConstParams
>::TraverseCXXConstructorDecl(f
);
137 currentFunctionDecl
= prev
;
141 bool ConstParams::CheckTraverseFunctionDecl(FunctionDecl
* functionDecl
)
143 if (ignoreLocation(functionDecl
) || !functionDecl
->isThisDeclarationADefinition()) {
146 // ignore stuff that forms part of the stable URE interface
147 if (isInUnoIncludeFile(functionDecl
)) {
150 if (functionDecl
->isDeleted())
152 // ignore virtual methods
153 if (isa
<CXXMethodDecl
>(functionDecl
)
154 && dyn_cast
<CXXMethodDecl
>(functionDecl
)->isVirtual() ) {
158 if (functionDecl
->isMain()) {
162 // ignore the macros from include/tools/link.hxx
163 auto canonicalDecl
= functionDecl
->getCanonicalDecl();
164 if (compiler
.getSourceManager().isMacroBodyExpansion(compat::getBeginLoc(canonicalDecl
))
165 || compiler
.getSourceManager().isMacroArgExpansion(compat::getBeginLoc(canonicalDecl
))) {
166 StringRef name
{ Lexer::getImmediateMacroName(
167 compat::getBeginLoc(canonicalDecl
), compiler
.getSourceManager(), compiler
.getLangOpts()) };
168 if (name
.startswith("DECL_LINK") || name
.startswith("DECL_STATIC_LINK"))
170 auto loc2
= compat::getImmediateExpansionRange(compiler
.getSourceManager(), compat::getBeginLoc(canonicalDecl
)).first
;
171 if (compiler
.getSourceManager().isMacroBodyExpansion(loc2
))
173 StringRef name2
{ Lexer::getImmediateMacroName(
174 loc2
, compiler
.getSourceManager(), compiler
.getLangOpts()) };
175 if (name2
.startswith("DECL_DLLPRIVATE_LINK"))
180 if (functionDecl
->getIdentifier())
182 StringRef name
= functionDecl
->getName();
183 if ( name
== "file_write"
184 || name
== "SalMainPipeExchangeSignal_impl"
185 || name
.startswith("SbRtl_")
187 || name
== "GoPrevious"
188 || name
.startswith("Read_F_")
189 // UNO component entry points
190 || name
.endswith("component_getFactory")
191 || name
== "egiGraphicExport"
192 || name
== "etiGraphicExport"
193 || name
== "epsGraphicExport"
194 // callback for some external code?
195 || name
== "ScAddInAsyncCallBack"
196 // used as function pointers
197 || name
== "Read_Footnote"
198 || name
== "Read_Field"
199 || name
== "Read_And"
200 // passed as a LINK<> to another method
201 || name
== "GlobalBasicErrorHdl_Impl"
203 || name
== "extract_throw" || name
== "readProp"
209 std::string fqn
= functionDecl
->getQualifiedNameAsString();
210 if ( fqn
== "connectivity::jdbc::GlobalRef::set"
211 || fqn
== "(anonymous namespace)::ReorderNotifier::operator()"
212 || fqn
== "static_txtattr_cast")
215 // calculate the ones we want to check
216 bool foundInterestingParam
= false;
217 for (const ParmVarDecl
*pParmVarDecl
: functionDecl
->parameters()) {
218 // ignore unused params
219 if (pParmVarDecl
->getName().empty()
220 || pParmVarDecl
->hasAttr
<UnusedAttr
>())
222 auto const type
= loplugin::TypeCheck(pParmVarDecl
->getType());
223 if (!( type
.Pointer().NonConst()
224 || type
.LvalueReference().NonConst()))
226 // since we normally can't change typedefs, just ignore them
227 if (isa
<TypedefType
>(pParmVarDecl
->getType()))
229 // some typedefs turn into these
230 if (isa
<DecayedType
>(pParmVarDecl
->getType()))
232 // TODO ignore these for now, has some effects I don't understand
233 if (type
.Pointer().Pointer())
235 // const is meaningless when applied to function pointer types
236 if (pParmVarDecl
->getType()->isFunctionPointerType())
238 interestingParamSet
.insert(pParmVarDecl
);
239 parmToFunction
[pParmVarDecl
] = functionDecl
;
240 foundInterestingParam
= true;
242 return foundInterestingParam
;
245 bool ConstParams::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
247 if (!currentFunctionDecl
)
249 const ParmVarDecl
* parmVarDecl
= dyn_cast_or_null
<ParmVarDecl
>(declRefExpr
->getDecl());
252 if (interestingParamSet
.find(parmVarDecl
) == interestingParamSet
.end())
254 if (!checkIfCanBeConst(declRefExpr
, parmVarDecl
))
255 interestingParamSet
.erase(parmVarDecl
);
259 // Walk up from a statement that contains a DeclRefExpr, checking if the usage means that the
260 // related ParamVarDecl can be const.
261 bool ConstParams::checkIfCanBeConst(const Stmt
* stmt
, const ParmVarDecl
* parmVarDecl
)
263 const Stmt
* parent
= getParentStmt( stmt
);
266 // check if we're inside a CXXCtorInitializer
267 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
268 if ( parentsRange
.begin() != parentsRange
.end())
270 if (auto cxxConstructorDecl
= dyn_cast_or_null
<CXXConstructorDecl
>(parentsRange
.begin()->get
<Decl
>()))
272 for ( auto cxxCtorInitializer
: cxxConstructorDecl
->inits())
274 if ( cxxCtorInitializer
->getInit() == stmt
)
276 if (cxxCtorInitializer
->isAnyMemberInitializer())
278 // if the member is not pointer-to-const or ref-to-const or value, we cannot make the param const
279 auto fieldDecl
= cxxCtorInitializer
->getAnyMember();
280 auto tc
= loplugin::TypeCheck(fieldDecl
->getType());
281 if (tc
.Pointer() || tc
.LvalueReference())
282 return tc
.Pointer().Const() || tc
.LvalueReference().Const();
288 // probably base initialiser, but no simple way to look up the relevant constructor decl
294 if (auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>()))
296 return isOkForParameter(varDecl
->getType());
302 DiagnosticsEngine::Warning
,
304 compat::getBeginLoc(stmt
))
305 << stmt
->getSourceRange();
309 if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
)) {
310 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
311 if (op
== UO_AddrOf
|| op
== UO_PreInc
|| op
== UO_PostInc
312 || op
== UO_PreDec
|| op
== UO_PostDec
) {
315 if (op
== UO_Deref
) {
316 return checkIfCanBeConst(parent
, parmVarDecl
);
319 } else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
)) {
320 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
321 if (binaryOp
->getRHS() == stmt
&& op
== BO_Assign
) {
322 return isOkForParameter(binaryOp
->getLHS()->getType());
324 if (binaryOp
->getRHS() == stmt
) {
327 if (op
== BO_Assign
|| op
== BO_PtrMemD
|| op
== BO_PtrMemI
|| op
== BO_MulAssign
328 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
329 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
330 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
) {
333 // for pointer arithmetic need to check parent
334 if (binaryOp
->getType()->isPointerType()) {
335 return checkIfCanBeConst(parent
, parmVarDecl
);
338 } else if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(parent
)) {
339 const CXXConstructorDecl
* constructorDecl
= constructExpr
->getConstructor();
340 for (unsigned i
= 0; i
< constructExpr
->getNumArgs(); ++i
) {
341 if (constructExpr
->getArg(i
) == stmt
) {
342 return isOkForParameter(constructorDecl
->getParamDecl(i
)->getType());
345 } else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
)) {
346 const CXXMethodDecl
* calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(operatorCallExpr
->getDirectCallee());
347 if (calleeMethodDecl
) {
349 if (calleeMethodDecl
->getNumParams() == 0)
350 return calleeMethodDecl
->isConst();
351 // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
353 auto Opc
= operatorCallExpr
->getOperator();
354 if (Opc
== OO_Equal
|| Opc
== OO_StarEqual
||
355 Opc
== OO_SlashEqual
|| Opc
== OO_PercentEqual
||
356 Opc
== OO_PlusEqual
|| Opc
== OO_MinusEqual
||
357 Opc
== OO_LessLessEqual
|| Opc
== OO_GreaterGreaterEqual
||
358 Opc
== OO_AmpEqual
|| Opc
== OO_CaretEqual
||
361 if (operatorCallExpr
->getArg(0) == stmt
) // assigning to the param
363 // not all operator= take a const&
364 return isOkForParameter(calleeMethodDecl
->getParamDecl(0)->getType());
366 if (operatorCallExpr
->getOperator() == OO_Subscript
&& operatorCallExpr
->getArg(1) == stmt
)
368 if (operatorCallExpr
->getOperator() == OO_EqualEqual
|| operatorCallExpr
->getOperator() == OO_ExclaimEqual
)
371 if (operatorCallExpr
->getArg(0) == stmt
)
372 return calleeMethodDecl
->isConst();
373 unsigned const n
= std::min(
374 operatorCallExpr
->getNumArgs(),
375 calleeMethodDecl
->getNumParams() + 1);
376 for (unsigned i
= 1; i
< n
; ++i
)
377 if (operatorCallExpr
->getArg(i
) == stmt
) {
378 auto qt
= calleeMethodDecl
->getParamDecl(i
- 1)->getType();
379 return isOkForParameter(qt
);
382 const Expr
* callee
= operatorCallExpr
->getCallee()->IgnoreParenImpCasts();
383 const DeclRefExpr
* dr
= dyn_cast
<DeclRefExpr
>(callee
);
384 const FunctionDecl
* calleeFunctionDecl
= nullptr;
386 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
388 if (calleeFunctionDecl
) {
389 for (unsigned i
= 0; i
< operatorCallExpr
->getNumArgs(); ++i
) {
390 if (operatorCallExpr
->getArg(i
) == stmt
) {
391 return isOkForParameter(calleeFunctionDecl
->getParamDecl(i
)->getType());
397 } else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
)) {
398 QualType functionType
= callExpr
->getCallee()->getType();
399 if (functionType
->isFunctionPointerType()) {
400 functionType
= functionType
->getPointeeType();
402 if (const FunctionProtoType
* prototype
= functionType
->getAs
<FunctionProtoType
>()) {
403 // TODO could do better
404 if (prototype
->isVariadic()) {
407 if (callExpr
->getCallee() == stmt
) {
410 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
411 if (callExpr
->getArg(i
) == stmt
) {
412 return isOkForParameter(prototype
->getParamType(i
));
416 const FunctionDecl
* calleeFunctionDecl
= callExpr
->getDirectCallee();
417 if (calleeFunctionDecl
)
419 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
)) {
420 const MemberExpr
* memberExpr
= dyn_cast
<MemberExpr
>(stmt
);
421 if (memberExpr
&& memberCallExpr
->getImplicitObjectArgument() == memberExpr
->getBase())
423 const CXXMethodDecl
* calleeMethodDecl
= dyn_cast
<CXXMethodDecl
>(calleeFunctionDecl
);
424 return calleeMethodDecl
->isConst();
427 // TODO could do better
428 if (calleeFunctionDecl
->isVariadic()) {
431 if (callExpr
->getCallee() == stmt
) {
434 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
435 if (i
>= calleeFunctionDecl
->getNumParams()) // can happen in template code
437 if (callExpr
->getArg(i
) == stmt
) {
438 return isOkForParameter(calleeFunctionDecl
->getParamDecl(i
)->getType());
443 } else if (auto callExpr
= dyn_cast
<ObjCMessageExpr
>(parent
)) {
444 if (callExpr
->getInstanceReceiver() == stmt
) {
447 if (auto const method
= callExpr
->getMethodDecl()) {
448 // TODO could do better
449 if (method
->isVariadic()) {
452 assert(method
->param_size() == callExpr
->getNumArgs());
453 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
454 if (callExpr
->getArg(i
) == stmt
) {
455 return isOkForParameter(
456 method
->param_begin()[i
]->getType());
460 } else if (isa
<CXXReinterpretCastExpr
>(parent
)) {
462 } else if (isa
<CXXConstCastExpr
>(parent
)) {
464 } else if (isa
<CastExpr
>(parent
)) { // all other cast expression subtypes
465 if (auto e
= dyn_cast
<ExplicitCastExpr
>(parent
)) {
466 if (loplugin::TypeCheck(e
->getTypeAsWritten()).Void()) {
467 if (auto const sub
= dyn_cast
<DeclRefExpr
>(
468 e
->getSubExpr()->IgnoreParenImpCasts()))
470 if (sub
->getDecl() == parmVarDecl
)
475 return checkIfCanBeConst(parent
, parmVarDecl
);
476 } else if (isa
<MemberExpr
>(parent
)) {
477 return checkIfCanBeConst(parent
, parmVarDecl
);
478 } else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
)) {
479 if (arraySubscriptExpr
->getIdx() == stmt
)
481 return checkIfCanBeConst(parent
, parmVarDecl
);
482 } else if (isa
<ParenExpr
>(parent
)) {
483 return checkIfCanBeConst(parent
, parmVarDecl
);
484 } else if (isa
<DeclStmt
>(parent
)) {
485 // TODO could do better here, but would require tracking the target(s)
487 } else if (isa
<ReturnStmt
>(parent
)) {
488 return !isPointerOrReferenceToNonConst(currentFunctionDecl
->getReturnType());
489 } else if (isa
<InitListExpr
>(parent
)) {
491 } else if (isa
<IfStmt
>(parent
)) {
493 } else if (isa
<WhileStmt
>(parent
)) {
495 } else if (isa
<ForStmt
>(parent
)) {
497 } else if (isa
<CompoundStmt
>(parent
)) {
499 } else if (isa
<SwitchStmt
>(parent
)) {
501 } else if (isa
<DoStmt
>(parent
)) {
503 } else if (isa
<CXXDeleteExpr
>(parent
)) {
505 } else if (isa
<VAArgExpr
>(parent
)) {
507 } else if (isa
<CXXDependentScopeMemberExpr
>(parent
)) {
509 } else if (isa
<MaterializeTemporaryExpr
>(parent
)) {
510 return checkIfCanBeConst(parent
, parmVarDecl
);
511 } else if (auto conditionalExpr
= dyn_cast
<ConditionalOperator
>(parent
)) {
512 if (conditionalExpr
->getCond() == stmt
)
514 return checkIfCanBeConst(parent
, parmVarDecl
);
515 } else if (isa
<UnaryExprOrTypeTraitExpr
>(parent
)) {
517 } else if (auto cxxNewExpr
= dyn_cast
<CXXNewExpr
>(parent
)) {
518 for (unsigned i
= 0; i
< cxxNewExpr
->getNumPlacementArgs(); ++i
)
519 if (cxxNewExpr
->getPlacementArg(i
) == stmt
)
522 } else if (auto lambdaExpr
= dyn_cast
<LambdaExpr
>(parent
)) {
523 for (auto it
= lambdaExpr
->capture_begin(); it
!= lambdaExpr
->capture_end(); ++it
)
525 if (it
->capturesVariable() && it
->getCapturedVar() == parmVarDecl
)
526 return it
->getCaptureKind() != LCK_ByRef
;
529 } else if (isa
<CXXTypeidExpr
>(parent
)) {
531 } else if (isa
<ParenListExpr
>(parent
)) {
532 return false; // could be improved, seen in constructors when calling base class constructor
533 } else if (isa
<CXXUnresolvedConstructExpr
>(parent
)) {
535 } else if (isa
<UnresolvedMemberExpr
>(parent
)) {
537 } else if (isa
<PackExpansionExpr
>(parent
)) {
539 } else if (isa
<ExprWithCleanups
>(parent
)) {
540 return checkIfCanBeConst(parent
, parmVarDecl
);
541 } else if (isa
<CaseStmt
>(parent
)) {
543 } else if (isa
<CXXPseudoDestructorExpr
>(parent
)) {
545 } else if (isa
<CXXDependentScopeMemberExpr
>(parent
)) {
547 } else if (isa
<ObjCIvarRefExpr
>(parent
)) {
548 return checkIfCanBeConst(parent
, parmVarDecl
);
553 DiagnosticsEngine::Warning
,
554 "oh dear, what can the matter be?",
555 compat::getBeginLoc(parent
))
556 << parent
->getSourceRange();
560 bool ConstParams::isOkForParameter(const QualType
& qt
) {
561 if (qt
->isIntegralOrEnumerationType())
563 auto const type
= loplugin::TypeCheck(qt
);
564 if (type
.Pointer()) {
565 return bool(type
.Pointer().Const());
566 } else if (type
.LvalueReference().Const().Pointer()) {
567 // If we have a method that takes (T* t) and it calls std::vector<T*>::push_back
568 // then the type of push_back is T * const &
569 // There is probably a more elegant way to check this, but it will probably require
570 // recalculating types while walking up the AST.
572 } else if (type
.LvalueReference()) {
573 return bool(type
.LvalueReference().Const());
578 bool ConstParams::isPointerOrReferenceToNonConst(const QualType
& qt
) {
579 auto const type
= loplugin::TypeCheck(qt
);
580 if (type
.Pointer()) {
581 return !bool(type
.Pointer().Const());
582 } else if (type
.LvalueReference()) {
583 return !bool(type
.LvalueReference().Const());
588 loplugin::Plugin::Registration
< ConstParams
> X("constparams", false);
592 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */