[PATCH] Sema: Fix some undefined behaviour when acting on redeclarations

Justin Bogner mail at justinbogner.com
Fri Jul 10 16:06:27 PDT 2015


Richard Smith <richard at metafoo.co.uk> writes:
> On Fri, Jul 10, 2015 at 3:15 PM, Justin Bogner <mail at justinbogner.com> wrote:
>
>     Justin Bogner <mail at justinbogner.com> writes:
>     > Justin Bogner <mail at justinbogner.com> writes:
>     >> Richard Smith <richard at metafoo.co.uk> writes:
>     >>> On Tue, Jun 30, 2015 at 5:32 PM, Justin Bogner <mail at justinbogner.com>
>     wrote:
>     >>>> Richard Smith <richard at metafoo.co.uk> writes:
>     >>>>> On Tue, Jun 30, 2015 at 4:57 PM, Justin Bogner
>     >>>>> <mail at justinbogner.com> wrote:
>     >>>>>> +        if (Name && !isAcceptableTagRedeclaration(PrevTagDecl,
>     Kind,
>     >>>>>> + TUK == TUK_Definition, KWLoc,
>     >>>>>> +                                                  *Name)) {
>     >>>>>
>     >>>>> So, this is tricky: if SafeToContinue is false, we don't want to
>     >>>>> carry on because we'd make an EnumDecl a redeclaration of a
>     >>>>> RecordDecl or vicer versa, which would violate the AST
>     >>>>> invariants. I'm not sure how we get here from error recovery:
>     >>>>> both places that set Name to nullptr also jump past this code.
>     >>>>> I think the only way to reach this is if SkipBody->Previous is
>     >>>>> non-null, in which case this is not error recovery and we should
>     >>>>> perform this check.
>     >>>>>
>     >>>>> Can you instead change isAcceptableTagRedeclaration to take a
>     >>>>> const IdentifierInfo*?
>     >>>>
>     >>>> The IdentifierInfo is only used in a diagnostic - I think it makes
>     sense
>     >>>> to pass `OrigName` here if we want the check. This is what we do a
>     >>>> little below for diagnoseQualifiedDeclaration().
>     >>>
>     >>> But OrigName is null too, isn't it? The only places that make Name and
>     >>> OrigName differ also jump past this code.
>     >>
>     >> You're right - I misread the assert where OrigName is assigned and was
>     >> confused.
>     >>
>     >> I don't think passing const IdentifierInfo* is correct -
>     >> isAcceptableTagRedeclaration implements [dcl.type.elab]p3, which is
>     >> about elaborated-type-specifiers with the same name agreeing on kind.
>     >> It doesn't make much sense to do this check if there isn't a name at
>     >> all, and it seems suspicious to me that we're getting here at all.
>     >>
>     >> What does !Previous.empty() mean if we don't have a Name?
>     >>
>     >> This behaviour only triggers on one test in the test suite,
>     >> test/Modules/submodules-merge-defs.cpp. You can reproduce with
>     >> assert(Name) before the call to isAcceptableTagRedeclaration() if
>     you're
>     >> curious.
>     >
>     > FWIW, we're no longer hitting the UB here as of r241763 - I believe
>     > r241743 is the relevant commit. It's not totally clear to me whether
>     > that fixed or hid the bug though.
>    
>     Sorry, I was wrong about this, but now I understand how we get here.
>     This fires when we're merging anonymous enum definitions (as per
>     r236690). We're merging an enum like the following with a definition
>     we've already seen:
>    
>         enum { f, g, h };
>    
>     In this case, it's perfectly reasonable to not have a name and we do
>     want to check that the redecl is compatible, so I've gone with the
>     "const IdentifierInfo *" solution, attached. Okay to commit?
>
> LGTM

r241963

>
>     On a side note, passing null pointers to `operator<<(const
>     DiagnosticBuilder, const IdentifierInfo)` eventually leads to this code:
>    
>         case DiagnosticsEngine::ak_identifierinfo: {
>           const IdentifierInfo *II = getArgIdentifier(ArgNo);
>           assert(ModifierLen == 0 && "No modifiers for strings yet");
>    
>           // Don't crash if get passed a null pointer by accident.
>           if (!II) {
>             const char *S = "(null)";
>             OutStr.append(S, S + strlen(S));
>             continue;
>           }
>    
>           llvm::raw_svector_ostream(OutStr) << '\'' << II->getName() << '\'';
>           break;
>         }
>    
>     Here, the comment claims that passing a null pointer is an accident, but
>     we seem to do it intentionally (at least, a few tests fail if I change
>     it to an assert). Is the comment wrong, or should we look into fixing
>     that?
>
> Just looking through ActOnTag it seems there are a couple of different
> diagnostics which can already produce such errors for anonymous declarations.
> Ideally we should diagnose this better, though that won't be completely
> trivial because it's not always reasonable to assume that a null II means "
> <anonymous declaration>". So yes, we should fix this, but it's unlikely we
> have good test coverage for these cases today so it may be a little tricky to
> track them all down.




More information about the cfe-commits mailing list