1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
18 #include <clang/Basic/FileManager.h>
19 #include <clang/Lex/Lexer.h>
21 #include "pluginhandler.hxx"
24 Base classes for plugin actions.
31 Expr
const * skipImplicit(Expr
const * expr
) {
32 if (auto const e
= dyn_cast
<MaterializeTemporaryExpr
>(expr
)) {
33 expr
= e
->GetTemporaryExpr()->IgnoreImpCasts();
35 if (auto const e
= dyn_cast
<CXXBindTemporaryExpr
>(expr
)) {
36 expr
= e
->getSubExpr();
41 bool structurallyIdentical(Stmt
const * stmt1
, Stmt
const * stmt2
) {
42 if (stmt1
->getStmtClass() != stmt2
->getStmtClass()) {
45 switch (stmt1
->getStmtClass()) {
46 case Stmt::CXXConstructExprClass
:
47 if (cast
<CXXConstructExpr
>(stmt1
)->getConstructor()->getCanonicalDecl()
48 != cast
<CXXConstructExpr
>(stmt2
)->getConstructor()->getCanonicalDecl())
53 case Stmt::DeclRefExprClass
:
54 if (cast
<DeclRefExpr
>(stmt1
)->getDecl()->getCanonicalDecl()
55 != cast
<DeclRefExpr
>(stmt2
)->getDecl()->getCanonicalDecl())
60 case Stmt::ImplicitCastExprClass
:
62 auto const e1
= cast
<ImplicitCastExpr
>(stmt1
);
63 auto const e2
= cast
<ImplicitCastExpr
>(stmt2
);
64 if (e1
->getCastKind() != e2
->getCastKind()
65 || e1
->getType().getCanonicalType() != e2
->getType().getCanonicalType())
71 case Stmt::MemberExprClass
:
73 auto const e1
= cast
<MemberExpr
>(stmt1
);
74 auto const e2
= cast
<MemberExpr
>(stmt2
);
75 if (e1
->isArrow() != e2
->isArrow()
76 || e1
->getType().getCanonicalType() != e2
->getType().getCanonicalType())
82 case Stmt::CXXMemberCallExprClass
:
83 case Stmt::CXXOperatorCallExprClass
:
84 if (cast
<Expr
>(stmt1
)->getType().getCanonicalType()
85 != cast
<Expr
>(stmt2
)->getType().getCanonicalType())
90 case Stmt::MaterializeTemporaryExprClass
:
91 case Stmt::ParenExprClass
:
94 // Conservatively assume non-identical for expressions that don't happen for us in practice
95 // when compiling the LO code base (and for which the above set of supported classes would
96 // need to be extended):
99 auto i1
= stmt1
->child_begin();
100 auto e1
= stmt1
->child_end();
101 auto i2
= stmt2
->child_begin();
102 auto e2
= stmt2
->child_end();
103 for (; i1
!= e1
; ++i1
, ++i2
) {
104 if (i2
== e2
|| !structurallyIdentical(*i1
, *i2
)) {
113 Plugin::Plugin( const InstantiationData
& data
)
114 : compiler( data
.compiler
), handler( data
.handler
), name( data
.name
)
118 DiagnosticBuilder
Plugin::report( DiagnosticsEngine::Level level
, StringRef message
, SourceLocation loc
) const
120 return handler
.report( level
, name
, message
, compiler
, loc
);
123 void normalizeDotDotInFilePath( std::string
& s
)
125 for (std::string::size_type i
= 0;;)
128 if (i
== std::string::npos
) {
131 if (i
+ 2 == s
.length() || s
[i
+ 2] == '/') {
132 s
.erase(i
, 2); // [AAA]/.[/CCC] -> [AAA][/CCC]
133 } else if (s
[i
+ 2] == '.'
134 && (i
+ 3 == s
.length() || s
[i
+ 3] == '/'))
136 if (i
== 0) { // /..[/CCC] -> /..[/CCC]
139 auto j
= s
.rfind('/', i
- 1);
140 if (j
== std::string::npos
)
142 // BBB/..[/CCC] -> BBB/..[/CCC] (instead of BBB/../CCC ->
143 // CCC, to avoid wrong ../../CCC -> CCC; relative paths
144 // shouldn't happen anyway, and even if they did, wouldn't
145 // match against WORKDIR anyway, as WORKDIR should be
149 s
.erase(j
, i
+ 3 - j
); // AAA/BBB/..[/CCC] -> AAA[/CCC]
157 void Plugin::registerPlugin( Plugin
* (*create
)( const InstantiationData
& ), const char* optionName
,
158 bool isPPCallback
, bool isSharedPlugin
, bool byDefault
)
160 PluginHandler::registerPlugin( create
, optionName
, isPPCallback
, isSharedPlugin
, byDefault
);
163 bool Plugin::evaluate(const Expr
* expr
, APSInt
& x
)
165 if (compat::EvaluateAsInt(expr
, x
, compiler
.getASTContext()))
169 if (isa
<CXXNullPtrLiteralExpr
>(expr
)) {
176 const Stmt
* Plugin::getParentStmt( const Stmt
* stmt
)
178 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
179 if ( parentsRange
.begin() == parentsRange
.end())
181 return parentsRange
.begin()->get
<Stmt
>();
184 Stmt
* Plugin::getParentStmt( Stmt
* stmt
)
186 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
187 if ( parentsRange
.begin() == parentsRange
.end())
189 return const_cast<Stmt
*>(parentsRange
.begin()->get
<Stmt
>());
192 static const Decl
* getDeclContext(ASTContext
& context
, const Stmt
* stmt
)
194 auto it
= context
.getParents(*stmt
).begin();
196 if (it
== context
.getParents(*stmt
).end())
199 const Decl
*aDecl
= it
->get
<Decl
>();
203 const Stmt
*aStmt
= it
->get
<Stmt
>();
205 return getDeclContext(context
, aStmt
);
210 const FunctionDecl
* Plugin::getParentFunctionDecl( const Stmt
* stmt
)
212 const Decl
*decl
= getDeclContext(compiler
.getASTContext(), stmt
);
214 return static_cast<const FunctionDecl
*>(decl
->getNonClosureContext());
219 StringRef
Plugin::getFilenameOfLocation(SourceLocation spellingLocation
) const
221 // prevent crashes when running the global-analysis plugins
222 if (!spellingLocation
.isValid())
225 static enum { NOINIT
, STDIN
, GOOD
} s_Mode(NOINIT
);
228 return compiler
.getSourceManager().getFilename(spellingLocation
);
230 else if (s_Mode
== STDIN
231 || !compiler
.getSourceManager().isInMainFile(spellingLocation
))
233 const char* bufferName
= compiler
.getSourceManager().getPresumedLoc(spellingLocation
).getFilename();
238 auto const fn(compiler
.getSourceManager().getFilename(spellingLocation
));
239 if (!fn
.data()) // wtf? happens in sot/source/sdstor/stg.cxx
244 assert(fn
.startswith("/") || fn
== "<stdin>");
246 s_Mode
= fn
== "<stdin>" ? STDIN
: GOOD
;
247 return getFilenameOfLocation(spellingLocation
);
251 bool Plugin::isInUnoIncludeFile(SourceLocation spellingLocation
) const
253 StringRef name
{ getFilenameOfLocation(spellingLocation
) };
254 return compiler
.getSourceManager().isInMainFile(spellingLocation
)
255 ? (isSamePathname(name
, SRCDIR
"/cppu/source/cppu/compat.cxx")
256 || isSamePathname(name
, SRCDIR
"/cppuhelper/source/compat.cxx")
257 || isSamePathname(name
, SRCDIR
"/sal/osl/all/compat.cxx"))
258 : (hasPathnamePrefix(name
, SRCDIR
"/include/com/")
259 || hasPathnamePrefix(name
, SRCDIR
"/include/cppu/")
260 || hasPathnamePrefix(name
, SRCDIR
"/include/cppuhelper/")
261 || hasPathnamePrefix(name
, SRCDIR
"/include/osl/")
262 || hasPathnamePrefix(name
, SRCDIR
"/include/rtl/")
263 || hasPathnamePrefix(name
, SRCDIR
"/include/sal/")
264 || hasPathnamePrefix(name
, SRCDIR
"/include/salhelper/")
265 || hasPathnamePrefix(name
, SRCDIR
"/include/systools/")
266 || hasPathnamePrefix(name
, SRCDIR
"/include/typelib/")
267 || hasPathnamePrefix(name
, SRCDIR
"/include/uno/"));
270 bool Plugin::isInUnoIncludeFile(const FunctionDecl
* functionDecl
) const
272 return isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(
273 functionDecl
->getCanonicalDecl()->getNameInfo().getLoc()));
276 SourceLocation
Plugin::locationAfterToken( SourceLocation location
)
278 return Lexer::getLocForEndOfToken( location
, 0, compiler
.getSourceManager(), compiler
.getLangOpts());
281 bool Plugin::isUnitTestMode()
283 return PluginHandler::isUnitTestMode();
286 bool Plugin::containsPreprocessingConditionalInclusion(SourceRange range
)
288 // Preprocessing directives (other than _Pragma, which is not relevant here) cannot appear in
289 // macro expansions, so it is safe to just consider the range of expansion locations:
290 auto const begin
= compiler
.getSourceManager().getExpansionLoc(
292 auto const end
= compiler
.getSourceManager().getExpansionLoc(
294 assert(begin
.isFileID() && end
.isFileID());
296 || compiler
.getSourceManager().isBeforeInTranslationUnit(
301 DiagnosticsEngine::Fatal
,
302 ("unexpected broken range for Plugin::containsPreprocessingConditionalInclusion,"
307 // Conservatively assume "yes" if lexing fails:
311 for (auto loc
= begin
;;) {
313 if (Lexer::getRawToken(
314 loc
, tok
, compiler
.getSourceManager(),
315 compiler
.getLangOpts(), true))
319 DiagnosticsEngine::Fatal
,
320 ("unexpected broken range for"
321 " Plugin::containsPreprocessingConditionalInclusion, case 2"),
325 // Conservatively assume "yes" if lexing fails:
328 if (hash
&& tok
.is(tok::raw_identifier
)) {
329 auto const id
= tok
.getRawIdentifier();
330 if (id
== "if" || id
== "ifdef" || id
== "ifndef"
331 || id
== "elif" || id
== "else" || id
== "endif")
339 hash
= tok
.is(tok::hash
) && tok
.isAtStartOfLine();
340 loc
= loc
.getLocWithOffset(
342 Lexer::MeasureTokenLength(
343 loc
, compiler
.getSourceManager(),
344 compiler
.getLangOpts()),
350 Plugin::IdenticalDefaultArgumentsResult
Plugin::checkIdenticalDefaultArguments(
351 Expr
const * argument1
, Expr
const * argument2
)
353 if ((argument1
== nullptr) != (argument2
== nullptr)) {
354 return IdenticalDefaultArgumentsResult::No
;
356 if (argument1
== nullptr) {
357 return IdenticalDefaultArgumentsResult::Yes
;
359 if (argument1
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
)
360 && argument2
->isNullPointerConstant(compiler
.getASTContext(), Expr::NPC_NeverValueDependent
))
362 return IdenticalDefaultArgumentsResult::Yes
;
365 if (evaluate(argument1
, x1
) && evaluate(argument2
, x2
))
368 ? IdenticalDefaultArgumentsResult::Yes
369 : IdenticalDefaultArgumentsResult::No
;
371 APFloat
f1(0.0f
), f2(0.0f
);
372 if (argument1
->EvaluateAsFloat(f1
, compiler
.getASTContext())
373 && argument2
->EvaluateAsFloat(f2
, compiler
.getASTContext()))
375 return f1
.bitwiseIsEqual(f2
)
376 ? IdenticalDefaultArgumentsResult::Yes
377 : IdenticalDefaultArgumentsResult::No
;
379 auto const desugared1
= argument1
->IgnoreParenImpCasts();
380 auto const desugared2
= argument2
->IgnoreParenImpCasts();
381 if (auto const lit1
= dyn_cast
<clang::StringLiteral
>(desugared1
)) {
382 if (auto const lit2
= dyn_cast
<clang::StringLiteral
>(desugared2
)) {
383 return lit1
->getBytes() == lit2
->getBytes()
384 ? IdenticalDefaultArgumentsResult::Yes
385 : IdenticalDefaultArgumentsResult::No
;
388 // catch params with defaults like "= OUString()"
389 for (Expr
const * e1
= desugared1
, * e2
= desugared2
;;) {
390 auto const ce1
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e1
));
391 auto const ce2
= dyn_cast
<CXXConstructExpr
>(skipImplicit(e2
));
392 if (ce1
== nullptr || ce2
== nullptr) {
395 if (ce1
->getConstructor()->getCanonicalDecl() != ce2
->getConstructor()->getCanonicalDecl())
397 return IdenticalDefaultArgumentsResult::No
;
399 if (ce1
->isElidable() && ce2
->isElidable() && ce1
->getNumArgs() == 1
400 && ce2
->getNumArgs() == 1)
402 assert(ce1
->getConstructor()->isCopyOrMoveConstructor());
403 e1
= ce1
->getArg(0)->IgnoreImpCasts();
404 e2
= ce2
->getArg(0)->IgnoreImpCasts();
407 if (ce1
->getNumArgs() == 0 && ce2
->getNumArgs() == 0) {
408 return IdenticalDefaultArgumentsResult::Yes
;
412 // If the EvaluateAsRValue derivatives above failed because the arguments use e.g. (constexpr)
413 // function template specializations that happen to not have been instantiated in this TU, try a
414 // structural comparison of the arguments:
415 if (structurallyIdentical(argument1
, argument2
)) {
416 return IdenticalDefaultArgumentsResult::Yes
;
420 DiagnosticsEngine::Fatal
, "TODO: Unexpected 'IdenticalDefaultArgumentsResult::Maybe'",
421 argument1
->getExprLoc())
422 << argument1
->getSourceRange();
424 DiagnosticsEngine::Note
, "TODO: second argument is here", argument2
->getExprLoc())
425 << argument2
->getSourceRange();
429 return IdenticalDefaultArgumentsResult::Maybe
;
432 RewritePlugin::RewritePlugin( const InstantiationData
& data
)
434 , rewriter( data
.rewriter
)
438 bool RewritePlugin::insertText( SourceLocation Loc
, StringRef Str
, bool InsertAfter
, bool indentNewLines
)
441 if (wouldRewriteWorkdir(Loc
))
443 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
444 if( !handler
.checkOverlap( Range
) )
446 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
449 if( rewriter
->InsertText( Loc
, Str
, InsertAfter
, indentNewLines
))
450 return reportEditFailure( Loc
);
451 handler
.addSourceModification(Range
);
455 bool RewritePlugin::insertTextAfter( SourceLocation Loc
, StringRef Str
)
458 if (wouldRewriteWorkdir(Loc
))
460 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
461 if( !handler
.checkOverlap( Range
) )
463 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
466 if( rewriter
->InsertTextAfter( Loc
, Str
))
467 return reportEditFailure( Loc
);
468 handler
.addSourceModification(Range
);
472 bool RewritePlugin::insertTextAfterToken( SourceLocation Loc
, StringRef Str
)
475 if (wouldRewriteWorkdir(Loc
))
477 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
478 if( !handler
.checkOverlap( Range
) )
480 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
483 if( rewriter
->InsertTextAfterToken( Loc
, Str
))
484 return reportEditFailure( Loc
);
485 handler
.addSourceModification(Range
);
489 bool RewritePlugin::insertTextBefore( SourceLocation Loc
, StringRef Str
)
492 if (wouldRewriteWorkdir(Loc
))
494 SourceRange
Range(SourceRange(Loc
, Loc
.getLocWithOffset(Str
.size())));
495 if( !handler
.checkOverlap( Range
) )
497 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", Range
.getBegin());
500 if( rewriter
->InsertTextBefore( Loc
, Str
))
501 return reportEditFailure( Loc
);
502 handler
.addSourceModification(Range
);
506 bool RewritePlugin::removeText( SourceLocation Start
, unsigned Length
, RewriteOptions opts
)
508 CharSourceRange
range( SourceRange( Start
, Start
.getLocWithOffset( Length
)), false );
509 return removeText( range
, opts
);
512 bool RewritePlugin::removeText( SourceRange range
, RewriteOptions opts
)
514 return removeText( CharSourceRange( range
, true ), opts
);
517 bool RewritePlugin::removeText( CharSourceRange range
, RewriteOptions opts
)
520 if (wouldRewriteWorkdir(range
.getBegin()))
522 if( rewriter
->getRangeSize( range
, opts
) == -1 )
523 return reportEditFailure( range
.getBegin());
524 if( !handler
.checkOverlap( range
.getAsRange() ) )
526 report( DiagnosticsEngine::Warning
, "double code removal, possible plugin error", range
.getBegin());
529 if( opts
.flags
& RemoveWholeStatement
|| opts
.flags
& RemoveAllWhitespace
)
531 if( !adjustRangeForOptions( &range
, opts
))
532 return reportEditFailure( range
.getBegin());
534 if( rewriter
->RemoveText( range
, opts
))
535 return reportEditFailure( range
.getBegin());
536 handler
.addSourceModification(range
.getAsRange());
540 bool RewritePlugin::adjustRangeForOptions( CharSourceRange
* range
, RewriteOptions opts
)
543 SourceManager
& SM
= rewriter
->getSourceMgr();
544 SourceLocation fileStartLoc
= SM
.getLocForStartOfFile( SM
.getFileID( range
->getBegin()));
545 if( fileStartLoc
.isInvalid())
547 bool isInvalid
= false;
548 const char* fileBuf
= SM
.getCharacterData( fileStartLoc
, &isInvalid
);
551 const char* startBuf
= SM
.getCharacterData( range
->getBegin(), &isInvalid
);
554 SourceLocation locationEnd
= range
->getEnd();
555 if( range
->isTokenRange())
556 locationEnd
= locationAfterToken( locationEnd
);
557 const char* endBuf
= SM
.getCharacterData( locationEnd
, &isInvalid
);
560 const char* startPos
= startBuf
;
562 while( startPos
>= fileBuf
&& ( *startPos
== ' ' || *startPos
== '\t' ))
564 if( startPos
>= fileBuf
&& *startPos
== '\n' )
565 startPos
= startBuf
- 1; // do not remove indentation whitespace (RemoveLineIfEmpty can do that)
566 const char* endPos
= endBuf
;
567 while( *endPos
== ' ' || *endPos
== '\t' )
569 if( opts
.flags
& RemoveWholeStatement
)
576 *range
= CharSourceRange( SourceRange( range
->getBegin().getLocWithOffset( startPos
- startBuf
+ 1 ),
577 locationEnd
.getLocWithOffset( endPos
- endBuf
)), false );
581 bool RewritePlugin::replaceText( SourceLocation Start
, unsigned OrigLength
, StringRef NewStr
)
584 if (wouldRewriteWorkdir(Start
))
586 SourceRange
Range(Start
, Start
.getLocWithOffset(std::max
<size_t>(OrigLength
, NewStr
.size())));
587 if( OrigLength
!= 0 && !handler
.checkOverlap( Range
) )
589 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", Start
);
592 if( rewriter
->ReplaceText( Start
, OrigLength
, NewStr
))
593 return reportEditFailure( Start
);
594 handler
.addSourceModification(Range
);
598 bool RewritePlugin::replaceText( SourceRange range
, StringRef NewStr
)
601 if (wouldRewriteWorkdir(range
.getBegin()))
603 if( rewriter
->getRangeSize( range
) == -1 )
604 return reportEditFailure( range
.getBegin());
605 if( !handler
.checkOverlap( range
) )
607 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
610 if( rewriter
->ReplaceText( range
, NewStr
))
611 return reportEditFailure( range
.getBegin());
612 handler
.addSourceModification(range
);
616 bool RewritePlugin::replaceText( SourceRange range
, SourceRange replacementRange
)
619 if (wouldRewriteWorkdir(range
.getBegin()))
621 if( rewriter
->getRangeSize( range
) == -1 )
622 return reportEditFailure( range
.getBegin());
623 if( !handler
.checkOverlap( range
) )
625 report( DiagnosticsEngine::Warning
, "overlapping code replacement, possible plugin error", range
.getBegin());
628 if( rewriter
->ReplaceText( range
, replacementRange
))
629 return reportEditFailure( range
.getBegin());
630 handler
.addSourceModification(range
);
634 bool RewritePlugin::wouldRewriteWorkdir(SourceLocation loc
)
636 if (loc
.isInvalid() || loc
.isMacroID()) {
640 getFilenameOfLocation(compiler
.getSourceManager().getSpellingLoc(loc
))
641 .startswith(WORKDIR
"/");
644 bool RewritePlugin::reportEditFailure( SourceLocation loc
)
646 report( DiagnosticsEngine::Warning
, "cannot perform source modification (macro expansion involved?)", loc
);
652 template<typename Fn
> bool checkPathname(
653 StringRef pathname
, StringRef against
, Fn check
)
655 if (check(pathname
, against
)) {
659 for (std::size_t n
= 0;;)
661 std::size_t n1
= pathname
.find('\\', n
);
662 std::size_t n2
= against
.find('\\', n
);
664 if (n1
>= against
.size()) {
665 return check(pathname
.substr(n
), against
.substr(n
));
667 if ((against
[n1
] != '/' && against
[n1
] != '\\')
668 || pathname
.substr(n
, n1
- n
) != against
.substr(n
, n1
- n
))
674 if (n2
>= pathname
.size()) {
675 return check(pathname
.substr(n
), against
.substr(n
));
677 if (pathname
[n2
] != '/'
678 || pathname
.substr(n
, n2
- n
) != against
.substr(n
, n2
- n
))
691 bool hasPathnamePrefix(StringRef pathname
, StringRef prefix
)
693 return checkPathname(
695 [](StringRef p
, StringRef a
) { return p
.startswith(a
); });
698 bool isSamePathname(StringRef pathname
, StringRef other
)
700 return checkPathname(
701 pathname
, other
, [](StringRef p
, StringRef a
) { return p
== a
; });
704 bool hasCLanguageLinkageType(FunctionDecl
const * decl
) {
705 assert(decl
!= nullptr);
706 if (decl
->isExternC()) {
709 if (decl
->isInExternCContext()) {
715 static const CXXRecordDecl
* stripTypeSugar(QualType qt
)
717 const clang::Type
* t
= qt
.getTypePtr();
718 while (auto elaboratedType
= dyn_cast
<ElaboratedType
>(t
))
719 t
= elaboratedType
->desugar().getTypePtr();
720 auto recordType
= dyn_cast
<RecordType
>(t
);
723 return dyn_cast_or_null
<CXXRecordDecl
>(recordType
->getDecl());
726 int derivedFromCount(const CXXRecordDecl
* subclassRecordDecl
, const CXXRecordDecl
* baseclassRecordDecl
)
728 if (!subclassRecordDecl
|| !baseclassRecordDecl
)
730 int derivedCount
= 0;
731 if (subclassRecordDecl
== baseclassRecordDecl
)
733 if (!subclassRecordDecl
->hasDefinition())
735 for (auto it
= subclassRecordDecl
->bases_begin(); it
!= subclassRecordDecl
->bases_end(); ++it
)
737 derivedCount
+= derivedFromCount(stripTypeSugar(it
->getType()), baseclassRecordDecl
);
738 // short-circuit, we only care about 0,1,2
739 if (derivedCount
> 1)
742 for (auto it
= subclassRecordDecl
->vbases_begin(); it
!= subclassRecordDecl
->vbases_end(); ++it
)
744 derivedCount
+= derivedFromCount(stripTypeSugar(it
->getType()), baseclassRecordDecl
);
745 // short-circuit, we only care about 0,1,2
746 if (derivedCount
> 1)
752 int derivedFromCount(QualType subclassQt
, QualType baseclassQt
)
754 auto baseclassRecordDecl
= stripTypeSugar(baseclassQt
);
755 if (!baseclassRecordDecl
)
757 auto subclassRecordDecl
= stripTypeSugar(subclassQt
);
758 if (!subclassRecordDecl
)
761 return derivedFromCount(subclassRecordDecl
, baseclassRecordDecl
);
767 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */