diff --git a/iwyu_ast_util.cc b/iwyu_ast_util.cc index 55c5190..4c75628 100644 --- a/iwyu_ast_util.cc +++ b/iwyu_ast_util.cc @@ -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(decl_ctx); +} + } // namespace include_what_you_use diff --git a/iwyu_ast_util.h b/iwyu_ast_util.h index e47ab3d..c70be3f 100644 --- a/iwyu_ast_util.h +++ b/iwyu_ast_util.h @@ -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_ diff --git a/iwyu_output.cc b/iwyu_output.cc index 6a86236..b5ee605 100644 --- a/iwyu_output.cc +++ b/iwyu_output.cc @@ -976,8 +976,9 @@ set 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 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. diff --git a/tests/cxx/decl_inside_func-d1.h b/tests/cxx/decl_inside_func-d1.h new file mode 100644 index 0000000..003a2a2 --- /dev/null +++ b/tests/cxx/decl_inside_func-d1.h @@ -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" diff --git a/tests/cxx/decl_inside_func-i1.h b/tests/cxx/decl_inside_func-i1.h new file mode 100644 index 0000000..9d24a8b --- /dev/null +++ b/tests/cxx/decl_inside_func-i1.h @@ -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 */ diff --git a/tests/cxx/decl_inside_func.cc b/tests/cxx/decl_inside_func.cc new file mode 100644 index 0000000..18e5287 --- /dev/null +++ b/tests/cxx/decl_inside_func.cc @@ -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 */