1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #include <cassert>
11 #include <string>
12 #include <set>
13 #include <iostream>
14 
15 #include "plugin.hxx"
16 
17 /**
18 Find parameters that have no name, i.e. they are unused and we're worked around the "unused parameter" warning.
19 
20 Most of these can be removed.
21 
22 TODO look for places where we are working around the warning by doing
23     (void) param1;
24  */
25 namespace {
26 
27 class CheckUnusedParams: public loplugin::FilteringPlugin<CheckUnusedParams> {
28 public:
CheckUnusedParams(loplugin::InstantiationData const & data)29     explicit CheckUnusedParams(loplugin::InstantiationData const & data):
30         FilteringPlugin(data) {}
31     void run() override;
32     bool VisitFunctionDecl(FunctionDecl const *);
33     bool VisitUnaryOperator(UnaryOperator const *);
34     bool VisitInitListExpr(InitListExpr const *);
35     bool VisitCallExpr(CallExpr const *);
36     bool VisitBinaryOperator(BinaryOperator const *);
37     bool VisitCXXConstructExpr(CXXConstructExpr const *);
38 private:
39     void checkForFunctionDecl(Expr const *, bool bCheckOnly = false);
40     std::set<FunctionDecl const *> m_addressOfSet;
41     enum class PluginPhase { FindAddressOf, Warning };
42     PluginPhase m_phase;
43 };
44 
run()45 void CheckUnusedParams::run()
46 {
47     StringRef fn(handler.getMainFileName());
48     if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sal/"))
49          return;
50     // Taking pointer to function
51     if (loplugin::isSamePathname(fn, SRCDIR "/l10ntools/source/xmlparse.cxx"))
52          return;
53     // macro magic which declares something needed by an external library
54     if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/misc/gridprinter.cxx"))
55          return;
56 
57     // valid test/qa code
58     if (loplugin::hasPathnamePrefix(fn, SRCDIR "/compilerplugins/clang/test/"))
59          return;
60     if (loplugin::isSamePathname(fn, SRCDIR "/cppu/qa/test_reference.cxx"))
61          return;
62 
63     // leave this alone for now
64     if (loplugin::hasPathnamePrefix(fn, SRCDIR "/libreofficekit/"))
65          return;
66     // this has a certain pattern to its code which appears to include lots of unused params
67     if (loplugin::hasPathnamePrefix(fn, SRCDIR "/xmloff/"))
68          return;
69     // I believe someone is busy working on this chunk of code
70     if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/docshell/dataprovider.cxx"))
71          return;
72     // I think erack is working on stuff here
73     if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/excel/xiformula.cxx"))
74          return;
75     // lots of callbacks here
76     if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/lotus/op.cxx"))
77          return;
78     // template magic
79     if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/html/htmlpars.cxx"))
80          return;
81 
82     m_phase = PluginPhase::FindAddressOf;
83     TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
84     m_phase = PluginPhase::Warning;
85     TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
86 }
87 
VisitUnaryOperator(UnaryOperator const * op)88 bool CheckUnusedParams::VisitUnaryOperator(UnaryOperator const * op) {
89     if (op->getOpcode() != UO_AddrOf) {
90         return true;
91     }
92     if (m_phase != PluginPhase::FindAddressOf)
93         return true;
94     checkForFunctionDecl(op->getSubExpr());
95     return true;
96 }
97 
VisitBinaryOperator(BinaryOperator const * binaryOperator)98 bool CheckUnusedParams::VisitBinaryOperator(BinaryOperator const * binaryOperator) {
99     if (binaryOperator->getOpcode() != BO_Assign) {
100         return true;
101     }
102     if (m_phase != PluginPhase::FindAddressOf)
103         return true;
104     checkForFunctionDecl(binaryOperator->getRHS());
105     return true;
106 }
107 
VisitCallExpr(CallExpr const * callExpr)108 bool CheckUnusedParams::VisitCallExpr(CallExpr const * callExpr) {
109     if (m_phase != PluginPhase::FindAddressOf)
110         return true;
111     for (auto arg : callExpr->arguments())
112         checkForFunctionDecl(arg);
113     return true;
114 }
115 
VisitCXXConstructExpr(CXXConstructExpr const * constructExpr)116 bool CheckUnusedParams::VisitCXXConstructExpr(CXXConstructExpr const * constructExpr) {
117     if (m_phase != PluginPhase::FindAddressOf)
118         return true;
119     for (auto arg : constructExpr->arguments())
120         checkForFunctionDecl(arg);
121     return true;
122 }
123 
VisitInitListExpr(InitListExpr const * initListExpr)124 bool CheckUnusedParams::VisitInitListExpr(InitListExpr const * initListExpr) {
125     if (m_phase != PluginPhase::FindAddressOf)
126         return true;
127     for (auto subStmt : *initListExpr)
128         checkForFunctionDecl(dyn_cast<Expr>(subStmt));
129     return true;
130 }
131 
checkForFunctionDecl(Expr const * expr,bool bCheckOnly)132 void CheckUnusedParams::checkForFunctionDecl(Expr const * expr, bool bCheckOnly) {
133     auto e1 = expr->IgnoreParenCasts();
134     auto declRef = dyn_cast<DeclRefExpr>(e1);
135     if (!declRef)
136         return;
137     auto functionDecl = dyn_cast<FunctionDecl>(declRef->getDecl());
138     if (!functionDecl)
139         return;
140     if (bCheckOnly)
141         getParentStmt(expr)->dump();
142     else
143         m_addressOfSet.insert(functionDecl->getCanonicalDecl());
144 }
145 
noFieldsInRecord(RecordType const * recordType)146 static int noFieldsInRecord(RecordType const * recordType) {
147     auto recordDecl = recordType->getDecl();
148     // if it's complicated, lets just assume it has fields
149     if (isa<ClassTemplateSpecializationDecl>(recordDecl))
150         return 1;
151     return std::distance(recordDecl->field_begin(), recordDecl->field_end());
152 }
startswith(const std::string & rStr,const char * pSubStr)153 static bool startswith(const std::string& rStr, const char* pSubStr) {
154     return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
155 }
endswith(const std::string & rStr,const char * pSubStr)156 static bool endswith(const std::string& rStr, const char* pSubStr) {
157     auto len = strlen(pSubStr);
158     if (len > rStr.size())
159         return false;
160     return rStr.compare(rStr.size() - len, rStr.size(), pSubStr) == 0;
161 }
162 
VisitFunctionDecl(FunctionDecl const * decl)163 bool CheckUnusedParams::VisitFunctionDecl(FunctionDecl const * decl) {
164     if (m_phase != PluginPhase::Warning)
165         return true;
166     if (m_addressOfSet.find(decl->getCanonicalDecl()) != m_addressOfSet.end())
167         return true;
168     if (ignoreLocation(decl))
169         return true;
170     if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(decl->getLocation())))
171         return true;
172 
173     auto cxxMethodDecl = dyn_cast<CXXMethodDecl>(decl);
174     if (cxxMethodDecl) {
175         if (cxxMethodDecl->isVirtual())
176             return true;
177         auto cxxConstructorDecl = dyn_cast<CXXConstructorDecl>(cxxMethodDecl);
178         if (cxxConstructorDecl && cxxConstructorDecl->isCopyOrMoveConstructor())
179             return true;
180     }
181     if (!decl->isThisDeclarationADefinition())
182         return true;
183     if (decl->isFunctionTemplateSpecialization())
184         return true;
185     if (decl->isDeleted())
186         return true;
187     if (decl->getTemplatedKind() != clang::FunctionDecl::TK_NonTemplate)
188         return true;
189     if (decl->isOverloadedOperator())
190         return true;
191     if (decl->isExternC())
192         return true;
193 
194     //TODO, filtering out any functions relating to class templates for now:
195     CXXRecordDecl const * r = dyn_cast<CXXRecordDecl>(decl->getDeclContext());
196     if (r != nullptr
197         && (r->getTemplateSpecializationKind() != TSK_Undeclared
198             || r->isDependentContext()))
199     {
200         return true;
201     }
202     FunctionDecl const * canon = decl->getCanonicalDecl();
203     std::string fqn = canon->getQualifiedNameAsString(); // because sometimes clang returns nonsense for the filename of canon
204     if (ignoreLocation(canon))
205         return true;
206     if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(canon->getLocation())))
207         return true;
208     StringRef fn = getFilenameOfLocation(compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(canon)));
209     // Some backwards compat magic.
210     // TODO Can probably be removed, but need to do some checking
211     if (loplugin::isSamePathname(fn, SRCDIR "/include/sax/fshelper.hxx"))
212          return true;
213     // Platform-specific code
214     if (loplugin::isSamePathname(fn, SRCDIR "/include/svl/svdde.hxx"))
215          return true;
216     if (loplugin::isSamePathname(fn, SRCDIR "/include/vcl/svmain.hxx"))
217          return true;
218     // passing pointer to function
219     if (loplugin::isSamePathname(fn, SRCDIR "/include/vcl/BitmapReadAccess.hxx"))
220          return true;
221     if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/unx/gtk/gtkobject.hxx"))
222          return true;
223     if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/unx/gtk/gtksalframe.hxx"))
224          return true;
225     if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/unx/gtk/gtkframe.hxx"))
226          return true;
227     if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/gtk/fpicker/SalGtkFilePicker.hxx"))
228          return true;
229     if (loplugin::isSamePathname(fn, SRCDIR "/extensions/source/propctrlr/propertyeditor.hxx"))
230          return true;
231     if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/solar/inc/navtoolbar.hxx"))
232          return true;
233     if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/grammar.cxx"))
234          return true;
235     if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/lexer.cxx"))
236          return true;
237     // marked with a TODO/FIXME
238     if (loplugin::isSamePathname(fn, SRCDIR "/vcl/inc/sallayout.hxx"))
239          return true;
240     if (loplugin::isSamePathname(fn, SRCDIR "/accessibility/inc/standard/vclxaccessiblelist.hxx"))
241          return true;
242     // these are "extern C" but clang doesn't seem to report that accurately
243     if (loplugin::isSamePathname(fn, SRCDIR "/sax/source/fastparser/fastparser.cxx"))
244          return true;
245     // these all follow the same pattern, seems a pity to break that
246     if (loplugin::isSamePathname(fn, SRCDIR "/include/vcl/graphicfilter.hxx"))
247          return true;
248     // looks like work in progress
249     if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/ipdf/pdfdocument.cxx"))
250          return true;
251     // macro magic
252     if (loplugin::isSamePathname(fn, SRCDIR "/basctl/source/inc/basidesh.hxx"))
253          return true;
254     // template magic
255     if (loplugin::hasPathnamePrefix(fn, SRCDIR "/canvas/"))
256          return true;
257     if (loplugin::hasPathnamePrefix(fn, SRCDIR "/include/canvas/"))
258          return true;
259     if (loplugin::isSamePathname(fn, SRCDIR "/include/comphelper/unwrapargs.hxx"))
260          return true;
261     // this looks like vaguely useful code (ParseError) that I'm loathe to remove
262     if (loplugin::isSamePathname(fn, SRCDIR "/connectivity/source/inc/RowFunctionParser.hxx"))
263          return true;
264     if (loplugin::isSamePathname(fn, SRCDIR "/include/svx/EnhancedCustomShapeFunctionParser.hxx"))
265          return true;
266     // TODO marker parameter in constructor, should probably be using an enum
267     if (loplugin::isSamePathname(fn, SRCDIR "/framework/inc/uielement/uicommanddescription.hxx"))
268          return true;
269     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/SlideTransitionPane.hxx"))
270          return true;
271     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/animations/CustomAnimationPane.hxx"))
272          return true;
273     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/table/TableDesignPane.hxx"))
274          return true;
275     // debug stuff
276     if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/core/data/column2.cxx"))
277          return true;
278     // weird stuff
279     if (loplugin::isSamePathname(fn, SRCDIR "/scaddins/source/analysis/analysishelper.hxx"))
280          return true;
281     // SFX_DECL_CHILDWINDOWCONTEXT macro stuff
282     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/NavigatorChildWindow.hxx"))
283          return true;
284     // TODO, need to remove this from the .sdi file too
285     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/SlideSorterViewShell.hxx"))
286          return true;
287     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/OutlineViewShell.hxx"))
288          return true;
289     // SFX_DECL_INTERFACE macro stuff
290     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/inc/ViewShellBase.hxx"))
291          return true;
292     // debug stuff
293     if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/filter/ppt/pptinanimations.hxx"))
294          return true;
295     // takes pointer to fn
296     if (loplugin::isSamePathname(fn, SRCDIR "/include/sfx2/shell.hxx"))
297          return true;
298     // TODO, need to remove this from the .sdi file too
299     if (fqn == "SfxObjectShell::StateView_Impl")
300          return true;
301     // SFX_DECL_CHILDWINDOW_WITHID macro
302     if (loplugin::isSamePathname(fn, SRCDIR "/include/sfx2/infobar.hxx"))
303          return true;
304     // this looks like vaguely useful code (ParseError) that I'm loathe to remove
305     if (loplugin::isSamePathname(fn, SRCDIR "/slideshow/source/inc/slideshowexceptions.hxx"))
306          return true;
307     // SFX_DECL_VIEWFACTORY macro
308     if (loplugin::isSamePathname(fn, SRCDIR "/starmath/inc/view.hxx"))
309          return true;
310     // debugging
311     if (fqn == "BrowseBox::DoShowCursor" || fqn == "BrowseBox::DoHideCursor")
312          return true;
313     // if I change this one, it then overrides a superclass virtual method
314     if (fqn == "GalleryBrowser2::KeyInput")
315          return true;
316     // takes pointer to function
317     if (fqn == "cmis::AuthProvider::onedriveAuthCodeFallback" || fqn == "cmis::AuthProvider::gdriveAuthCodeFallback")
318          return true;
319     if (fqn == "ooo_mount_operation_ask_password")
320          return true;
321     // TODO tricky to remove because of default params
322     if (fqn == "xmloff::OAttribute2Property::addBooleanProperty")
323          return true;
324     // taking pointer to function
325     if (fqn == "sw::DocumentContentOperationsManager::DeleteAndJoinWithRedlineImpl"
326         || fqn == "sw::DocumentContentOperationsManager::DeleteRangeImpl"
327         || fqn == "SwTableFormula::GetFormulaBoxes"
328         || fqn == "SwFEShell::Drag"
329         || fqn == "GetASCWriter" || fqn == "GetHTMLWriter" || fqn == "GetXMLWriter"
330         || fqn == "SwWrtShell::UpdateLayoutFrame" || fqn == "SwWrtShell::DefaultDrag"
331         || fqn == "SwWrtShell::DefaultEndDrag"
332         || startswith(fqn, "SwWW8ImplReader::Read_"))
333          return true;
334     // WIN32 only
335     if (fqn == "SwFntObj::GuessLeading")
336          return true;
337     // SFX_DECL_CHILDWINDOW_WITHID macro
338     if (fqn == "SwSpellDialogChildWindow::SwSpellDialogChildWindow"
339         || fqn == "SwFieldDlgWrapper::SwFieldDlgWrapper"
340         || fqn == "SwInputChild::SwInputChild")
341          return true;
342     // SFX_DECL_VIEWFACTORY macro
343     if (fqn == "SwSrcView::SwSrcView")
344          return true;
345     // Serves to disambiguate two very similar methods
346     if (fqn == "MSWordStyles::BuildGetSlot")
347          return true;
348     // TODO there are just too many default params to make this worth fixing right now
349     if (fqn == "ScDocument::CopyMultiRangeFromClip")
350         return true;
351     // TODO looks like this needs fixing?
352     if (fqn == "ScTable::ExtendPrintArea")
353         return true;
354     // there is a FIXME in the code
355     if (fqn == "ScRangeUtil::IsAbsTabArea")
356         return true;
357     // SFX_DECL_CHILDWINDOW_WITHID
358     if (fqn == "ScInputWindowWrapper::ScInputWindowWrapper"
359         || fqn == "sc::SearchResultsDlgWrapper::SearchResultsDlgWrapper")
360         return true;
361     // ExecMethod in .sdi file
362     if (fqn == "ScChartShell::ExecuteExportAsGraphic")
363         return true;
364     // bool marker parameter
365     if (fqn == "SvxIconReplacementDialog::SvxIconReplacementDialog")
366         return true;
367     // used as pointer to fn
368     if (endswith(fqn, "_createInstance"))
369         return true;
370     // callback
371     if (startswith(fqn, "SbRtl_"))
372         return true;
373     // takes pointer to fn
374     if (fqn == "migration::BasicMigration_create" || fqn == "migration::WordbookMigration_create"
375         || fqn == "comp_CBlankNode::_create" || fqn == "comp_CURI::_create"
376         || fqn == "comp_CLiteral::_create" || fqn == "CDocumentBuilder::_getInstance"
377         || fqn == "DOM::CDocumentBuilder::_getInstance"
378         || fqn == "xml_security::serial_number_adapter::create"
379         || fqn == "desktop::splash::create" || fqn == "ScannerManager_CreateInstance"
380         || fqn == "formula::FormulaOpCodeMapperObj::create"
381         || fqn == "(anonymous namespace)::createInstance"
382         || fqn == "x_error_handler"
383         || fqn == "warning_func"
384         || fqn == "error_func"
385         || fqn == "ScaDateAddIn_CreateInstance"
386         || fqn == "ScaPricingAddIn_CreateInstance"
387         || fqn == "(anonymous namespace)::PDFSigningPKCS7PasswordCallback"
388         || fqn == "ContextMenuEventLink"
389         || fqn == "DelayedCloseEventLink"
390         || fqn == "GDIMetaFile::ImplColMonoFnc"
391         || fqn == "vcl::getGlyph0"
392         || fqn == "vcl::getGlyph6"
393         || fqn == "vcl::getGlyph12"
394         || fqn == "setPasswordCallback"
395         || fqn == "VCLExceptionSignal_impl"
396         || fqn == "getFontTable"
397         || fqn == "textconversiondlgs::ChineseTranslation_UnoDialog::create"
398         || fqn == "pcr::DefaultHelpProvider::Create"
399         || fqn == "pcr::DefaultFormComponentInspectorModel::Create"
400         || fqn == "pcr::ObjectInspectorModel::Create"
401         || fqn == "GraphicExportFilter::GraphicExportFilter"
402         || fqn == "CertificateContainer::CertificateContainer"
403         || startswith(fqn, "ParseCSS1_")
404         )
405          return true;
406     // TODO
407     if (fqn == "FontSubsetInfo::CreateFontSubsetFromType1")
408          return true;
409     // used in template magic
410     if (fqn == "MtfRenderer::MtfRenderer" || fqn == "shell::sessioninstall::SyncDbusSessionHelper::SyncDbusSessionHelper"
411         || fqn == "dp_gui::LicenseDialog::LicenseDialog"
412         || fqn == "(anonymous namespace)::OGLTransitionFactoryImpl::OGLTransitionFactoryImpl")
413          return true;
414     // FIXME
415     if (fqn == "GtkSalDisplay::filterGdkEvent" || fqn == "SvXMLEmbeddedObjectHelper::ImplReadObject"
416         || fqn == "chart::CachedDataSequence::CachedDataSequence")
417          return true;
418     // used via macro
419     if (fqn == "framework::MediaTypeDetectionHelper::MediaTypeDetectionHelper"
420         || fqn == "framework::UriAbbreviation::UriAbbreviation"
421         || fqn == "framework::DispatchDisabler::DispatchDisabler"
422         || fqn == "framework::DispatchRecorderSupplier::DispatchRecorderSupplier")
423          return true;
424     // TODO Armin Le Grand is still working on this
425     if (fqn == "svx::frame::CreateDiagFrameBorderPrimitives"
426         || fqn == "svx::frame::CreateBorderPrimitives")
427          return true;
428     // marked with a TODO
429     if (fqn == "pcr::FormLinkDialog::getExistingRelation"
430         || fqn == "ooo::vba::DebugHelper::basicexception"
431         || fqn == "ScPrintFunc::DrawToDev")
432          return true;
433     // macros at work
434     if (fqn == "msfilter::lcl_PrintDigest")
435          return true;
436     // TODO something wrong here, the method that calls this (Normal::GenSlidingWindowFunction) cannot be correct
437     if (fqn == "sc::opencl::OpBase::Gen")
438          return true;
439     // Can't change this without conflicting with another constructor with the same signature
440     if (fqn == "XclExpSupbook::XclExpSupbook")
441          return true;
442     // ignore the LINK macros from include/tools/link.hxx
443     if (decl->getLocation().isMacroID())
444         return true;
445     // debug code in sw/
446     if (fqn == "lcl_dbg_out")
447          return true;
448 
449     for( auto it = decl->param_begin(); it != decl->param_end(); ++it) {
450         auto param = *it;
451         if (param->hasAttr<UnusedAttr>())
452             continue;
453         if (!param->getName().empty())
454             continue;
455         // ignore params which are enum types with only a single enumerator, these are marker/tag types
456         auto paramType = param->getType();
457         if (paramType->isEnumeralType()) {
458             auto enumType = paramType->getAs<EnumType>();
459             int cnt = std::distance(enumType->getDecl()->enumerator_begin(), enumType->getDecl()->enumerator_end());
460             if (cnt == 1)
461                 continue;
462         }
463         // ignore params which are a reference to a struct which has no fields.
464         // These are either
465         //  (a) marker/tag types
466         //  (b) selective "friend" access
467         if (paramType->isReferenceType()) {
468             auto referenceType = paramType->getAs<ReferenceType>();
469             if (referenceType->getPointeeType()->isRecordType()) {
470                 auto recordType = referenceType->getPointeeType()->getAs<RecordType>();
471                 if (noFieldsInRecord(recordType) == 0)
472                     continue;
473             }
474         }
475         else if (paramType->isRecordType()) {
476             if (noFieldsInRecord(paramType->getAs<RecordType>()) == 0)
477                 continue;
478         }
479         report( DiagnosticsEngine::Warning,
480                 "unused param %0 in %1", compat::getBeginLoc(param))
481                 << param->getSourceRange()
482                 << param->getName()
483                 << fqn;
484         if (canon != decl)
485         {
486             unsigned idx = param->getFunctionScopeIndex();
487             const ParmVarDecl* pOther = canon->getParamDecl(idx);
488             report( DiagnosticsEngine::Note, "declaration is here",
489                     compat::getBeginLoc(pOther))
490                     << pOther->getSourceRange();
491         }
492     }
493     return true;
494 }
495 
496 loplugin::Plugin::Registration<CheckUnusedParams> X("checkunusedparams", false);
497 
498 }
499 
500 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
501