Ignore uses of declarations from inside of functions

Using type deduction, a function can define a local type and return it:

    auto func() {
      struct X {};
      return X();
    }

Before this change, IWYU would register X as a use for callers of func.

In certain situations (union inside lambda inside macro) this would lead
to a forward-declare use trying to format a forward-declaration of the
type local to the function in PrintForwardDeclare. This led to a crash
since the lambda was unnamed, but there's really no point trying to
forward-declare a type that is private to a function.

Generalize this so we ignore all uses of symbols declared inside a
function. In order to get in contact with that symbol, we must already
have access to the function, so the containing header must already be
included.

Fixes issue: 795
This commit is contained in:
Kim Gräsman 2022-12-31 15:56:44 +01:00
parent 80410de523
commit 81ee985377
6 changed files with 118 additions and 4 deletions

View File

@ -1507,4 +1507,9 @@ string GetKindName(const TypeLoc typeloc) {
return string(typeloc.getTypePtr()->getTypeClassName()) + "TypeLoc";
}
bool IsDeclaredInsideFunction(const Decl* decl) {
const DeclContext* decl_ctx = decl->getDeclContext();
return isa<FunctionDecl>(decl_ctx);
}
} // namespace include_what_you_use

View File

@ -839,6 +839,10 @@ std::string GetKindName(const clang::Stmt* stmt);
std::string GetKindName(const clang::Type* type);
std::string GetKindName(const clang::TypeLoc typeloc);
// Returns true if decl is entirely inside a function, which implies it's only
// visible from said function.
bool IsDeclaredInsideFunction(const clang::Decl* decl);
} // namespace include_what_you_use
#endif // INCLUDE_WHAT_YOU_USE_IWYU_AST_UTIL_H_

View File

@ -976,8 +976,9 @@ set<string> CalculateMinimalIncludes(
// A4) If the file containing the use has a pragma inhibiting the forward
// declaration of the symbol, change the use to a full info use in order
// to make sure that the compiler can see some declaration of the symbol.
// A5) If a nested class, discard this use (the parent class declaration
// is sufficient).
// A5) If declaration is nested inside a class or a function, discard this use.
// The containing class/function is required to use the nested decl, and
// so will force use of the containing header.
// A6) If any of the redeclarations of this declaration is in the same
// file as the use (and before it), and is actually a definition,
// discard the forward-declare use.
@ -986,7 +987,9 @@ set<string> CalculateMinimalIncludes(
// A8) If --no_fwd_decls has been passed, recategorize as a full use.
// Trimming symbol uses (1st pass):
// B1) TBD
// B1) If declaration is nested inside a function, discard this use.
// The function is required to use the nested decl, and so will force use of
// the containing header.
// B2) If the definition of a full use comes after the use, change the
// full use to a forward-declare use that points to a fwd-decl
// that comes before the use. (This is for cases like typedefs
@ -1108,7 +1111,8 @@ void ProcessForwardDeclare(OneUse* use,
}
}
// (A5) If using a nested class, discard this use.
// (A5) If using a nested class or a type declared inside a function, discard
// this use.
if (IsNestedClass(tag_decl)) {
// iwyu will require the full type of the parent class when it
// recurses on the qualifier (any use of Foo::Bar requires the
@ -1127,6 +1131,12 @@ void ProcessForwardDeclare(OneUse* use,
use->set_ignore_use();
return;
}
} else if (IsDeclaredInsideFunction(tag_decl)) {
VERRS(6) << "Ignoring fwd-decl use of " << use->symbol_name() << " ("
<< use->PrintableUseLoc() << "): declared inside a "
<< "function\n";
use->set_ignore_use();
return;
}
// (A6) If a definition exists earlier in this file, discard this use.
@ -1197,6 +1207,16 @@ void ProcessFullUse(OneUse* use,
if (use->ignore_use()) // we're already ignoring it
return;
// (B1) If declaration is inside a function, it can only be seen via said
// function. Discard this use and assume the use of the function provides.
if (IsDeclaredInsideFunction(use->decl())) {
VERRS(6) << "Ignoring full use of " << use->symbol_name() << " ("
<< use->PrintableUseLoc() << "): declared inside a "
<< "function\n";
use->set_ignore_use();
return;
}
// We normally ignore uses for builtins, but when there is a mapping defined
// for the symbol, we should respect that. So, we need to determine whether
// the symbol has any mappings.

View File

@ -0,0 +1,10 @@
//===--- decl_inside_func-d1.h - test input file for iwyu -----------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
#include "tests/cxx/decl_inside_func-i1.h"

View File

@ -0,0 +1,34 @@
//===--- decl_inside_func-i1.h - test input file for iwyu -----------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// Reduced/derived/copied from Quill 1.6.3:
// https://github.com/odygrd/quill/blob/v1.6.3/quill/include/quill/PatternFormatter.h#L35
// This macro defines a lambda containing a function-local declaration of a
// union X, which is cleverly returned from the lambda.
//
// Declarations inside a function are only directly visible from the function
// itself, so any uses of X should be ignored -- the only way to get in any
// contact with X is to use MACRO, so this header will be desired already.
#define MACRO(str) \
[] { \
union X { \
static constexpr auto value() { \
return str; \
} \
}; \
return X{}; \
}()
/**** IWYU_SUMMARY
(tests/cxx/decl_inside_func-i1.h has correct #includes/fwd-decls)
***** IWYU_SUMMARY */

View File

@ -0,0 +1,41 @@
//===--- decl_inside_func.cc - test input file for iwyu -------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
// A declaration inside a function can sometimes escape (see implementation of
// MACRO), and while such a declaration may look like it's independently used
// here, the only way to make contact with it is through its containing
// function.
// Since we must have a declaration of _the function_ in order to call it, the
// contained declaration will have followed along and we can ignore any direct
// uses of the contained decl.
// This used to lead to the contained declaration being fwd-decl used, and IWYU
// crashing when trying to format a printable forward-decl. Ignoring it
// completely avoids that malfunction.
// IWYU_ARGS: -I . -Xiwyu --check_also="tests/cxx/decl_inside_func-i1.h"
#include "tests/cxx/decl_inside_func-d1.h"
const char* f() {
// IWYU: MACRO is ...*decl_inside_func-i1.h
return MACRO("bleh").value();
}
/**** IWYU_SUMMARY
tests/cxx/decl_inside_func.cc should add these lines:
#include "tests/cxx/decl_inside_func-i1.h"
tests/cxx/decl_inside_func.cc should remove these lines:
- #include "tests/cxx/decl_inside_func-d1.h" // lines XX-XX
The full include-list for tests/cxx/decl_inside_func.cc:
#include "tests/cxx/decl_inside_func-i1.h" // for MACRO
***** IWYU_SUMMARY */