diff options
author | Sylvestre Ledru <sylvestre@debian.org> | 2020-11-26 15:37:46 +0100 |
---|---|---|
committer | Sylvestre Ledru <sylvestre@debian.org> | 2020-11-26 15:37:46 +0100 |
commit | fba8fe3a1de68d9e7e23347561e248cebfb051ce (patch) | |
tree | c2cf5e2e08626627572d788e3926b114c0fd3ba0 | |
parent | b97db87f8b21543941cf220f49d89eea49c99295 (diff) |
New upstream version 8.15
48 files changed, 1116 insertions, 464 deletions
@@ -6,5 +6,7 @@ __pycache__ .gdb_history
compile_commands.json
+tags
+TAGS
/build
diff --git a/.travis.yml b/.travis.yml index 8caec05..9f6270d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,44 +1,32 @@ -dist: xenial +dist: bionic language: cpp addons: apt: sources: - - sourceline: 'deb http://apt.llvm.org/xenial/ llvm-toolchain-xenial-10 main' + - sourceline: 'deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-11 main' key_url: 'https://apt.llvm.org/llvm-snapshot.gpg.key' packages: + - cmake - ninja-build - # TODO: These should really be the snapshots packages, but they are currently - # broken. Remove the version suffix once they get fixed (see issue #642 for - # more info) - - llvm-10-dev - - llvm-10-tools - - libclang-10-dev - - clang-10 + - llvm-11-dev + - libclang-11-dev + - clang-11 before_install: - # Install a supported cmake version (>= 3.4.3) - - wget -O cmake.sh https://cmake.org/files/v3.10/cmake-3.10.0-Linux-x86_64.sh - - sudo sh cmake.sh --skip-license --exclude-subdir --prefix=/usr/local - # Extract the version number from the most-recently installed LLVM - VERSION=`ls -t /usr/lib/ | grep '^llvm-' | head -n 1 | sed -E 's/llvm-(.+)/\1/'` # Absolute paths to LLVM's root and bin directory - - ROOT_PATH=`llvm-config-$VERSION --prefix` + - PREFIX_PATH=`llvm-config-$VERSION --prefix` - BIN_PATH=`llvm-config-$VERSION --bindir` - # TODO: Remove these hacky fixups - - (cd $ROOT_PATH/lib && sudo ln -rsf libclang-cpp.so.1 libclang-cpp-$VERSION.so.1) - - sudo touch $ROOT_PATH/lib/libExampleIRTransforms.a - - sudo touch $ROOT_PATH/lib/libBye.a - script: # Build IWYU - mkdir build - cd build - - cmake -GNinja -DCMAKE_PREFIX_PATH=$ROOT_PATH -DCMAKE_C_COMPILER=$BIN_PATH/clang -DCMAKE_CXX_COMPILER=$BIN_PATH/clang++ -DCMAKE_INSTALL_PREFIX=./ ../ + - cmake -GNinja -DCMAKE_PREFIX_PATH=$PREFIX_PATH -DCMAKE_C_COMPILER=$BIN_PATH/clang -DCMAKE_CXX_COMPILER=$BIN_PATH/clang++ -DCMAKE_INSTALL_PREFIX=./ ../ - ninja # Test IWYU diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fe0411..60477ba 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,6 +26,10 @@ endif() message(STATUS "IWYU: configuring for LLVM ${LLVM_VERSION}...") +# The good default is given by the llvm toolchain installation itself, but still in +# case both static and shared libraries are available, allow to override that default. +option(IWYU_LINK_CLANG_DYLIB "Link against the clang dynamic library" ${CLANG_LINK_CLANG_DYLIB}) + set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) add_definitions(${LLVM_DEFINITIONS}) @@ -64,21 +68,22 @@ if (NOT IWYU_IN_TREE) # Use only major.minor.patch for the resource directory structure; some # platforms include suffix in LLVM_VERSION. set(llvm_ver ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}) - set(clang_headers_src ${CMAKE_PREFIX_PATH}/lib/clang/${llvm_ver}/include) - set(clang_headers_dst ${CMAKE_BINARY_DIR}/lib/clang/${llvm_ver}/include) + set(clang_headers_src "${LLVM_LIBRARY_DIR}/clang/${llvm_ver}/include") + set(clang_headers_dst "${CMAKE_BINARY_DIR}/lib/clang/${llvm_ver}/include") - file(GLOB_RECURSE in_files RELATIVE ${clang_headers_src} ${clang_headers_src}/*) + file(GLOB_RECURSE in_files RELATIVE "${clang_headers_src}" + "${clang_headers_src}/*") set(out_files) foreach (file ${in_files}) - set(src ${clang_headers_src}/${file}) - set(dst ${clang_headers_dst}/${file}) + set(src "${clang_headers_src}/${file}") + set(dst "${clang_headers_dst}/${file}") - add_custom_command(OUTPUT ${dst} - DEPENDS ${src} - COMMAND ${CMAKE_COMMAND} -E copy_if_different ${src} ${dst} + add_custom_command(OUTPUT "${dst}" + DEPENDS "${src}" + COMMAND ${CMAKE_COMMAND} -E copy_if_different "${src}" "${dst}" COMMENT "Copying clang's ${file}...") - list(APPEND out_files ${dst}) + list(APPEND out_files "${dst}") endforeach() add_custom_target(clang-resource-headers ALL DEPENDS ${out_files}) @@ -105,7 +110,7 @@ if (MSVC) endif() # If only clang-cpp is available, we take that. -if (TARGET clang-cpp AND NOT TARGET clangBasic) +if (IWYU_LINK_CLANG_DYLIB) target_link_libraries(include-what-you-use PRIVATE clang-cpp) else() target_link_libraries(include-what-you-use @@ -37,7 +37,10 @@ We assume you already have compiled LLVM and Clang libraries on your system, eit | 6 | 0.10 | `clang_6.0` | | 7 | 0.11 | `clang_7.0` | | 8 | 0.12 | `clang_8.0` | +| 9 | 0.13 | `clang_9.0` | +| 10 | 0.14 | `clang_10` | | ... | ... | ... | +| trunk | master | `master` | > NOTE: If you use the Debian/Ubuntu packaging available from <https://apt.llvm.org>, you'll need the following packages installed: > diff --git a/boost-1.64-all-private.imp b/boost-1.64-all-private.imp index 5f32299..439e4d2 100644 --- a/boost-1.64-all-private.imp +++ b/boost-1.64-all-private.imp @@ -1,15 +1,13 @@ [ -#grep -r '^ *# *include' boost/ | grep -e "boost/[^:]*/detail/.*hp*:" -e "boost/[^:]*/impl/.*hp*:" | grep -e "\:.*/detail/" -e "\:.*/impl/" | perl -nle 'm/^([^:]+).*["<]([^>]+)[">]/ && print qq@ { include: ["<$2>", private, "<$1>", private ] },@' | grep -e \\[\"\<boost/ | sort -u -#remove circular dependencies -# boost/fusion/container/set/detail/value_of_data_impl.hpp with itself... -# -# { include: ["<boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp>", private, "<boost/numeric/odeint/integrate/detail/integrate_n_steps.hpp>", private ] }, -# { include: ["<boost/numeric/odeint/integrate/detail/integrate_n_steps.hpp>", private, "<boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp>", private ] }, -# -# { include: ["<boost/python/detail/type_list.hpp>", private, "<boost/python/detail/type_list_impl.hpp>", private ] }, -# { include: ["<boost/python/detail/type_list.hpp>", private, "<boost/python/detail/type_list_impl_no_pts.hpp>", private ] }, -# { include: ["<boost/python/detail/type_list_impl.hpp>", private, "<boost/python/detail/type_list.hpp>", private ] }, -# { include: ["<boost/python/detail/type_list_impl_no_pts.hpp>", private, "<boost/python/detail/type_list.hpp>", private ] }, +# grep -r '^ *# *include' boost/ | grep -e "boost/[^:]*/detail/.*hp*:" -e "boost/[^:]*/impl/.*hp*:" | grep -e "\:.*/detail/" -e "\:.*/impl/" | perl -nle 'm/^([^:]+).*["<]([^>]+)[">]/ && print qq@ { include: ["<$2>", private, "<$1>", private ] },@' | grep -e \\[\"\<boost/ | sort -u +# manually remove circular dependencies +# * boost/fusion/container/set/detail/value_of_data_impl.hpp with itself +# * boost/variant/detail/multivisitors_cpp14_based.hpp with itself +# * boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp with boost/numeric/odeint/integrate/detail/integrate_n_steps.hpp and back +# * boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp with boost/numeric/odeint/integrate/detail/integrate_const.hpp and back +# * boost/numeric/odeint/iterator/integrate/detail/integrate_adaptive.hpp with boost/numeric/odeint/iterator/integrate/detail/integrate_const.hpp and back +# * boost/python/detail/type_list.hpp with boost/python/detail/type_list_impl.hpp and back +# * boost/python/detail/type_list.hpp with boost/python/detail/type_list_impl_no_pts.hpp and back { include: ["<boost/accumulators/numeric/detail/function_n.hpp>", private, "<boost/accumulators/numeric/detail/function2.hpp>", private ] }, { include: ["<boost/accumulators/numeric/detail/function_n.hpp>", private, "<boost/accumulators/numeric/detail/function3.hpp>", private ] }, @@ -4360,10 +4358,6 @@ { include: ["<boost/numeric/interval/detail/msvc_rounding_control.hpp>", private, "<boost/numeric/interval/detail/x86_rounding_control.hpp>", private ] }, { include: ["<boost/numeric/interval/detail/test_input.hpp>", private, "<boost/numeric/interval/detail/division.hpp>", private ] }, { include: ["<boost/numeric/interval/detail/x86gcc_rounding_control.hpp>", private, "<boost/numeric/interval/detail/x86_rounding_control.hpp>", private ] }, - { include: ["<boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp>", private, "<boost/numeric/odeint/integrate/detail/integrate_const.hpp>", private ] }, - { include: ["<boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp>", private, "<boost/numeric/odeint/integrate/detail/integrate_n_steps.hpp>", private ] }, - { include: ["<boost/numeric/odeint/integrate/detail/integrate_const.hpp>", private, "<boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp>", private ] }, - { include: ["<boost/numeric/odeint/integrate/detail/integrate_n_steps.hpp>", private, "<boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp>", private ] }, { include: ["<boost/numeric/odeint/iterator/detail/ode_iterator_base.hpp>", private, "<boost/numeric/odeint/iterator/impl/adaptive_iterator_impl.hpp>", private ] }, { include: ["<boost/numeric/odeint/iterator/detail/ode_iterator_base.hpp>", private, "<boost/numeric/odeint/iterator/impl/const_step_iterator_impl.hpp>", private ] }, { include: ["<boost/numeric/odeint/iterator/detail/ode_iterator_base.hpp>", private, "<boost/numeric/odeint/iterator/impl/n_step_iterator_impl.hpp>", private ] }, @@ -4372,9 +4366,7 @@ { include: ["<boost/numeric/odeint/iterator/integrate/detail/functors.hpp>", private, "<boost/numeric/odeint/iterator/integrate/detail/integrate_const.hpp>", private ] }, { include: ["<boost/numeric/odeint/iterator/integrate/detail/functors.hpp>", private, "<boost/numeric/odeint/iterator/integrate/detail/integrate_n_steps.hpp>", private ] }, { include: ["<boost/numeric/odeint/iterator/integrate/detail/functors.hpp>", private, "<boost/numeric/odeint/iterator/integrate/detail/integrate_times.hpp>", private ] }, - { include: ["<boost/numeric/odeint/iterator/integrate/detail/integrate_adaptive.hpp>", private, "<boost/numeric/odeint/iterator/integrate/detail/integrate_const.hpp>", private ] }, { include: ["<boost/numeric/odeint/iterator/integrate/detail/integrate_adaptive.hpp>", private, "<boost/numeric/odeint/iterator/integrate/detail/integrate_n_steps.hpp>", private ] }, - { include: ["<boost/numeric/odeint/iterator/integrate/detail/integrate_const.hpp>", private, "<boost/numeric/odeint/iterator/integrate/detail/integrate_adaptive.hpp>", private ] }, { include: ["<boost/numeric/odeint/stepper/detail/generic_rk_call_algebra.hpp>", private, "<boost/numeric/odeint/stepper/detail/generic_rk_algorithm.hpp>", private ] }, { include: ["<boost/numeric/odeint/stepper/detail/generic_rk_operations.hpp>", private, "<boost/numeric/odeint/stepper/detail/generic_rk_algorithm.hpp>", private ] }, { include: ["<boost/numeric/odeint/util/detail/less_with_sign.hpp>", private, "<boost/numeric/odeint/integrate/detail/integrate_adaptive.hpp>", private ] }, @@ -4914,10 +4906,6 @@ { include: ["<boost/python/detail/scope.hpp>", private, "<boost/python/detail/defaults_def.hpp>", private ] }, { include: ["<boost/python/detail/sfinae.hpp>", private, "<boost/python/detail/enable_if.hpp>", private ] }, { include: ["<boost/python/detail/signature.hpp>", private, "<boost/python/detail/caller.hpp>", private ] }, - { include: ["<boost/python/detail/type_list.hpp>", private, "<boost/python/detail/type_list_impl.hpp>", private ] }, - { include: ["<boost/python/detail/type_list.hpp>", private, "<boost/python/detail/type_list_impl_no_pts.hpp>", private ] }, - { include: ["<boost/python/detail/type_list_impl.hpp>", private, "<boost/python/detail/type_list.hpp>", private ] }, - { include: ["<boost/python/detail/type_list_impl_no_pts.hpp>", private, "<boost/python/detail/type_list.hpp>", private ] }, { include: ["<boost/python/detail/value_is_xxx.hpp>", private, "<boost/python/detail/value_is_shared_ptr.hpp>", private ] }, { include: ["<boost/python/detail/wrap_python.hpp>", private, "<boost/python/detail/prefix.hpp>", private ] }, { include: ["<boost/qvm/detail/determinant_impl.hpp>", private, "<boost/qvm/detail/cofactor_impl.hpp>", private ] }, @@ -5287,7 +5275,6 @@ { include: ["<boost/variant/detail/has_result_type.hpp>", private, "<boost/variant/detail/apply_visitor_delayed.hpp>", private ] }, { include: ["<boost/variant/detail/has_result_type.hpp>", private, "<boost/variant/detail/apply_visitor_unary.hpp>", private ] }, { include: ["<boost/variant/detail/move.hpp>", private, "<boost/variant/detail/initializer.hpp>", private ] }, - { include: ["<boost/variant/detail/multivisitors_cpp14_based.hpp>", private, "<boost/variant/detail/multivisitors_cpp14_based.hpp>", private ] }, { include: ["<boost/variant/detail/substitute_fwd.hpp>", private, "<boost/variant/detail/substitute.hpp>", private ] }, { include: ["<boost/variant/detail/substitute.hpp>", private, "<boost/variant/detail/enable_recursive.hpp>", private ] }, { include: ["<boost/vmd/detail/adjust_tuple_type.hpp>", private, "<boost/vmd/detail/recurse/equal/equal_headers.hpp>", private ] }, diff --git a/fix_includes.py b/fix_includes.py index f1cfbf2..7600361 100755 --- a/fix_includes.py +++ b/fix_includes.py @@ -135,7 +135,7 @@ _IWYU_PRAGMA_ASSOCIATED_RE = re.compile(r'IWYU\s*pragma:\s*associated') # we fold _C_COMMENT_START_RE and _C_COMMENT_END_RE into _COMMENT_LINE_RE. # The _NAMESPACE_CONTINUE_ALLMAN_MIXED_RE is also set on lines when Allman # and mixed namespaces are detected but the RE is too easy to match to add -# under normal circumstances (must always be proceded by Allman/mixed). +# under normal circumstances (must always be preceded by Allman/mixed). _LINE_TYPES = [_COMMENT_LINE_RE, _BLANK_LINE_RE, _NAMESPACE_START_RE, _NAMESPACE_START_ALLMAN_RE, _NAMESPACE_START_MIXED_RE, _NAMESPACE_END_RE, @@ -235,6 +235,10 @@ class IWYUOutputRecord(object): # report, though, forward-declares inside '#if 0' or similar.) self.seen_forward_declare_lines = set() + # Those spans which pertain to nested forward declarations (i.e. of nested + # classes). This set should be a subset of self.seen_forward_declare_lines. + self.nested_forward_declare_lines = set() + # A set of each line in the iwyu 'add' section. self.includes_and_forward_declares_to_add = OrderedSet() @@ -261,6 +265,7 @@ class IWYUOutputRecord(object): self.lines_to_delete.intersection_update(other.lines_to_delete) self.some_include_lines.update(other.some_include_lines) self.seen_forward_declare_lines.update(other.seen_forward_declare_lines) + self.nested_forward_declare_lines.update(other.nested_forward_declare_lines) self.includes_and_forward_declares_to_add.update( other.includes_and_forward_declares_to_add) self.full_include_lines.update(other.full_include_lines) @@ -443,8 +448,10 @@ class IWYUOutputParser(object): continue m = self._LINE_NUMBERS_COMMENT_RE.search(line) if m: - retval.seen_forward_declare_lines.add((int(m.group(1)), - int(m.group(2))+1)) + line_range = (int(m.group(1)), int(m.group(2))+1) + retval.seen_forward_declare_lines.add(line_range) + if '::' in line: + retval.nested_forward_declare_lines.add(line_range) # IWYUOutputRecord.includes_and_forward_declares_to_add for line in self.lines_by_section.get(self._ADD_SECTION_RE, []): @@ -503,6 +510,10 @@ class LineInfo(object): # of the class/struct. For other types of lines, this is None. self.key = None + # If this is a forward-declaration of a nested class, then this will be + # True. + self.is_nested_forward_declaration = False + def __str__(self): if self.deleted: line = 'XX-%s-XX' % self.line @@ -771,6 +782,13 @@ def _CalculateLineTypesAndKeys(file_lines, iwyu_record): % (iwyu_record.filename, line_number)) file_lines[line_number].type = _FORWARD_DECLARE_RE + for (start_line, end_line) in iwyu_record.nested_forward_declare_lines: + for line_number in range(start_line, end_line): + if line_number >= len(file_lines): + raise FixIncludesError('iwyu line number %s:%d is past file-end' + % (iwyu_record.filename, line_number)) + file_lines[line_number].is_nested_forward_declaration = True + # While we're at it, let's do a bit more sanity checking on iwyu_record. for line_number in iwyu_record.lines_to_delete: if line_number >= len(file_lines): @@ -1281,7 +1299,8 @@ def _ShouldInsertBlankLine(decorated_move_span, next_decorated_move_span, def _GetToplevelReorderSpans(file_lines): - """Returns a sorted list of all reorder_spans not inside an #ifdef/namespace. + """Returns a sorted list of all reorder_spans not inside an + #ifdef/namespace/class. This routine looks at all the reorder_spans in file_lines, ignores reorder spans inside #ifdefs and namespaces -- except for the 'header @@ -1337,7 +1356,8 @@ def _GetToplevelReorderSpans(file_lines): good_reorder_spans = [] for reorder_span in reorder_spans: for line_number in range(*reorder_span): - if in_ifdef[line_number] or in_namespace[line_number]: + if (in_ifdef[line_number] or in_namespace[line_number] or + file_lines[line_number].is_nested_forward_declaration): break else: # for/else good_reorder_spans.append(reorder_span) # never in ifdef or namespace diff --git a/fix_includes_test.py b/fix_includes_test.py index b01f145..380da54 100755 --- a/fix_includes_test.py +++ b/fix_includes_test.py @@ -2945,6 +2945,34 @@ template <typename T> class B; // lines 4-5 self.RegisterFileContents({'dont_remove_template_lines': infile}) self.ProcessAndTest(iwyu_output) + def testDontRemoveSimilarNestedDeclarations(self): + """Tests we don't accidentally think repeated nested forward declarations + are dupes.""" + infile = """\ +#include <notused.h> ///- + +class A { + class Inner; +}; + +class B { + class Inner; +}; +""" + iwyu_output = """\ +dont_remove_similar_nested should add these lines: + +dont_remove_similar_nested should remove these lines: +- #include <notused.h> // lines 1-1 + +The full include-list for dont_remove_similar_nested: +class A::Inner; // lines 4-4 +class B::Inner; // lines 8-8 +--- +""" + self.RegisterFileContents({'dont_remove_similar_nested': infile}) + self.ProcessAndTest(iwyu_output) + def testNestedNamespaces(self): infile = """\ // Copyright 2010 diff --git a/gcc.libc.imp b/gcc.libc.imp index 66e9fdb..1d436f5 100644 --- a/gcc.libc.imp +++ b/gcc.libc.imp @@ -97,6 +97,7 @@ { include: [ "<bits/syslog-path.h>", private, "<sys/syslog.h>", private ] }, { include: [ "<bits/syslog.h>", private, "<sys/syslog.h>", private ] }, { include: [ "<bits/termios.h>", private, "<termios.h>", public ] }, + { include: [ "<bits/time.h>", private, "<time.h>", public ] }, { include: [ "<bits/time.h>", private, "<sys/time.h>", public ] }, { include: [ "<bits/timerfd.h>", private, "<sys/timerfd.h>", public ] }, { include: [ "<bits/timex.h>", private, "<sys/timex.h>", public ] }, diff --git a/gcc.symbols.imp b/gcc.symbols.imp index 9f498d8..614cb56 100644 --- a/gcc.symbols.imp +++ b/gcc.symbols.imp @@ -57,6 +57,7 @@ { symbol: [ "intptr_t", private, "<unistd.h>", public ] }, { symbol: [ "key_t", private, "<sys/types.h>", public ] }, { symbol: [ "key_t", private, "<sys/ipc.h>", public ] }, + { symbol: [ "max_align_t", private, "<stddef.h>", public ] }, { symbol: [ "mode_t", private, "<sys/types.h>", public ] }, { symbol: [ "mode_t", private, "<sys/stat.h>", public ] }, { symbol: [ "mode_t", private, "<sys/ipc.h>", public ] }, @@ -77,6 +78,7 @@ { symbol: [ "pid_t", private, "<termios.h>", public ] }, { symbol: [ "pid_t", private, "<time.h>", public ] }, { symbol: [ "pid_t", private, "<utmpx.h>", public ] }, + { symbol: [ "ptrdiff_t", private, "<stddef.h>", public ] }, { symbol: [ "sigset_t", private, "<signal.h>", public ] }, { symbol: [ "sigset_t", private, "<sys/epoll.h>", public ] }, { symbol: [ "sigset_t", private, "<sys/select.h>", public ] }, @@ -105,6 +107,8 @@ { symbol: [ "uid_t", private, "<sys/stat.h>", public ] }, { symbol: [ "useconds_t", private, "<sys/types.h>", public ] }, { symbol: [ "useconds_t", private, "<unistd.h>", public ] }, + { symbol: [ "wchar_t", private, "<stddef.h>", public ] }, + { symbol: [ "wchar_t", private, "<stdlib.h>", public ] }, # glob.h seems to define size_t if necessary, but it should come from stddef. { symbol: [ "size_t", private, "<stddef.h>", public ] }, { symbol: [ "size_t", private, "<stdio.h>", public ] }, diff --git a/generate_qt_mappings.py b/generate_qt_mappings.py index 2a8cd51..3c45237 100755 --- a/generate_qt_mappings.py +++ b/generate_qt_mappings.py @@ -22,42 +22,144 @@ Example usage : from __future__ import print_function import argparse import glob +import json import os import re import sys +OUTFILEHDR = ("# Do not edit! This file was generated by the script %s." % + os.path.basename(__file__)) + +QOBJECT_SYMBOLS = [ + "QObjectList", + "qFindChildren", + "qobject_cast", + "QT_NO_NARROWING_CONVERSIONS_IN_CONNECT", + "Q_CLASSINFO", + "Q_DISABLE_COPY", + "Q_DISABLE_COPY_MOVE", + "Q_DISABLE_MOVE", + "Q_EMIT", + "Q_ENUM", + "Q_ENUM_NS", + "Q_FLAG", + "Q_FLAG_NS", + "Q_GADGET", + "Q_INTERFACES", + "Q_INVOKABLE", + "Q_NAMESPACE", + "Q_NAMESPACE_EXPORT", + "Q_OBJECT", + "Q_PROPERTY", + "Q_REVISION", + "Q_SET_OBJECT_NAME", + "Q_SIGNAL", + "Q_SIGNALS", + "Q_SLOT", + "Q_SLOTS", + "emit", + "slots", + "signals", + "SIGNAL", + "SLOT", +] + + +class QtHeader(object): + """ Carry data associated with a Qt header """ + def __init__(self, headername): + self.headername = headername + self.classname = os.path.basename(headername) + self.modulename = os.path.basename(os.path.dirname(headername)) + self._private_headers = None + + def get_private_headers(self): + """ Return a list of headernames included by this header """ + if self._private_headers is None: + with open(self.headername, 'r') as headerfile: + included = re.findall(r'#include "(.*)\.h"', headerfile.read()) + self._private_headers = list(included) + return self._private_headers + + +def build_imp_lines(symbols_map, includes_map): + """ Generate a big string containing the mappings in .imp format. + + This should ideally return a jsonable structure instead, and use json.dump + to write it to the output file directly. But there doesn't seem to be a + simple way to convince Python's json library to generate a "packed" + formatting, it always prefers to wrap dicts onto multiple lines. + + Cheat, and use json.dumps for escaping and build a string instead. + """ + root = [] + + def jsonline(mapping): + return " " + json.dumps(mapping) + + for symbol, header in symbols_map: + map_to = "<" + header + ">" + root.append(jsonline({"symbol": [symbol, "private", map_to, "public"]})) + + for module, include, header in includes_map: + # Use regex map-from to match both quoted and angled includes and + # optional directory prefix (e.g. <QtCore/qnamespace.h> is equivalent to + # "qnamespace.h"). + map_from = r'@["<](%s/)?%s\.h[">]' % (module, include) + map_to = "<" + header + ">" + root.append(jsonline({"include": [map_from, "private", + map_to, "public"]})) + + lines = "[\n" + lines += ",\n".join(root) + lines += "\n]\n" + return lines + + +def add_mapping_rules(header, symbols_map, includes_map): + """ Add symbol and include mappings for a Qt module. """ + symbols_map += [(header.classname, header.classname)] + for include in header.get_private_headers(): + includes_map += [(header.modulename, include, header.classname)] + + def main(qt_include_dir, output_file): + """ Entry point. """ symbols_map = [] includes_map = [] + deferred_headers = [] - headers = glob.glob(os.path.join(args.qt_include_dir, '**/*[!.h]')) + # Add manual overrides. + symbols_map += [("qDebug", "QtGlobal")] + symbols_map += [(symbol, "QObject") for symbol in QOBJECT_SYMBOLS] + includes_map += [("QtCore", "qnamespace", "Qt")] + + # Collect mapping information from Qt directory tree. + headers = glob.glob(os.path.join(qt_include_dir, '**/*[!.h]')) for header in headers: if os.path.isdir(header): continue - class_name = os.path.basename(header) - module_name = os.path.basename(os.path.dirname(header)) + header = QtHeader(header) + if header.classname == "QInternal": + continue - symbols_map += ['{ symbol: [ "%s", "private", ' % class_name - + '"<%s>", "public" ] }' % class_name] + if header.classname == header.modulename: + deferred_headers.append(header) + else: + add_mapping_rules(header, symbols_map, includes_map) - with open(header, 'r') as f: - content = f.read() + for header in deferred_headers: + add_mapping_rules(header, symbols_map, includes_map) - includes = re.findall(r'#include "(.*)\.h"', content) - for include in includes: - includes_map += [ - '{ include: [ "@[\\"<](%s/)?%s\\\\.h[\\">]", ' % ( - module_name, include) - + '"private", "<%s>", "public" ] }' % class_name] + # Transform to .imp-style format and write to output file. + lines = build_imp_lines(symbols_map, includes_map) + with open(output_file, 'w') as outfile: + print(OUTFILEHDR, file=outfile) + print(lines, file=outfile) - with open(args.output_file, 'w') as f: - print("# Do not edit! This file was generated by the script %s." % - os.path.basename(__file__), file=f) - print("[", file=f) - print(" %s" % ",\n ".join(symbols_map + includes_map), file=f) - print("]", file=f) + return 0 if __name__ == "__main__": @@ -89,7 +89,6 @@ #include <algorithm> // for swap, find, make_pair #include <cstddef> // for size_t -#include <cstdio> // for snprintf #include <cstdlib> // for atoi, exit #include <cstring> #include <deque> // for swap @@ -232,12 +231,6 @@ using std::vector; namespace { -string IntToString(int i) { - char buf[64]; // big enough for any number - snprintf(buf, sizeof(buf), "%d", i); - return buf; -} - bool CanIgnoreLocation(SourceLocation loc) { // If we're in a macro expansion, we always want to treat this as // being in the expansion location, never the as-written location, @@ -327,17 +320,17 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { void set_current_ast_node(ASTNode* an) { current_ast_node_ = an; } bool TraverseDecl(Decl* decl) { - if (current_ast_node_->StackContainsContent(decl)) + if (current_ast_node_ && current_ast_node_->StackContainsContent(decl)) return true; // avoid recursion - ASTNode node(decl, *GlobalSourceManager()); + ASTNode node(decl); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); return Base::TraverseDecl(decl); } bool TraverseStmt(Stmt* stmt) { - if (current_ast_node_->StackContainsContent(stmt)) + if (current_ast_node_ && current_ast_node_->StackContainsContent(stmt)) return true; // avoid recursion - ASTNode node(stmt, *GlobalSourceManager()); + ASTNode node(stmt); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); return Base::TraverseStmt(stmt); } @@ -346,9 +339,9 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { if (qualtype.isNull()) return Base::TraverseType(qualtype); const Type* type = qualtype.getTypePtr(); - if (current_ast_node_->StackContainsContent(type)) + if (current_ast_node_ && current_ast_node_->StackContainsContent(type)) return true; // avoid recursion - ASTNode node(type, *GlobalSourceManager()); + ASTNode node(type); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); return Base::TraverseType(qualtype); } @@ -370,9 +363,9 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { if (typeloc.getAs<QualifiedTypeLoc>()) { typeloc = typeloc.getUnqualifiedLoc(); } - if (current_ast_node_->StackContainsContent(&typeloc)) + if (current_ast_node_ && current_ast_node_->StackContainsContent(&typeloc)) return true; // avoid recursion - ASTNode node(&typeloc, *GlobalSourceManager()); + ASTNode node(&typeloc); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); return Base::TraverseTypeLoc(typeloc); } @@ -380,7 +373,7 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { bool TraverseNestedNameSpecifier(NestedNameSpecifier* nns) { if (nns == nullptr) return true; - ASTNode node(nns, *GlobalSourceManager()); + ASTNode node(nns); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); if (!this->getDerived().VisitNestedNameSpecifier(nns)) return false; @@ -390,7 +383,7 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { bool TraverseNestedNameSpecifierLoc(NestedNameSpecifierLoc nns_loc) { if (!nns_loc) // using NNSLoc::operator bool() return true; - ASTNode node(&nns_loc, *GlobalSourceManager()); + ASTNode node(&nns_loc); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); // TODO(csilvers): have VisitNestedNameSpecifierLoc instead. if (!this->getDerived().VisitNestedNameSpecifier( @@ -400,7 +393,7 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { } bool TraverseTemplateName(TemplateName template_name) { - ASTNode node(&template_name, *GlobalSourceManager()); + ASTNode node(&template_name); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); if (!this->getDerived().VisitTemplateName(template_name)) return false; @@ -408,7 +401,7 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { } bool TraverseTemplateArgument(const TemplateArgument& arg) { - ASTNode node(&arg, *GlobalSourceManager()); + ASTNode node(&arg); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); if (!this->getDerived().VisitTemplateArgument(arg)) return false; @@ -416,7 +409,7 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { } bool TraverseTemplateArgumentLoc(const TemplateArgumentLoc& argloc) { - ASTNode node(&argloc, *GlobalSourceManager()); + ASTNode node(&argloc); CurrentASTNodeUpdater canu(¤t_ast_node_, &node); if (!this->getDerived().VisitTemplateArgumentLoc(argloc)) return false; @@ -451,7 +444,7 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { // etc. string AnnotatedName(const string& name) const { return (PrintableCurrentLoc() + ": (" + - IntToString(current_ast_node_->depth()) + GetSymbolAnnotation() + + std::to_string(current_ast_node_->depth()) + GetSymbolAnnotation() + (current_ast_node_->in_forward_declare_context() ? ", fwd decl" : "") + ") [ " + name + " ] "); @@ -549,32 +542,10 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { // // * CXXDestructorDecl: a destructor call for each non-POD field // in the dtor's class, and each base type of that class. - // * CXXConstructorDecl: a constructor call for each type/base - // of the class that is not explicitly listed in an initializer. // * CXXRecordDecl: a CXXConstructorDecl for each implicit // constructor (zero-arg and copy). A CXXDestructor decl // if the destructor is implicit. A CXXOperatorCallDecl if // operator= is explicit. - - bool TraverseCXXConstructorDecl(clang::CXXConstructorDecl* decl) { - if (!Base::TraverseCXXConstructorDecl(decl)) return false; - if (CanIgnoreCurrentASTNode()) return true; - // We only care about classes that are actually defined. - if (!decl || !decl->isThisDeclarationADefinition()) return true; - - // RAV's TraverseCXXConstructorDecl already handles - // explicitly-written initializers, so we just want the rest. - for (CXXConstructorDecl::init_const_iterator it = decl->init_begin(); - it != decl->init_end(); ++it) { - const CXXCtorInitializer* init = *it; - if (!init->isWritten()) { - if (!this->getDerived().TraverseStmt(init->getInit())) - return false; - } - } - return true; - } - bool TraverseCXXDestructorDecl(clang::CXXDestructorDecl* decl) { if (!Base::TraverseCXXDestructorDecl(decl)) return false; if (CanIgnoreCurrentASTNode()) return true; @@ -655,70 +626,21 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { sema.PerformPendingInstantiations(); } - // clang doesn't bother to set a TypeSourceInfo for implicit - // methods, since, well, they don't have a location. But - // RecursiveASTVisitor crashes without one, so when we lie and say - // we're not implicit, we have to lie and give a location as well. - // (We give the null location.) This is a small memory leak. - void SetTypeSourceInfoForImplicitMethodIfNeeded(FunctionDecl* decl) { - if (decl->getTypeSourceInfo() == nullptr) { - ASTContext& ctx = compiler_->getASTContext(); - decl->setTypeSourceInfo(ctx.getTrivialTypeSourceInfo(decl->getType())); - } - } - - // RAV.h's TraverseDecl() ignores implicit nodes, so we lie a bit. - // TODO(csilvers): figure out a more principled way. - bool TraverseImplicitDeclHelper(clang::FunctionDecl* decl) { - CHECK_(decl->isImplicit() && "TraverseImplicitDecl is for implicit decls"); - decl->setImplicit(false); - SetTypeSourceInfoForImplicitMethodIfNeeded(decl); - bool retval = this->getDerived().TraverseDecl(decl); - decl->setImplicit(true); - return retval; - } - // Handle implicit methods that otherwise wouldn't be seen by RAV. bool TraverseCXXRecordDecl(clang::CXXRecordDecl* decl) { - if (!Base::TraverseCXXRecordDecl(decl)) return false; if (CanIgnoreCurrentASTNode()) return true; // We only care about classes that are actually defined. - if (!decl || !decl->isThisDeclarationADefinition()) return true; - - InstantiateImplicitMethods(decl); - - // Check to see if there are any implicit constructors. Can be - // several: implicit default constructor, implicit copy constructor. - for (CXXRecordDecl::ctor_iterator it = decl->ctor_begin(); - it != decl->ctor_end(); ++it) { - CXXConstructorDecl* ctor = *it; - if (ctor->isImplicit() && !ctor->isDeleted()) { - if (!TraverseImplicitDeclHelper(ctor)) - return false; - } + if (decl && decl->isThisDeclarationADefinition()) { + InstantiateImplicitMethods(decl); } - // Check the (single) destructor. - bool has_implicit_declared_destructor = - (!decl->needsImplicitDestructor() && - !decl->hasUserDeclaredDestructor()); - if (has_implicit_declared_destructor) { - if (!TraverseImplicitDeclHelper(decl->getDestructor())) - return false; - } - - // Check copy and move assignment operators. - for (CXXRecordDecl::method_iterator it = decl->method_begin(); - it != decl->method_end(); ++it) { - bool is_assignment_operator = - it->isCopyAssignmentOperator() || it->isMoveAssignmentOperator(); - if (is_assignment_operator && it->isImplicit()) { - if (!TraverseImplicitDeclHelper(*it)) - return false; - } - } + return Base::TraverseCXXRecordDecl(decl); + } - return true; + bool TraverseClassTemplateSpecializationDecl( + clang::ClassTemplateSpecializationDecl* decl) { + if (!Base::TraverseClassTemplateSpecializationDecl(decl)) return false; + return TraverseCXXRecordDecl(decl); } //------------------------------------------------------------ @@ -842,8 +764,6 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { !IsCXXConstructExprInInitializer(current_ast_node()) && !IsCXXConstructExprInNewExpr(current_ast_node()); if (will_call_implicit_destructor_on_leaving_scope) { - // Create the destructor if it hasn't been lazily created yet. - InstantiateImplicitMethods(expr->getConstructor()->getParent()); if (const CXXDestructorDecl* dtor_decl = GetSiblingDestructorFor(expr)) { if (!this->getDerived().TraverseImplicitDestructorCall( const_cast<CXXDestructorDecl*>(dtor_decl), GetTypeOf(expr))) @@ -858,7 +778,6 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { if (CanIgnoreCurrentASTNode()) return true; // In this case, we *know* we're responsible for destruction as well. - InstantiateImplicitMethods(expr->getConstructor()->getParent()); CXXConstructorDecl* ctor_decl = expr->getConstructor(); CXXDestructorDecl* dtor_decl = const_cast<CXXDestructorDecl*>(GetSiblingDestructorFor(expr)); @@ -926,6 +845,12 @@ class BaseAstVisitor : public RecursiveASTVisitor<Derived> { return true; } + /// Return whether this visitor should recurse into implicit + /// code, e.g., implicit constructors and destructors. + bool shouldVisitImplicitCode() const { + return true; + } + protected: CompilerInstance* compiler() { return compiler_; } @@ -1048,15 +973,7 @@ class AstFlattenerVisitor : public BaseAstVisitor<AstFlattenerVisitor> { CHECK_(seen_nodes_.empty() && "Nodes should be clear before GetNodesBelow"); NodeSet* node_set = &nodeset_decl_cache_[decl]; if (node_set->empty()) { - if (decl->isImplicit()) { - // TODO: For now, it is only working for functions. Check if it could - // make sense for other implicit decls too (e.g. BuiltinTemplateDecl) - if (FunctionDecl* func = DynCastFrom(decl)) { - TraverseImplicitDeclHelper(func); - } - } else { - TraverseDecl(decl); - } + TraverseDecl(decl); swap(*node_set, seen_nodes_); // move the seen_nodes_ into the cache } return *node_set; // returns the cache entry @@ -1242,13 +1159,19 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> { using Base::PrintableCurrentLoc; using Base::current_ast_node; + enum class IgnoreKind { + ForUse, + ForExpansion, + }; + //------------------------------------------------------------ // Pure virtual methods that a subclass must implement. // Returns true if we are not interested in iwyu information for the // given type, where the type is *not* the current AST node. // TODO(csilvers): check we're calling this consistent with its API. - virtual bool CanIgnoreType(const Type* type) const = 0; + virtual bool CanIgnoreType(const Type* type, + IgnoreKind = IgnoreKind::ForUse) const = 0; // Returns true if we are not interested in doing an iwyu check on // the given decl, where the decl is *not* the current AST node. @@ -1649,7 +1572,7 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> { // Sometimes a shadow decl comes between us and the 'real' decl. if (const UsingShadowDecl* shadow_decl = DynCastFrom(used_decl)) target_decl = shadow_decl->getTargetDecl(); - + // Map private decls like __normal_iterator to their public counterpart. target_decl = MapPrivateDeclToPublicDecl(target_decl); if (CanIgnoreDecl(target_decl)) @@ -1677,7 +1600,7 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> { // instead? We can call it "Use what you use". :-) // TODO(csilvers): check for using statements and namespace aliases too. if (const UsingDecl* using_decl - = GetUsingDeclarationOf(used_decl, + = GetUsingDeclarationOf(used_decl, GetDeclContext(current_ast_node()))) { preprocessor_info().FileInfoFor(used_in)->ReportUsingDeclUse( used_loc, using_decl, use_flags, "(for using decl)"); @@ -1736,7 +1659,7 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> { // If we're a use that depends on a using declaration, make sure // we #include the file with the using declaration. if (const UsingDecl* using_decl - = GetUsingDeclarationOf(used_decl, + = GetUsingDeclarationOf(used_decl, GetDeclContext(current_ast_node()))) { preprocessor_info().FileInfoFor(used_in)->ReportUsingDeclUse( used_loc, using_decl, ComputeUseFlags(current_ast_node()), @@ -2269,6 +2192,8 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> { bool VisitUnaryExprOrTypeTraitExpr(clang::UnaryExprOrTypeTraitExpr* expr) { if (CanIgnoreCurrentASTNode()) return true; + current_ast_node()->set_in_forward_declare_context(false); + // Calling sizeof on a reference-to-X is the same as calling it on X. // If sizeof() takes a type, this is easy to check. If sizeof() // takes an expr, it's hard to tell -- GetTypeOf(expr) 'sees through' @@ -2675,9 +2600,7 @@ class IwyuBaseAstVisitor : public BaseAstVisitor<Derived> { // return false; // Read past elaborations like 'class' keyword or namespaces. - while (ast_node->ParentIsA<ElaboratedType>()) { - ast_node = ast_node->parent(); - } + ast_node = MostElaboratedAncestor(ast_node); // Now there are two options: either we have a type or we have a declaration // involving a type. @@ -2891,10 +2814,22 @@ class InstantiatedTemplateVisitor string GetSymbolAnnotation() const override { return " in tpl"; } - bool CanIgnoreType(const Type* type) const override { - if (!IsTypeInteresting(type) || !IsKnownTemplateParam(type)) + bool CanIgnoreType(const Type* type, IgnoreKind ignore_kind = + IgnoreKind::ForUse) const override { + if (!IsTypeInteresting(type)) return true; + switch (ignore_kind) { + case IgnoreKind::ForUse: + if (!IsKnownTemplateParam(type)) + return true; + break; + case IgnoreKind::ForExpansion: + if (!InvolvesKnownTemplateParam(type)) + return true; + break; + } + // If we're a default template argument, we should ignore the type // if the template author intend-to-provide it, but otherwise we // should not ignore it -- the caller is responsible for the type. @@ -2911,7 +2846,6 @@ class InstantiatedTemplateVisitor return IsDefaultTemplateParameter(type) && IsProvidedByTemplate(type); } - bool IsTypeInteresting(const Type* type) const { // We only care about types that would have been dependent in the // uninstantiated template: that is, SubstTemplateTypeParmType types @@ -2928,6 +2862,11 @@ class InstantiatedTemplateVisitor return ContainsKey(resugar_map_, type); } + bool InvolvesKnownTemplateParam(const Type* type) const { + return InvolvesTypeForWhich( + type, [&](const Type* type) { return IsKnownTemplateParam(type); }); + } + // We ignore function calls in nodes_to_ignore_, which were already // handled by the template-as-written, and function names that we // are not responsible for because the template code is (for @@ -3044,7 +2983,8 @@ class InstantiatedTemplateVisitor bool TraverseSubstTemplateTypeParmTypeHelper( const clang::SubstTemplateTypeParmType* type) { - if (CanIgnoreCurrentASTNode() || CanIgnoreType(type)) + if (CanIgnoreCurrentASTNode() || + CanIgnoreType(type, IgnoreKind::ForExpansion)) return true; const Type* actual_type = ResugarType(type); @@ -3069,21 +3009,9 @@ class InstantiatedTemplateVisitor return TraverseSubstTemplateTypeParmTypeHelper(typeloc.getTypePtr()); } - // These do the actual work of finding the types to return. Our - // task is made easier since (at least in theory), every time we - // instantiate a template type, the instantiation has type - // SubstTemplateTypeParmTypeLoc in the AST tree. - bool VisitSubstTemplateTypeParmType(clang::SubstTemplateTypeParmType* type) { - if (CanIgnoreCurrentASTNode() || CanIgnoreType(type)) - return true; - - // Figure out how this type was actually written. clang always - // canonicalizes SubstTemplateTypeParmType, losing typedef info, etc. - const Type* actual_type = ResugarType(type); - CHECK_(actual_type && "If !CanIgnoreType(), we should be resugar-able"); - - // TODO(csilvers): whenever we report a type use here, we want to - // do an iwyu check on this type (to see if sub-types are used). + // Check whether a use of a template parameter is a full use. + bool IsTemplateTypeParmUseFullUse(const Type* type) { + const ASTNode* node = MostElaboratedAncestor(current_ast_node()); // If we're a nested-name-specifier class (the Foo in Foo::bar), // we need our full type info no matter what the context (even if @@ -3091,9 +3019,8 @@ class InstantiatedTemplateVisitor // TODO(csilvers): consider encoding this logic via // in_forward_declare_context. I think this will require changing // in_forward_declare_context to yes/no/maybe. - if (current_ast_node()->ParentIsA<NestedNameSpecifier>()) { - ReportTypeUse(CurrentLoc(), actual_type); - return Base::VisitSubstTemplateTypeParmType(type); + if (node->ParentIsA<NestedNameSpecifier>()) { + return true; } // If we're inside a typedef, we don't need our full type info -- @@ -3109,45 +3036,140 @@ class InstantiatedTemplateVisitor // to require it as well. TODO(csilvers): this doesn't really // make any sense. Who figures out we need the full type if // you do 'Foo<MyClass>::value_type m;'? - for (const ASTNode* ast_node = current_ast_node(); - ast_node != caller_ast_node_; ast_node = ast_node->parent()) { - if (ast_node->IsA<TypedefNameDecl>()) - return Base::VisitSubstTemplateTypeParmType(type); + for (const ASTNode* ast_node = node; ast_node != caller_ast_node_; + ast_node = ast_node->parent()) { + if (ast_node->IsA<TypedefNameDecl>()) { + return false; + } + if (ast_node->IsA<TemplateSpecializationType>()) { + // If we hit a template specialization node before the typedef then we + // probably still need a full-use, so stop looking. + break; + } } // sizeof(a reference type) is the same as sizeof(underlying type). // We have to handle that specially here, or else we'll say the // reference is forward-declarable, below. - if (current_ast_node()->ParentIsA<UnaryExprOrTypeTraitExpr>() && - isa<ReferenceType>(actual_type)) { - const ReferenceType* actual_reftype = cast<ReferenceType>(actual_type); - ReportTypeUse(CurrentLoc(), - actual_reftype->getPointeeTypeAsWritten().getTypePtr()); - return Base::VisitSubstTemplateTypeParmType(type); + if (node->ParentIsA<UnaryExprOrTypeTraitExpr>() && + isa<ReferenceType>(type)) { + return true; } // If we're used in a forward-declare context (MyFunc<T>() { T* t; }), // or are ourselves a pointer type (MyFunc<Myclass*>()), // we don't need to do anything: we're fine being forward-declared. - if (current_ast_node()->in_forward_declare_context()) - return Base::VisitSubstTemplateTypeParmType(type); + if (node->in_forward_declare_context()) + return false; - if (current_ast_node()->ParentIsA<PointerType>() || - current_ast_node()->ParentIsA<LValueReferenceType>() || - IsPointerOrReferenceAsWritten(actual_type)) - return Base::VisitSubstTemplateTypeParmType(type); + if (node->ParentIsA<PointerType>() || + node->ParentIsA<LValueReferenceType>() || + IsPointerOrReferenceAsWritten(type)) + return false; - // We attribute all uses in an instantiated template to the - // template's caller. - ReportTypeUse(caller_loc(), actual_type); + return true; + } - // Also report all previous explicit instantiations (declarations and - // definitions) as uses of the caller's location. - ReportExplicitInstantiations(actual_type); + // This helper is called on every use of a template argument type in an + // instantiated template. Its goal is to determine whether that use should + // constitute a full-use by the template caller, and perform other necessary + // bookkeeping. + void AnalyzeTemplateTypeParmUse(const Type* type) { + const ASTNode* node = MostElaboratedAncestor(current_ast_node()); + + // If the immediate parent node is a typedef, then register the new type as + // a new name for the template argument. + if (const TypedefNameDecl* typedef_decl = + node->GetParentAs<TypedefNameDecl>()) { + const Type* typedef_type = typedef_decl->getTypeForDecl(); + VERRS(6) << "Registering " << PrintableType(typedef_type) + << " as an alias for " << PrintableType(type) + << " in the context of this instantiation\n"; + template_argument_aliases_.emplace(typedef_type, type); + } + + // Figure out how this type was actually written. clang always + // canonicalizes SubstTemplateTypeParmType, losing typedef info, etc. + const Type* actual_type = ResugarType(type); + CHECK_(actual_type && + "Type passed to AnalyzeTemplateTypeParmUse should be resugar-able"); + + VERRS(6) << "AnalyzeTemplateTypeParmUse: type = " << PrintableType(type) + << ", actual_type = " << PrintableType(actual_type) << '\n'; + + if (!IsTemplateTypeParmUseFullUse(actual_type)) { + // Non-full uses will already have been reported when they were used as + // template arguments, so nothing to do here. + return; + } + + if (isa<ReferenceType>(actual_type)) { + // If the argument type is a reference type, then we actually care about + // the referred-to type. + const ReferenceType* actual_reftype = cast<ReferenceType>(actual_type); + type = actual_reftype->getPointeeTypeAsWritten().getTypePtr(); + } + + // At this point we know we are looking at a full-use of type. However, + // there are still two cases. If this is a template param that was written + // by the user, then we report a use of it. However, it might also be a + // parameter of some implementation detail template which just happens top + // involve the user's template parameter somehow. In this case, we need to + // recursively explore the instantiation of *that* template to see if the + // user's type is full-used therein. + + if (IsKnownTemplateParam(type)) { + // We attribute all uses in an instantiated template to the + // template's caller. + ReportTypeUse(caller_loc(), type); + + // Also report all previous explicit instantiations (declarations and + // definitions) as uses of the caller's location. + ReportExplicitInstantiations(type); + } else { + const Decl* decl = TypeToDeclAsWritten(type); + if (const auto* cts_decl = + dyn_cast<ClassTemplateSpecializationDecl>(decl)) { + if (ContainsKey(traversed_decls_, decl)) + return; // avoid recursion & repetition + traversed_decls_.insert(decl); + + VERRS(6) + << "Recursively traversing " << PrintableDecl(cts_decl) + << " which was full-used and involves a known template param\n"; + TraverseDecl(const_cast<ClassTemplateSpecializationDecl*>(cts_decl)); + } + } + } + + // Our task is made easier since (at least in theory), every time we + // instantiate a template type, the instantiation has type + // SubstTemplateTypeParmTypeLoc in the AST tree. + bool VisitSubstTemplateTypeParmType(clang::SubstTemplateTypeParmType* type) { + if (CanIgnoreCurrentASTNode() || + CanIgnoreType(type, IgnoreKind::ForExpansion)) + return true; + + AnalyzeTemplateTypeParmUse(type); return Base::VisitSubstTemplateTypeParmType(type); } + bool VisitTypedefType(clang::TypedefType* type) { + if (CanIgnoreCurrentASTNode()) + return true; + + // Typedefs of template arguments require special handling to ensure that + // we record full-uses of those arguments where appropriate. Those + // typedefs are stored in the template_argument_aliases_ map. + if (const Type* template_arg_type = + GetOrDefault(template_argument_aliases_, type, nullptr)) { + AnalyzeTemplateTypeParmUse(template_arg_type); + } + + return Base::VisitTypedefType(type); + } + bool VisitTemplateSpecializationType(TemplateSpecializationType* type) { if (CanIgnoreCurrentASTNode()) return true; @@ -3208,6 +3230,7 @@ class InstantiatedTemplateVisitor void Clear() { caller_ast_node_ = nullptr; resugar_map_.clear(); + template_argument_aliases_.clear(); traversed_decls_.clear(); nodes_to_ignore_.clear(); cache_storers_.clear(); @@ -3315,15 +3338,9 @@ class InstantiatedTemplateVisitor nodes_to_ignore_.AddAll(nodeset_getter.GetNodesBelow(daw)); } - // We need to iterate over the function. We do so even if it's - // an implicit function. - if (fn_decl->isImplicit()) { - if (!TraverseImplicitDeclHelper(const_cast<FunctionDecl*>(fn_decl))) - return false; - } else { - if (!TraverseDecl(const_cast<FunctionDecl*>(fn_decl))) - return false; - } + // We need to iterate over the function. + if (!TraverseDecl(const_cast<FunctionDecl*>(fn_decl))) + return false; // If we're a constructor, we also need to construct the entire class, // even typedefs that aren't used at construct time. Try compiling @@ -3537,6 +3554,12 @@ class InstantiatedTemplateVisitor // template-caller may or may not be responsible for. map<const Type*, const Type*> resugar_map_; + // When we see a full-use of a template argument we need to assign that full + // use to the template-caller. Sometimes those uses are hidden behind + // type aliases (typedefs). This maps those aliases to the underlying + // template arguments. + map<const Type*, const Type*> template_argument_aliases_; + // Used to avoid recursion in the *Helper() methods. set<const Decl*> traversed_decls_; @@ -3598,7 +3621,7 @@ class IwyuAstConsumer string GetSymbolAnnotation() const override { return ""; } // We are interested in all types for iwyu checking. - bool CanIgnoreType(const Type* type) const override { + bool CanIgnoreType(const Type* type, IgnoreKind) const override { return type == nullptr; } @@ -3761,6 +3784,10 @@ class IwyuAstConsumer bool VisitTagDecl(clang::TagDecl* decl) { if (CanIgnoreCurrentASTNode()) return true; + // Skip the injected class name. + if (decl->isImplicit()) + return Base::VisitTagDecl(decl); + if (IsForwardDecl(decl)) { // If we're a templated class, make sure we add the whole template. const NamedDecl* decl_to_fwd_declare = decl; @@ -3940,7 +3967,7 @@ class IwyuAstConsumer if (const TemplateSpecializationType* arg_tmpl = DynCastFrom(arg_type)) { // Special case: We are instantiating the type in the context of an // expression. Need to push the type to the AST stack explicitly. - ASTNode node(arg_tmpl, *GlobalSourceManager()); + ASTNode node(arg_tmpl); node.SetParent(current_ast_node()); instantiated_template_visitor_.ScanInstantiatedType( diff --git a/iwyu_ast_util.cc b/iwyu_ast_util.cc index b97396e..1d37a17 100644 --- a/iwyu_ast_util.cc +++ b/iwyu_ast_util.cc @@ -20,12 +20,12 @@ #include "iwyu_path_util.h" #include "iwyu_port.h" // for CHECK_ #include "iwyu_stl_util.h" -#include "iwyu_string_util.h" #include "iwyu_verrs.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/Support/Casting.h" #include "llvm/Support/raw_ostream.h" #include "clang/AST/ASTContext.h" +#include "clang/AST/ASTDumper.h" #include "clang/AST/CanonicalType.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" @@ -47,6 +47,7 @@ namespace clang { class FileEntry; } // namespace clang +using clang::ASTDumper; using clang::BlockPointerType; using clang::CXXConstructExpr; using clang::CXXConstructorDecl; @@ -110,6 +111,7 @@ using clang::TemplateSpecializationKind; using clang::TemplateSpecializationType; using clang::TranslationUnitDecl; using clang::Type; +using clang::TypeAliasTemplateDecl; using clang::TypeDecl; using clang::TypeLoc; using clang::TypedefNameDecl; @@ -122,6 +124,7 @@ using llvm::ArrayRef; using llvm::PointerUnion; using llvm::cast; using llvm::dyn_cast; +using llvm::dyn_cast_or_null; using llvm::errs; using llvm::isa; using llvm::raw_string_ostream; @@ -183,13 +186,12 @@ SourceLocation ASTNode::GetLocation() const { // locations are in a different file, then we're uncertain of our // own location. Return an invalid location. if (retval.isValid()) { - FullSourceLoc full_loc(retval, source_manager_); + clang::SourceManager& sm = *GlobalSourceManager(); + FullSourceLoc full_loc(retval, sm); const FileEntry* spelling_file = - source_manager_.getFileEntryForID( - source_manager_.getFileID(full_loc.getSpellingLoc())); + sm.getFileEntryForID(sm.getFileID(full_loc.getSpellingLoc())); const FileEntry* instantiation_file = - source_manager_.getFileEntryForID( - source_manager_.getFileID(full_loc.getExpansionLoc())); + sm.getFileEntryForID(sm.getFileID(full_loc.getExpansionLoc())); if (spelling_file != instantiation_file) return SourceLocation(); } @@ -233,6 +235,14 @@ bool IsElaborationNode(const ASTNode* ast_node) { return elaborated_type && elaborated_type->getKeyword() != clang::ETK_None; } +const ASTNode* MostElaboratedAncestor(const ASTNode* ast_node) { + // Read past elaborations like 'class' keyword or namespaces. + while (ast_node->ParentIsA<ElaboratedType>()) { + ast_node = ast_node->parent(); + } + return ast_node; +} + bool IsQualifiedNameNode(const ASTNode* ast_node) { if (ast_node == nullptr) return false; @@ -441,12 +451,14 @@ string PrintableDecl(const Decl* decl, bool terse/*=true*/) { string PrintableStmt(const Stmt* stmt) { std::string buffer; raw_string_ostream ostream(buffer); - stmt->dump(ostream, *GlobalSourceManager()); + ASTDumper dumper(ostream, /*ShowColors=*/false); + dumper.Visit(stmt); return ostream.str(); } void PrintStmt(const Stmt* stmt) { - stmt->dump(*GlobalSourceManager()); // This prints to errs(). + ASTDumper dumper(llvm::errs(), /*ShowColors=*/false); + dumper.Visit(stmt); } string PrintableType(const Type* type) { @@ -596,14 +608,14 @@ bool HasImplicitConversionCtor(const CXXRecordDecl* cxx_class) { } // C++ [class.virtual]p8: -// If the return type of D::f differs from the return type of B::f, the +// If the return type of D::f differs from the return type of B::f, the // class type in the return type of D::f shall be complete at the point of // declaration of D::f or shall be the class type D. bool HasCovariantReturnType(const CXXMethodDecl* method_decl) { QualType derived_return_type = method_decl->getReturnType(); for (CXXMethodDecl::method_iterator - it = method_decl->begin_overridden_methods(); + it = method_decl->begin_overridden_methods(); it != method_decl->end_overridden_methods(); ++it) { // There are further constraints on covariant return types as such // (e.g. parents must be related, derived override must have return type @@ -936,29 +948,6 @@ const NamedDecl* GetDefinitionAsWritten(const NamedDecl* decl) { return decl; } -bool IsDefaultNewOrDelete(const FunctionDecl* decl, - const string& decl_loc_as_quoted_include) { - // Clang will report <new> as the location of the default new and - // delete operators if <new> is included. Otherwise, it reports the - // (fake) file "<built-in>". - if (decl_loc_as_quoted_include != "<new>" && - !IsBuiltinFile(GetFileEntry(decl))) - return false; - - const string decl_name = decl->getNameAsString(); - if (!StartsWith(decl_name, "operator new") && - !StartsWith(decl_name, "operator delete")) - return false; - - // Placement-new/delete has 2 args, second is void*. The only other - // 2-arg overloads of new/delete in <new> take a const nothrow_t&. - if (decl->getNumParams() == 2 && - !decl->getParamDecl(1)->getType().isConstQualified()) - return false; - - return true; -} - bool IsFriendDecl(const Decl* decl) { // For 'template<...> friend class T', the decl will just be 'class T'. // We need to go 'up' a level to check friendship in the right place. @@ -1170,6 +1159,26 @@ const Type* RemoveSubstTemplateTypeParm(const Type* type) { return type; } +bool InvolvesTypeForWhich(const Type* type, + std::function<bool(const Type*)> pred) { + type = RemoveSubstTemplateTypeParm(type); + if (pred(type)) + return true; + const Decl* decl = TypeToDeclAsWritten(type); + if (const auto* cts_decl = + dyn_cast_or_null<ClassTemplateSpecializationDecl>(decl)) { + const TemplateArgumentList& tpl_args = cts_decl->getTemplateArgs(); + for (const TemplateArgument& tpl_arg : tpl_args.asArray()) { + if (const Type* arg_type = GetTemplateArgAsType(tpl_arg)) { + if (InvolvesTypeForWhich(arg_type, pred)) { + return true; + } + } + } + } + return false; +} + bool IsPointerOrReferenceAsWritten(const Type* type) { type = RemoveElaboration(type); return isa<PointerType>(type) || isa<LValueReferenceType>(type); @@ -1207,7 +1216,7 @@ const Type* RemovePointersAndReferences(const Type* type) { return type; } -const NamedDecl* TypeToDeclAsWritten(const Type* type) { +static const NamedDecl* TypeToDeclImpl(const Type* type, bool as_written) { // Get past all the 'class' and 'struct' prefixes, and namespaces. type = RemoveElaboration(type); @@ -1222,20 +1231,29 @@ const NamedDecl* TypeToDeclAsWritten(const Type* type) { // to keep typedefs as typedefs, so we do the record check last. // We use getAs<> when we can -- unfortunately, it only exists // for a few types so far. + const TemplateSpecializationType* template_spec_type = DynCastFrom(type); + const TemplateDecl* template_decl = + template_spec_type + ? template_spec_type->getTemplateName().getAsTemplateDecl() + : nullptr; + if (const TypedefType* typedef_type = DynCastFrom(type)) { return typedef_type->getDecl(); } else if (const InjectedClassNameType* icn_type = type->getAs<InjectedClassNameType>()) { return icn_type->getDecl(); + } else if (as_written && template_decl && + isa<TypeAliasTemplateDecl>(template_decl)) { + // A template type alias + return template_decl; } else if (const RecordType* record_type = type->getAs<RecordType>()) { return record_type->getDecl(); } else if (const TagType* tag_type = DynCastFrom(type)) { return tag_type->getDecl(); // probably just enums - } else if (const TemplateSpecializationType* template_spec_type - = DynCastFrom(type)) { + } else if (template_decl) { // A non-concrete template class, such as 'Myclass<T>' - return template_spec_type->getTemplateName().getAsTemplateDecl(); + return template_decl; } else if (const FunctionType* function_type = DynCastFrom(type)) { // TODO(csilvers): is it possible to map from fn type to fn decl? (void)function_type; @@ -1245,6 +1263,14 @@ const NamedDecl* TypeToDeclAsWritten(const Type* type) { } } +const NamedDecl* TypeToDeclAsWritten(const Type* type) { + return TypeToDeclImpl(type, /*as_written=*/true); +} + +const NamedDecl* TypeToDeclForContent(const Type* type) { + return TypeToDeclImpl(type, /*as_written=*/false); +} + const Type* RemoveReferenceAsWritten(const Type* type) { if (const LValueReferenceType* ref_type = DynCastFrom(type)) return ref_type->getPointeeType().getTypePtr(); @@ -1280,13 +1306,20 @@ GetTplTypeResugarMapForClassNoComponentTypes(const clang::Type* type) { if (!tpl_spec_type) return retval; - // Get the list of template args that apply to the decls. + // Pull the template arguments out of the specialization type. If this is + // a ClassTemplateSpecializationDecl specifically, we want to + // get the arguments therefrom to correctly handle default arguments. + const TemplateArgument* tpl_args = tpl_spec_type->getArgs(); + unsigned num_args = tpl_spec_type->getNumArgs(); + const NamedDecl* decl = TypeToDeclAsWritten(tpl_spec_type); - const ClassTemplateSpecializationDecl* tpl_decl = DynCastFrom(decl); - if (!tpl_decl) // probably because tpl_spec_type is a dependent type - return retval; - const TemplateArgumentList& tpl_args - = tpl_decl->getTemplateInstantiationArgs(); + const auto* cls_tpl_decl = dyn_cast<ClassTemplateSpecializationDecl>(decl); + if (cls_tpl_decl) { + const TemplateArgumentList& tpl_arg_list = + cls_tpl_decl->getTemplateInstantiationArgs(); + tpl_args = tpl_arg_list.data(); + num_args = tpl_arg_list.size(); + } // TemplateSpecializationType only includes explicitly specified // types in its args list, so we start with that. Note that an @@ -1302,12 +1335,13 @@ GetTplTypeResugarMapForClassNoComponentTypes(const clang::Type* type) { // (the latter are all desugared). If there's a match, update // the mapping. for (const Type* type : arg_components) { - for (unsigned i = 0; i < tpl_args.size(); ++i) { + for (unsigned i = 0; i < num_args; ++i) { if (const Type* arg_type = GetTemplateArgAsType(tpl_args[i])) { if (GetCanonicalType(type) == arg_type) { retval[arg_type] = type; VERRS(6) << "Adding a template-class type of interest: " - << PrintableType(type) << "\n"; + << PrintableType(arg_type) << " -> " << PrintableType(type) + << "\n"; explicit_args.insert(i); } } @@ -1316,7 +1350,7 @@ GetTplTypeResugarMapForClassNoComponentTypes(const clang::Type* type) { } // Now take a look at the args that were not filled explicitly. - for (unsigned i = 0; i < tpl_args.size(); ++i) { + for (unsigned i = 0; i < num_args; ++i) { if (ContainsKey(explicit_args, i)) continue; if (const Type* arg_type = GetTemplateArgAsType(tpl_args[i])) { diff --git a/iwyu_ast_util.h b/iwyu_ast_util.h index f62437b..18ddf2d 100644 --- a/iwyu_ast_util.h +++ b/iwyu_ast_util.h @@ -40,7 +40,6 @@ class ClassTemplateDecl; class Expr; class FunctionDecl; class NamedDecl; -class SourceManager; class TagDecl; class TemplateDecl; class TemplateName; @@ -72,37 +71,33 @@ class ASTNode { public: // In each case, the caller owns the object, and must guarantee it // lives for at least as long as the ASTNode object does. - ASTNode(const clang::Decl* decl, const clang::SourceManager& sm) + ASTNode(const clang::Decl* decl) : kind_(kDeclKind), as_decl_(decl), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::Stmt* stmt, const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::Stmt* stmt) : kind_(kStmtKind), as_stmt_(stmt), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::Type* type, const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::Type* type) : kind_(kTypeKind), as_type_(type), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::TypeLoc* typeloc, const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::TypeLoc* typeloc) : kind_(kTypelocKind), as_typeloc_(typeloc), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::NestedNameSpecifier* nns, const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::NestedNameSpecifier* nns) : kind_(kNNSKind), as_nns_(nns), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::NestedNameSpecifierLoc* nnsloc, - const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::NestedNameSpecifierLoc* nnsloc) : kind_(kNNSLocKind), as_nnsloc_(nnsloc), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::TemplateName* template_name, - const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::TemplateName* template_name) : kind_(kTemplateNameKind), as_template_name_(template_name), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::TemplateArgument* template_arg, - const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::TemplateArgument* template_arg) : kind_(kTemplateArgumentKind), as_template_arg_(template_arg), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } - ASTNode(const clang::TemplateArgumentLoc* template_argloc, - const clang::SourceManager& sm) + parent_(nullptr), in_fwd_decl_context_(false) { } + ASTNode(const clang::TemplateArgumentLoc* template_argloc) : kind_(kTemplateArgumentLocKind), as_template_argloc_(template_argloc), - parent_(nullptr), in_fwd_decl_context_(false), source_manager_(sm) { } + parent_(nullptr), in_fwd_decl_context_(false) { } // A 'forward-declare' context means some parent of us can be // forward-declared, which means we can be too. e.g. in @@ -327,7 +322,6 @@ class ASTNode { }; const ASTNode* parent_; bool in_fwd_decl_context_; - const clang::SourceManager& source_manager_; }; // --- Helper classes for ASTNode. @@ -376,6 +370,11 @@ class CurrentASTNodeUpdater { // uses ElaboratedType for namespaces ('ns::Foo myvar'). bool IsElaborationNode(const ASTNode* ast_node); +// Walk up to parents of the given node so long as each parent is an +// elaboration node (in the sense of IsElaborationNode). +// Can expand from a node representing 'X' to e.g. 'struct X' or 'mylib::X'. +const ASTNode* MostElaboratedAncestor(const ASTNode* ast_node); + // See if a given ast_node is a qualified name part of an ElaboratedType // node (e.g. 'class ns::Foo x', 'class ::Global x' or 'class Outer::Inner x'.) bool IsQualifiedNameNode(const ASTNode* ast_node); @@ -584,12 +583,6 @@ const clang::NamedDecl* GetInstantiatedFromDecl( // the original input. const clang::NamedDecl* GetDefinitionAsWritten(const clang::NamedDecl* decl); -// True if this decl is for default (not placement-new) -// new/delete/new[]/delete[] from <new>. The second argument -// is the quoted form of the file the decl comes from, e.g. '<new>'. -bool IsDefaultNewOrDelete(const clang::FunctionDecl* decl, - const string& decl_loc_as_quoted_include); - // Returns true if this decl is part of a friend decl. bool IsFriendDecl(const clang::Decl* decl); @@ -700,6 +693,11 @@ bool IsClassType(const clang::Type* type); // However, vector<T> is *not* converted to vector<int>. const clang::Type* RemoveSubstTemplateTypeParm(const clang::Type* type); +// Returns true if any type involved (recursively examining template +// arguments) satisfies the given predicate. +bool InvolvesTypeForWhich(const clang::Type* type, + std::function<bool(const clang::Type*)> pred); + // Returns true if type is a pointer type (pointer or reference, // looking through elaborations like 'class Foo*' (vs 'Foo*'), // but *not* following typedefs (which is why we can't just use @@ -744,6 +742,16 @@ const clang::Type* RemovePointersAndReferences(const clang::Type* type); // this function returns nullptr. const clang::NamedDecl* TypeToDeclAsWritten(const clang::Type* type); +// This is similar to TypeToDeclAsWritten, but in this case we are less +// interested in how the type was written; we want the Decl which we can +// explore the contents of, for example to determine which of its template +// arguments are used in a manner that constitutes a full use. +// +// The difference arises particularly for type aliases, where +// TypeToDeclAsWritten returns the Decl for the alias, whereas +// TypeToDeclForContent returns the underlying aliased Decl. +const clang::NamedDecl* TypeToDeclForContent(const clang::Type* type); + // Returns true if it's possible to implicitly convert a value of a // different type to 'type' via an implicit constructor. bool HasImplicitConversionConstructor(const clang::Type* type); diff --git a/iwyu_driver.cc b/iwyu_driver.cc index bd16314..42fea35 100644 --- a/iwyu_driver.cc +++ b/iwyu_driver.cc @@ -79,7 +79,7 @@ std::string GetExecutablePath(const char *Argv0) { } const char *SaveStringInSet(std::set<std::string> &SavedStrings, StringRef S) { - return SavedStrings.insert(S).first->c_str(); + return SavedStrings.insert(S.str()).first->c_str(); } void ExpandArgsFromBuf(const char *Arg, diff --git a/iwyu_globals.cc b/iwyu_globals.cc index 0f58b4d..2699896 100644 --- a/iwyu_globals.cc +++ b/iwyu_globals.cc @@ -293,7 +293,7 @@ static vector<HeaderSearchPath> ComputeHeaderSearchPaths( for (auto it = header_search->system_dir_begin(); it != header_search->system_dir_end(); ++it) { if (const DirectoryEntry* entry = it->getDir()) { - const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName())); + const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName().str())); search_path_map[path] = HeaderSearchPath::kSystemPath; } } @@ -303,7 +303,7 @@ static vector<HeaderSearchPath> ComputeHeaderSearchPaths( // search_dir_begin()/end() includes both system and user paths. // If it's a system path, it's already in the map, so everything // new is a user path. The insert only 'takes' for new entries. - const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName())); + const string path = NormalizeDirPath(MakeAbsolutePath(entry->getName().str())); search_path_map.insert(make_pair(path, HeaderSearchPath::kUserPath)); } } diff --git a/iwyu_globals.h b/iwyu_globals.h index af40256..242cdf6 100644 --- a/iwyu_globals.h +++ b/iwyu_globals.h @@ -99,7 +99,7 @@ struct CommandlineFlags { bool pch_in_code; // Treat the first seen include as a PCH. No short option. bool no_comments; // Disable 'why' comments. No short option. bool no_fwd_decls; // Disable forward declarations. - bool quoted_includes_first; // Place quoted includes first in sort order. + bool quoted_includes_first; // Place quoted includes first in sort order. bool cxx17ns; // -C: C++17 nested namespace syntax }; diff --git a/iwyu_include_picker.cc b/iwyu_include_picker.cc index 2bdf578..76f1de7 100644 --- a/iwyu_include_picker.cc +++ b/iwyu_include_picker.cc @@ -134,6 +134,7 @@ const IncludeMapEntry libc_symbol_map[] = { { "intptr_t", kPrivate, "<unistd.h>", kPublic }, { "key_t", kPrivate, "<sys/types.h>", kPublic }, { "key_t", kPrivate, "<sys/ipc.h>", kPublic }, + { "max_align_t", kPrivate, "<stddef.h>", kPublic }, { "mode_t", kPrivate, "<sys/types.h>", kPublic }, { "mode_t", kPrivate, "<sys/stat.h>", kPublic }, { "mode_t", kPrivate, "<sys/ipc.h>", kPublic }, @@ -154,6 +155,7 @@ const IncludeMapEntry libc_symbol_map[] = { { "pid_t", kPrivate, "<termios.h>", kPublic }, { "pid_t", kPrivate, "<time.h>", kPublic }, { "pid_t", kPrivate, "<utmpx.h>", kPublic }, + { "ptrdiff_t", kPrivate, "<stddef.h>", kPublic }, { "sigset_t", kPrivate, "<signal.h>", kPublic }, { "sigset_t", kPrivate, "<sys/epoll.h>", kPublic }, { "sigset_t", kPrivate, "<sys/select.h>", kPublic }, @@ -182,6 +184,8 @@ const IncludeMapEntry libc_symbol_map[] = { { "uid_t", kPrivate, "<sys/stat.h>", kPublic }, { "useconds_t", kPrivate, "<sys/types.h>", kPublic }, { "useconds_t", kPrivate, "<unistd.h>", kPublic }, + { "wchar_t", kPrivate, "<stddef.h>", kPublic }, + { "wchar_t", kPrivate, "<stdlib.h>", kPublic }, // glob.h seems to define size_t if necessary, but it should come from stddef. // It is unspecified if the cname headers provide ::size_t. // <locale.h> is the one header which defines NULL but not size_t. @@ -349,6 +353,7 @@ const IncludeMapEntry libc_include_map[] = { { "<bits/syslog-path.h>", kPrivate, "<sys/syslog.h>", kPrivate }, { "<bits/syslog.h>", kPrivate, "<sys/syslog.h>", kPrivate }, { "<bits/termios.h>", kPrivate, "<termios.h>", kPublic }, + { "<bits/time.h>", kPrivate, "<time.h>", kPublic }, { "<bits/time.h>", kPrivate, "<sys/time.h>", kPublic }, { "<bits/timerfd.h>", kPrivate, "<sys/timerfd.h>", kPublic }, { "<bits/timex.h>", kPrivate, "<sys/timex.h>", kPublic }, @@ -1595,7 +1600,7 @@ void IncludePicker::AddMappingsFromFile(const string& filename, // Add the path of the file we're currently processing // to the search path. Allows refs to be relative to referrer. - vector<string> extended_search_path = + vector<string> extended_search_path = ExtendMappingFileSearchPath(search_path, GetParentPath(absolute_path)); diff --git a/iwyu_include_picker.h b/iwyu_include_picker.h index 69b6094..5a1f1f0 100644 --- a/iwyu_include_picker.h +++ b/iwyu_include_picker.h @@ -194,10 +194,10 @@ class IncludePicker { // Adds a mapping from a one header to another, typically // from a private to a public quoted include. void AddIncludeMapping( - const string& map_from, IncludeVisibility from_visibility, + const string& map_from, IncludeVisibility from_visibility, const MappedInclude& map_to, IncludeVisibility to_visibility); - // Adds a mapping from a a symbol to a quoted include. We use this to + // Adds a mapping from a a symbol to a quoted include. We use this to // maintain mappings of documented types, e.g. // For std::map<>, include <map>. void AddSymbolMapping( diff --git a/iwyu_lexer_utils.cc b/iwyu_lexer_utils.cc index fcea2d2..648c9da 100644 --- a/iwyu_lexer_utils.cc +++ b/iwyu_lexer_utils.cc @@ -70,7 +70,7 @@ SourceLocation GetLocationAfter( string GetIncludeNameAsWritten( SourceLocation include_loc, const CharacterDataGetterInterface& data_getter) { - const string data = GetSourceTextUntilEndOfLine(include_loc, data_getter); + const string data = GetSourceTextUntilEndOfLine(include_loc, data_getter).str(); if (data.empty()) return data; string::size_type endpos = string::npos; diff --git a/iwyu_location_util.h b/iwyu_location_util.h index 3892a42..6f8cf81 100644 --- a/iwyu_location_util.h +++ b/iwyu_location_util.h @@ -89,7 +89,7 @@ bool IsInScratchSpace(clang::SourceLocation loc); inline string GetFilePath(const clang::FileEntry* file) { return (IsBuiltinFile(file) ? "<built-in>" : - NormalizeFilePath(file->getName())); + NormalizeFilePath(file->getName().str())); } //------------------------------------------------------------ diff --git a/iwyu_output.cc b/iwyu_output.cc index ca14571..e102ccb 100644 --- a/iwyu_output.cc +++ b/iwyu_output.cc @@ -168,7 +168,7 @@ string GetKindName(const clang::TagDecl* tag_decl) { if (const FakeNamedDecl* fake = FakeNamedDeclIfItIsOne(named_decl)) { return fake->kind_name(); } - return tag_decl->getKindName(); + return tag_decl->getKindName().str(); } string GetQualifiedNameAsString(const clang::NamedDecl* named_decl) { @@ -584,8 +584,8 @@ void IwyuFileInfo::AddUsingDecl(const UsingDecl* using_decl) { int start_linenum = GetLineNumber(GetInstantiationLoc(decl_lines.getBegin())); int end_linenum = GetLineNumber(GetInstantiationLoc(decl_lines.getEnd())); VERRS(6) << "Found using-decl: " - << GetFilePath(file_) << ":" - << to_string(start_linenum) << "-" << to_string(end_linenum) << ": " + << GetFilePath(file_) << ":" + << to_string(start_linenum) << "-" << to_string(end_linenum) << ": " << internal::PrintablePtr(using_decl) << internal::GetQualifiedNameAsString(using_decl) << "\n"; } @@ -678,7 +678,7 @@ void IwyuFileInfo::ReportForwardDeclareUse(SourceLocation use_loc, void IwyuFileInfo::ReportUsingDeclUse(SourceLocation use_loc, const UsingDecl* using_decl, UseFlags flags, - const char* comment) { + const char* comment) { // If accessing a symbol through a using decl in the same file that contains // the using decl, we must mark the using decl as referenced. At the end of // traversing the AST, we check to see if a using decl is unreferenced and @@ -1242,10 +1242,14 @@ void ProcessFullUse(OneUse* use, return; } // Special case for operators new/delete: Only treated as built-in if they - // are the default, non-placement versions. + // are the default, non-placement versions. This is modelled in Clang as + // 'replaceable global allocation functions': the helper method returns true + // for anything but placement-new. Users of the 'std::nothrow' and + // 'std::align_val_t' overloads already need to spell these two symbols, so + // <new> will be required for them without us doing any magic for operator new + // itself. if (const FunctionDecl* fn_decl = DynCastFrom(use->decl())) { - const string dfn_file = GetFilePath(fn_decl); - if (IsDefaultNewOrDelete(fn_decl, ConvertToQuotedInclude(dfn_file))) { + if (fn_decl->isReplaceableGlobalAllocationFunction()) { VERRS(6) << "Ignoring use of " << use->symbol_name() << " (" << use->PrintableUseLoc() << "): built-in new/delete\n"; use->set_ignore_use(); @@ -1389,14 +1393,10 @@ void CalculateIwyuForForwardDeclareUse( // If this record is defined in one of the desired_includes, mark that // fact. Also if it's defined in one of the actual_includes. - const NamedDecl* dfn = GetDefinitionForClass(use->decl()); - // If we are, ourselves, a template specialization, then the definition - // we use is not the definition of the specialization (that's us), but - // the definition of the template we're specializing. - if (spec_decl && dfn == spec_decl) - dfn = GetDefinitionForClass(spec_decl->getSpecializedTemplate()); bool dfn_is_in_desired_includes = false; bool dfn_is_in_actual_includes = false; + + const NamedDecl* dfn = GetDefinitionForClass(use->decl()); if (dfn) { vector<string> headers = GlobalIncludePicker().GetCandidateHeadersForFilepathIncludedFrom( diff --git a/iwyu_output.h b/iwyu_output.h index e274bc3..ac2c2bc 100644 --- a/iwyu_output.h +++ b/iwyu_output.h @@ -255,7 +255,7 @@ class IwyuFileInfo { UseFlags flags, const char* comment); // Called whenever a NamedDecl is accessed through a UsingDecl. - // ie: using std::swap; swap(a, b); + // ie: using std::swap; swap(a, b); void ReportUsingDeclUse(clang::SourceLocation use_loc, const clang::UsingDecl* using_decl, UseFlags flags, const char* comment); diff --git a/iwyu_path_util.cc b/iwyu_path_util.cc index ab4fc80..9987ea4 100644 --- a/iwyu_path_util.cc +++ b/iwyu_path_util.cc @@ -134,7 +134,7 @@ string NormalizeFilePath(const string& path) { std::replace(normalized.begin(), normalized.end(), '\\', '/'); #endif - return normalized.str(); + return normalized.str().str(); } string NormalizeDirPath(const string& path) { @@ -154,14 +154,14 @@ string MakeAbsolutePath(const string& path) { std::error_code error = llvm::sys::fs::make_absolute(absolute_path); CHECK_(!error); - return absolute_path.str(); + return absolute_path.str().str(); } string MakeAbsolutePath(const string& base_path, const string& relative_path) { llvm::SmallString<128> absolute_path(base_path); llvm::sys::path::append(absolute_path, relative_path); - return absolute_path.str(); + return absolute_path.str().str(); } string GetParentPath(const string& path) { diff --git a/iwyu_port.h b/iwyu_port.h index a46d585..d890575 100644 --- a/iwyu_port.h +++ b/iwyu_port.h @@ -63,8 +63,6 @@ class OstreamVoidifier { #if defined(_WIN32) -#define snprintf _snprintf - #define NOMINMAX 1 // Prevent Windows headers from redefining min/max. #include "Shlwapi.h" // for PathMatchSpecA diff --git a/iwyu_preprocessor.cc b/iwyu_preprocessor.cc index 58e7859..88b9314 100644 --- a/iwyu_preprocessor.cc +++ b/iwyu_preprocessor.cc @@ -313,7 +313,7 @@ void IwyuPreprocessorInfo::ProcessHeadernameDirectivesInFile( break; } const string filename = GetSourceTextUntilEndOfLine(current_loc, - DefaultDataGetter()); + DefaultDataGetter()).str(); // Use "" or <> based on where the file lives. string quoted_private_include; if (IsSystemIncludeFile(GetFilePath(current_loc))) @@ -332,7 +332,7 @@ void IwyuPreprocessorInfo::ProcessHeadernameDirectivesInFile( } string after_text = GetSourceTextUntilEndOfLine(current_loc, - DefaultDataGetter()); + DefaultDataGetter()).str(); const string::size_type close_brace_pos = after_text.find('}'); if (close_brace_pos == string::npos) { Warn(current_loc, "@headername directive missing a closing brace"); diff --git a/iwyu_tool.py b/iwyu_tool.py index 07210c9..a0768d6 100755 --- a/iwyu_tool.py +++ b/iwyu_tool.py @@ -267,6 +267,11 @@ class Process(object): return cls(process, outfile) +KNOWN_COMPILER_WRAPPERS=frozenset([ + "ccache" +]) + + class Invocation(object): """ Holds arguments of an IWYU invocation. """ def __init__(self, command, cwd): @@ -288,6 +293,10 @@ class Invocation(object): else: raise ValueError('Invalid compilation database entry: %s' % entry) + if command[0] in KNOWN_COMPILER_WRAPPERS: + # Remove the compiler wrapper from the command. + command = command[1:] + # Rewrite the compile command for IWYU compile_command, compile_args = command[0], command[1:] if is_msvc_driver(compile_command): diff --git a/iwyu_tool_test.py b/iwyu_tool_test.py index d0d8b59..d524dac 100755 --- a/iwyu_tool_test.py +++ b/iwyu_tool_test.py @@ -310,6 +310,19 @@ class CompilationDBTests(unittest.TestCase): self.assertEqual('/c057f113f69311e990bf54a05050d914/foobar/Test.cpp', entry['file']) + def test_unwrap_compile_command(self): + """ Wrapping compile commands should be unwrapped. """ + compilation_db = { + 'directory': '/home/user/llvm/build', + "command": "ccache cc -c test.c" + } + + invocation = iwyu_tool.Invocation.from_compile_command(compilation_db, []) + + self.assertEqual( + invocation.command, + [iwyu_tool.IWYU_EXECUTABLE, '-c', 'test.c']) + if __name__ == '__main__': unittest.main() diff --git a/iwyu_version.h b/iwyu_version.h index 2dada60..3cfbb5d 100644 --- a/iwyu_version.h +++ b/iwyu_version.h @@ -10,6 +10,6 @@ #ifndef INCLUDE_WHAT_YOU_USE_IWYU_VERSION_H_ #define INCLUDE_WHAT_YOU_USE_IWYU_VERSION_H_ -#define IWYU_VERSION_STRING "0.14" +#define IWYU_VERSION_STRING "0.15" #endif // INCLUDE_WHAT_YOU_USE_IWYU_VERSION_H_ diff --git a/more_tests/iwyu_include_picker_test.cc b/more_tests/iwyu_include_picker_test.cc index be55316..8f2b72b 100644 --- a/more_tests/iwyu_include_picker_test.cc +++ b/more_tests/iwyu_include_picker_test.cc @@ -13,7 +13,6 @@ #include "iwyu_include_picker.h" #include <stddef.h> -#include <stdio.h> #include <algorithm> #include <string> #include <vector> @@ -36,27 +35,21 @@ namespace include_what_you_use { namespace { -string IntToString(int i) { - char buf[64]; // big enough for any number - snprintf(buf, sizeof(buf), "%d", i); - return buf; -} - // Returns a string representing the first element where actual (a vector), // and expected (an array) differ, or "" if they're identical. template <size_t kCount> string VectorDiff(const string (&expected)[kCount], const vector<string>& actual) { for (int i = 0; i < std::min(kCount, actual.size()); ++i) { if (expected[i] != actual[i]) { - return ("Differ at #" + IntToString(i) + ": expected=" + expected[i] + + return ("Differ at #" + std::to_string(i) + ": expected=" + expected[i] + ", actual=" + actual[i]); } } if (kCount < actual.size()) { - return ("Differ at #" + IntToString(kCount) + + return ("Differ at #" + std::to_string(kCount) + ": expected at EOF, actual=" + actual[kCount]); } else if (actual.size() < kCount) { - return ("Differ at #" + IntToString(kCount) + ": expected=" + + return ("Differ at #" + std::to_string(kCount) + ": expected=" + expected[actual.size()] + ", actual at EOF"); } else { return ""; diff --git a/run_iwyu_tests.py b/run_iwyu_tests.py index 057ffa0..a566949 100755 --- a/run_iwyu_tests.py +++ b/run_iwyu_tests.py @@ -113,6 +113,8 @@ class OneIwyuTest(unittest.TestCase): self.Include('macro_defined_by_includer-prefix.h')], 'macro_location.cc': ['-Wno-sizeof-pointer-div'], 'ms_inline_asm.cc': ['-fms-extensions'], + 'operator_new.cc': ['-std=c++17'], + 'placement_new.cc': ['-std=c++17'], 'prefix_header_attribution.cc': [self.Include('prefix_header_attribution-d1.h')], 'prefix_header_includes_add.cc': prefix_headers, 'prefix_header_includes_keep.cc': prefix_headers, @@ -180,9 +182,11 @@ class OneIwyuTest(unittest.TestCase): 'no_fwd_decls.cc': ['.'], 'no_h_includes_cc.cc': ['.'], 'non_transitive_include.cc': ['.'], + 'operator_new.cc': ['.'], 'overloaded_class.cc': ['.'], 'pch_in_code.cc': ['.'], 'pointer_arith.cc': ['.'], + 'placement_new.cc': ['.'], 'pragma_associated.cc': ['.'], 'precomputed_tpl_args.cc': ['.'], 'prefix_header_attribution.cc': ['.'], @@ -196,6 +200,7 @@ class OneIwyuTest(unittest.TestCase): 'relative_exported_mapped_include.cc': ['tests/cxx/subdir'], 'remove_fwd_decl_when_including.cc': ['.'], 'self_include.cc': ['.'], + 'sizeof_in_template_arg.cc': ['.'], 'sizeof_reference.cc': ['.'], 'specialization_needs_decl.cc': ['.'], 'system_namespaces.cc': ['.'], diff --git a/tests/cxx/alias_template.cc b/tests/cxx/alias_template.cc index e25032b..1c9f896 100644 --- a/tests/cxx/alias_template.cc +++ b/tests/cxx/alias_template.cc @@ -11,19 +11,19 @@ #include "tests/cxx/direct.h" -template<class T> struct FullUseTemplateArg { +template<class T> struct FullUseTemplateArgInSizeof { char argument[sizeof(T)]; }; // Test that we go through alias template and handle aliased template // specialization. -template<class T> using Alias = FullUseTemplateArg<T>; +template<class T> using Alias = FullUseTemplateArgInSizeof<T>; // IWYU: IndirectClass needs a declaration // IWYU: IndirectClass is...*indirect.h Alias<IndirectClass> alias; // Test following through entire chain of aliases. -template<class T> using AliasChain1 = FullUseTemplateArg<T>; +template<class T> using AliasChain1 = FullUseTemplateArgInSizeof<T>; template<class T> using AliasChain2 = AliasChain1<T>; // IWYU: IndirectClass needs a declaration // IWYU: IndirectClass is...*indirect.h @@ -33,6 +33,25 @@ AliasChain2<IndirectClass> aliasChain; template<class T> using Pointer = T*; Pointer<int> intPtr; +template <class T> +struct FullUseTemplateArgAsVar { + T t; +}; + +// Test the used class being nested deeper in the alias +template <typename T> +using AliasNested = FullUseTemplateArgAsVar<FullUseTemplateArgAsVar<T>>; + +// IWYU: IndirectClass needs a declaration +// IWYU: IndirectClass is...*indirect.h +AliasNested<IndirectClass> aliasNested; + +template <typename T> +using AliasNested2 = FullUseTemplateArgInSizeof<FullUseTemplateArgInSizeof<T>>; +// IWYU: IndirectClass needs a declaration +// IWYU: IndirectClass is...*indirect.h +AliasNested2<IndirectClass> aliasNested2; + /**** IWYU_SUMMARY tests/cxx/alias_template.cc should add these lines: diff --git a/tests/cxx/alias_template_use-d1.h b/tests/cxx/alias_template_use-d1.h new file mode 100644 index 0000000..7c5f333 --- /dev/null +++ b/tests/cxx/alias_template_use-d1.h @@ -0,0 +1,10 @@ +//===--- alias_template_use-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 "alias_template_use-i1.h" diff --git a/tests/cxx/alias_template_use-i1.h b/tests/cxx/alias_template_use-i1.h new file mode 100644 index 0000000..bf28969 --- /dev/null +++ b/tests/cxx/alias_template_use-i1.h @@ -0,0 +1,14 @@ +//===--- alias_template_use-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. +// +//===----------------------------------------------------------------------===// + +#include "alias_template_use-i2.h" + +template<typename T> +using AliasTemplate = AliasedTemplate<T>; + diff --git a/tests/cxx/alias_template_use-i2.h b/tests/cxx/alias_template_use-i2.h new file mode 100644 index 0000000..fee40b8 --- /dev/null +++ b/tests/cxx/alias_template_use-i2.h @@ -0,0 +1,11 @@ +//===--- alias_template_use-i2.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. +// +//===----------------------------------------------------------------------===// + +template<typename T> +class AliasedTemplate {}; diff --git a/tests/cxx/alias_template_use.cc b/tests/cxx/alias_template_use.cc new file mode 100644 index 0000000..51c38bb --- /dev/null +++ b/tests/cxx/alias_template_use.cc @@ -0,0 +1,35 @@ +//===--- alias_template_use.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. +// +//===----------------------------------------------------------------------===// + +// Tests that use of template aliases is assigned to the header defining the +// alias, rather than the underlying type. + +#include "alias_template_use-d1.h" + +template<typename T> +class A { +}; + +class B { + // IWYU: AliasTemplate is...*alias_template_use-i1.h + A<AliasTemplate<int>> a; +}; + +/**** IWYU_SUMMARY + +tests/cxx/alias_template_use.cc should add these lines: +#include "alias_template_use-i1.h" + +tests/cxx/alias_template_use.cc should remove these lines: +- #include "alias_template_use-d1.h" // lines XX-XX + +The full include-list for tests/cxx/alias_template_use.cc: +#include "alias_template_use-i1.h" // for AliasTemplate + +***** IWYU_SUMMARY */ diff --git a/tests/cxx/badinc.cc b/tests/cxx/badinc.cc index 819e5c3..8c62bc0 100644 --- a/tests/cxx/badinc.cc +++ b/tests/cxx/badinc.cc @@ -388,19 +388,6 @@ template<typename T> void CallOverloadWithUsingShadowDecl(T t) { I1_NamespaceTemplateFn(t); } -template<typename T> void CallPlacementNew(T t) { - static char buffer[sizeof(t)]; - // These should all be iwyu violations here, even though we can't be - // *sure* some of these are actually placment-new until we get a - // specific type for T (at template-instantiation time). - // IWYU: operator new is...*<new> - new (&t) int; - // IWYU: operator new is...*<new> - new (buffer) T(); - // IWYU: operator new is...*<new> - new (&t) T(); -} - // This is defining a class declared in badinc-i1.h, but I think it's // correct that it's not an IWYU violation to leave out badinc-i1.h. class I1_DefinedInCc_Class { @@ -1178,8 +1165,6 @@ int main() { CallOverloadedFunctionDifferentFiles(5.0f); // This should not be an IWYU violation either: the iwyu use is in the fn. CallOverloadWithUsingShadowDecl(5); - // IWYU: I1_Class is...*badinc-i1.h - CallPlacementNew(i1_class); // Calling operator<< when the first argument is a macro. We should // still detect that operator<< is being used here, and not in the @@ -1554,28 +1539,16 @@ int main() { std::list<int>* list_ptr; list_ptr = 0; - // Make sure we only report an iwyu for <new> because of placement-new. - // We also need to check the argument to new. - int* newed_int = new int; - // IWYU: operator new is...*<new> - new(newed_int) int(4); // IWYU: std::vector is...*<vector> // IWYU: I2_Enum is...*badinc-i2.h std::vector<I2_Enum>* newed_vector // IWYU: std::vector is...*<vector> // IWYU: I2_Enum is...*badinc-i2.h = new std::vector<I2_Enum>; - // IWYU: i1_i1_classptr is...*badinc-i1.h - // IWYU: I1_Class is...*badinc-i1.h - // IWYU: kI1ConstInt is...*badinc-i1.h - // IWYU: operator new is...*<new> - new (i1_i1_classptr) I1_Class(kI1ConstInt); // IWYU: I1_Class needs a declaration // IWYU: I1_Class is...*badinc-i1.h // IWYU: kI1ConstInt is...*badinc-i1.h I1_Class* newed_i1_class_array = new I1_Class[kI1ConstInt]; - delete newed_int; - delete (((newed_int))); // TODO(csilvers): IWYU: I2_Enum is...*badinc-i2.h // IWYU: std::vector is...*<vector> delete newed_vector; @@ -1620,24 +1593,6 @@ int main() { // IWYU: I2_Class needs a declaration // IWYU: I2_Class is...*badinc-i2.h = new I1_TemplateClass<I2_Class, I1_Struct>(i1_union); - // IWYU: I1_TemplateClass is...*badinc-i1.h - // IWYU: I2_Class needs a declaration - // IWYU: I2_Class is...*badinc-i2.h - // IWYU: I1_Struct needs a declaration - char i1_templateclass_storage[sizeof(I1_TemplateClass<I2_Class, I1_Struct>)]; - // We need full type info for i1_templateclass because we never - // fwd-declare a class with default template parameters. - // IWYU: I1_TemplateClass is...*badinc-i1.h - // IWYU: I2_Class needs a declaration - // IWYU: I1_Struct needs a declaration - I1_TemplateClass<I2_Class, I1_Struct>* placement_newed_i1_template_class - // IWYU: I1_Struct needs a declaration - // IWYU: I1_Struct is...*badinc-i1.h - // IWYU: I1_TemplateClass is...*badinc-i1.h - // IWYU: I2_Class needs a declaration - // IWYU: I2_Class is...*badinc-i2.h - // IWYU: operator new is...*<new> - = new(i1_templateclass_storage) I1_TemplateClass<I2_Class, I1_Struct>(); // IWYU: I1_Class needs a declaration // IWYU: I1_Class is...*badinc-i1.h // IWYU: i1_ns::I1_NamespaceClass is...*badinc-i1.h @@ -1658,11 +1613,6 @@ int main() { // IWYU: I1_Struct is...*badinc-i1.h // IWYU: I1_TemplateClass is...*badinc-i1.h delete newed_i1_template_class_ctor; - // Make sure we handle it right when we explicitly call the dtor, as well. - // IWYU: I2_Class::~I2_Class is...*badinc-i2-inl.h - // IWYU: I1_TemplateClass is...*badinc-i1.h - // IWYU: I1_Struct is...*badinc-i1.h - placement_newed_i1_template_class->~I1_TemplateClass(); // IWYU: I1_Class is...*badinc-i1.h delete i1_class_tpl_ctor; // Check that we discover constructor/destructor locations as well. @@ -1910,7 +1860,6 @@ tests/cxx/badinc.cc should add these lines: #include <stdarg.h> #include <stddef.h> #include <list> -#include <new> #include "tests/cxx/badinc-i1.h" class D2_Class; class D2_ForwardDeclareClass; @@ -1943,12 +1892,11 @@ The full include-list for tests/cxx/badinc.cc: #include <algorithm> // for find #include <fstream> // for fstream #include <list> // for list -#include <new> // for operator new #include <string> // for basic_string, basic_string<>::iterator, operator+, string #include <typeinfo> // for type_info #include "tests/cxx/badinc-d1.h" // for D11, D1CopyClassFn, D1Function, D1_Class, D1_CopyClass, D1_Enum, D1_I1_Typedef, D1_StructPtr, D1_Subclass, D1_TemplateClass, D1_TemplateStructWithDefaultParam, MACRO_CALLING_I4_FUNCTION #include "tests/cxx/badinc-d4.h" // for D4_ClassForOperator, operator<< -#include "tests/cxx/badinc-i1.h" // for EmptyDestructorClass, H_Class::H_Class_DefinedInI1, I11, I12, I13, I1_And_I2_OverloadedFunction, I1_Base, I1_Class, I1_Class::NestedStruct, I1_ClassPtr, I1_Enum, I1_Function, I1_FunctionPtr, I1_I2_Class_Typedef, I1_MACRO_LOGGING_CLASS, I1_MACRO_SYMBOL_WITHOUT_VALUE, I1_MACRO_SYMBOL_WITH_VALUE, I1_MACRO_SYMBOL_WITH_VALUE0, I1_MACRO_SYMBOL_WITH_VALUE2, I1_ManyPtrStruct (ptr only), I1_MemberPtr, I1_NamespaceClass, I1_NamespaceStruct, I1_NamespaceTemplateFn, I1_OverloadedFunction, I1_PtrAndUseOnSameLine, I1_PtrDereferenceClass, I1_PtrDereferenceStatic, I1_PtrDereferenceStruct, I1_SiblingClass, I1_StaticMethod, I1_Struct, I1_Subclass, I1_SubclassesI2Class, I1_TemplateClass, I1_TemplateClass<>::I1_TemplateClass_int, I1_TemplateClassFwdDeclaredInD2 (ptr only), I1_TemplateFunction, I1_TemplateMethodOnlyClass, I1_TemplateSubclass, I1_Typedef, I1_TypedefOnly_Class, I1_TypedefOnly_Class<>::i, I1_Union, I1_UnnamedStruct, I1_UnusedNamespaceStruct (ptr only), I1_const_ptr, I2_OperatorDefinedInI1Class::operator<<, MACRO_CALLING_I6_FUNCTION, OperateOn, i1_GlobalFunction, i1_i1_classptr, i1_int, i1_int_global, i1_int_global2, i1_int_global2sub, i1_int_global3, i1_int_global3sub, i1_int_global4, i1_int_global4sub, i1_int_globalsub, i1_ns2, i1_ns4, i1_ns5, kI1ConstInt, operator== +#include "tests/cxx/badinc-i1.h" // for EmptyDestructorClass, H_Class::H_Class_DefinedInI1, I11, I12, I13, I1_And_I2_OverloadedFunction, I1_Base, I1_Class, I1_Class::NestedStruct, I1_ClassPtr, I1_Enum, I1_Function, I1_FunctionPtr, I1_I2_Class_Typedef, I1_MACRO_LOGGING_CLASS, I1_MACRO_SYMBOL_WITHOUT_VALUE, I1_MACRO_SYMBOL_WITH_VALUE, I1_MACRO_SYMBOL_WITH_VALUE0, I1_MACRO_SYMBOL_WITH_VALUE2, I1_ManyPtrStruct (ptr only), I1_MemberPtr, I1_NamespaceClass, I1_NamespaceStruct, I1_NamespaceTemplateFn, I1_OverloadedFunction, I1_PtrAndUseOnSameLine, I1_PtrDereferenceClass, I1_PtrDereferenceStatic, I1_PtrDereferenceStruct, I1_SiblingClass, I1_StaticMethod, I1_Struct, I1_Subclass, I1_SubclassesI2Class, I1_TemplateClass, I1_TemplateClass<>::I1_TemplateClass_int, I1_TemplateClassFwdDeclaredInD2 (ptr only), I1_TemplateFunction, I1_TemplateMethodOnlyClass, I1_TemplateSubclass, I1_Typedef, I1_TypedefOnly_Class, I1_TypedefOnly_Class<>::i, I1_Union, I1_UnnamedStruct, I1_UnusedNamespaceStruct (ptr only), I1_const_ptr, I2_OperatorDefinedInI1Class::operator<<, MACRO_CALLING_I6_FUNCTION, OperateOn, i1_GlobalFunction, i1_int, i1_int_global, i1_int_global2, i1_int_global2sub, i1_int_global3, i1_int_global3sub, i1_int_global4, i1_int_global4sub, i1_int_globalsub, i1_ns2, i1_ns4, i1_ns5, kI1ConstInt, operator== #include "tests/cxx/badinc2.c" class D2_Class; class D2_ForwardDeclareClass; diff --git a/tests/cxx/builtins_new_included.cc b/tests/cxx/builtins_new_included.cc deleted file mode 100644 index 76efc7b..0000000 --- a/tests/cxx/builtins_new_included.cc +++ /dev/null @@ -1,31 +0,0 @@ -//===--- builtins_new_included.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. -// -//===----------------------------------------------------------------------===// - -// Test that iwyu suggests the include for <new> be removed if only -// built-in functions are used. - -#include <new> - -void foo() { - char* ch = new char; - delete ch; - int* int_array = new int[10]; - delete[] int_array; -} - -/**** IWYU_SUMMARY - -tests/cxx/builtins_new_included.cc should add these lines: - -tests/cxx/builtins_new_included.cc should remove these lines: -- #include <new> // lines XX-XX - -The full include-list for tests/cxx/builtins_new_included.cc: - -***** IWYU_SUMMARY */ diff --git a/tests/cxx/operator_new.cc b/tests/cxx/operator_new.cc new file mode 100644 index 0000000..0019bf3 --- /dev/null +++ b/tests/cxx/operator_new.cc @@ -0,0 +1,86 @@ +//===--- operator_new.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. +// +//===----------------------------------------------------------------------===// + +// Test that iwyu suggests the include for <new> be removed if only +// built-in functions are used. + +#include <new> +#include "tests/cxx/direct.h" + +// The most primitive ::operator new/delete are builtins, and are basically +// wrappers around malloc. +void ExplicitOperators() { + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + IndirectClass* elem = (IndirectClass*)::operator new(sizeof(IndirectClass)); + ::operator delete(elem); + + // IWYU: IndirectClass needs a declaration + IndirectClass* arr = + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + (IndirectClass*)::operator new[](4 * sizeof(IndirectClass)); + ::operator delete[](arr); +} + +// New- and delete-expressions, unless using placement syntax, only use builtin +// operators. They're equivalent with the above, but also run ctors/dtors. +// For placement syntax, see tests/cxx/placement_new.cc +void ExpressionsBuiltinTypes() { + char* elem = new char; + delete elem; + + int* arr = new int[4]; + delete[] arr; +} + +// New- and delete-expressions with user-defined types. +void ExpressionsUserTypes() { + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + IndirectClass* elem = new IndirectClass; + // IWYU: IndirectClass is...*indirect.h + delete elem; + + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + IndirectClass* arr = new IndirectClass[4]; + // IWYU: IndirectClass is...*indirect.h + delete[] arr; +} + +// Aligned allocation uses operator new(size_t, std::align_val_t) under the +// hood in C++17, but does not require <new> to be included for it. Pre-C++17, +// the alignment is silently ignored (or unsupported if the standard library +// does not support aligned allocation). +void ImplicitAlignedAllocation() { + struct alignas(32) Aligned { + float value[8]; + }; + + Aligned* elem = new Aligned; + delete elem; + + Aligned* arr = new Aligned[10]; + delete[] arr; +} + +/**** IWYU_SUMMARY + +tests/cxx/operator_new.cc should add these lines: +#include "tests/cxx/indirect.h" + +tests/cxx/operator_new.cc should remove these lines: +- #include <new> // lines XX-XX +- #include "tests/cxx/direct.h" // lines XX-XX + +The full include-list for tests/cxx/operator_new.cc: +#include "tests/cxx/indirect.h" // for IndirectClass + +***** IWYU_SUMMARY */ diff --git a/tests/cxx/placement_new-d1.h b/tests/cxx/placement_new-d1.h new file mode 100644 index 0000000..03e4d54 --- /dev/null +++ b/tests/cxx/placement_new-d1.h @@ -0,0 +1,15 @@ +//===--- placement_new-d1.h - test input file for iwyu ---*- C++ -*--------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef INCLUDE_WHAT_YOU_USE_TESTS_CXX_PLACEMENT_NEW_D1_H_ +#define INCLUDE_WHAT_YOU_USE_TESTS_CXX_PLACEMENT_NEW_D1_H_ + +#include "tests/cxx/placement_new-i1.h" + +#endif // INCLUDE_WHAT_YOU_USE_TESTS_CXX_PLACEMENT_NEW_D1_H_ diff --git a/tests/cxx/placement_new-i1.h b/tests/cxx/placement_new-i1.h new file mode 100644 index 0000000..3f5e4fb --- /dev/null +++ b/tests/cxx/placement_new-i1.h @@ -0,0 +1,24 @@ +//===--- placement_new-i1.h - test input file for iwyu ----*- C++ -*-------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef INCLUDE_WHAT_YOU_USE_TESTS_CXX_PLACEMENT_NEW_I1_H_ +#define INCLUDE_WHAT_YOU_USE_TESTS_CXX_PLACEMENT_NEW_I1_H_ + +#include <new> + +template <class T, class U> +class ClassTemplate { + public: + ClassTemplate() = default; + + T first; + U second; +}; + +#endif // INCLUDE_WHAT_YOU_USE_TESTS_CXX_PLACEMENT_NEW_I1_H_ diff --git a/tests/cxx/placement_new.cc b/tests/cxx/placement_new.cc new file mode 100644 index 0000000..a879a4a --- /dev/null +++ b/tests/cxx/placement_new.cc @@ -0,0 +1,151 @@ +//===--- placement_new.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. +// +//===----------------------------------------------------------------------===// + +// Test that use of placement-new requires include of <new> in all the usual +// scenarios. +// +// Requires -std=c++17 on the command-line to have std::aligned_val_t available +// in <new>. + +#include "tests/cxx/direct.h" +#include "tests/cxx/placement_new-d1.h" + +// Placement new of builtin types. +void PlacementNewBuiltinType() { + // Make sure we only report a use of <new> because of placement new, not + // ordinary new-expressions. + int* newed_int = new int; + // IWYU: operator new is...*<new> + new (newed_int) int(4); + + delete newed_int; +} + +// Placement new of user-defined type. +void PlacementNewUserType() { + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + IndirectClass* icptr = new IndirectClass; + + // IWYU: IndirectClass is...*indirect.h + // IWYU: operator new is...*<new> + new (icptr) IndirectClass; + + // IWYU: IndirectClass is...*indirect.h + delete icptr; +} + +// Placement new in macro, use is attributed to the macro. +static char global_buffer[256]; +// IWYU: operator new is...*<new> +#define CONSTRUCT_GLOBAL(T) new (global_buffer) T; + +void PlacementNewInMacro() { + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + IndirectClass* a = CONSTRUCT_GLOBAL(IndirectClass); +} + +// Placement new inside a template. +template <typename T> +void PlacementNewInTemplate(T t) { + static char buffer[sizeof(t)]; + // These should all be iwyu violations here, even though we can't be + // *sure* some of these are actually placement new until we get a + // specific type for T (at template-instantiation time). + // IWYU: operator new is...*<new> + new (&t) int; + // IWYU: operator new is...*<new> + new (buffer) T(); + // IWYU: operator new is...*<new> + new (&t) T(); +} + +// Placement new when the newed type _is_ a template. +void PlacementNewOfTemplate() { + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + // IWYU: ClassTemplate is...*placement_new-i1.h + char template_storage[sizeof(ClassTemplate<IndirectClass, IndirectClass>)]; + + // IWYU: IndirectClass needs a declaration + // IWYU: ClassTemplate needs a declaration + ClassTemplate<IndirectClass, IndirectClass>* placement_newed_template = + // Need <new> because of placement new, and requires both template and + // arguments as complete types. + // + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + // IWYU: ClassTemplate is...*placement_new-i1.h + // IWYU: operator new is...*<new> + new (template_storage) ClassTemplate<IndirectClass, IndirectClass>(); + + // Make sure we handle it right when we explicitly call the dtor, as well. + // IWYU: ClassTemplate is...*placement_new-i1.h + placement_newed_template->~ClassTemplate(); +} + +// new(std::nothrow) is not strictly placement allocation, but it uses placement +// syntax to adjust exception policy. +// To use 'std::nothrow' we must include <new>, even if we don't need it for +// 'new' itself. +void NoThrow() { + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + // IWYU: std::nothrow is...*<new> + IndirectClass* elem = new (std::nothrow) IndirectClass; + // IWYU: IndirectClass is...*indirect.h + delete elem; + + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + // IWYU: std::nothrow is...*<new> + IndirectClass* arr = new (std::nothrow) IndirectClass[4]; + // IWYU: IndirectClass is...*indirect.h + delete[] arr; +} + +// new(std::align_val_t) is not strictly placement allocation, but it uses +// placement syntax to provide alignment hints. +// To use 'std::align_val_t' we must include <new>, even if we don't need it +// for 'new' itself. +// The aligned allocation mechanics are only available as of C++17. +void ExplicitAlignedAllocation() { + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + // IWYU: std::align_val_t is...*<new> + IndirectClass* elem = new (std::align_val_t(32)) IndirectClass; + // IWYU: IndirectClass is...*indirect.h + delete elem; + + // IWYU: IndirectClass needs a declaration + // IWYU: IndirectClass is...*indirect.h + // IWYU: std::align_val_t is...*<new> + IndirectClass* arr = new (std::align_val_t(32)) IndirectClass[10]; + // IWYU: IndirectClass is...*indirect.h + delete[] arr; +} + +/**** IWYU_SUMMARY + +tests/cxx/placement_new.cc should add these lines: +#include <new> +#include "tests/cxx/indirect.h" +#include "tests/cxx/placement_new-i1.h" + +tests/cxx/placement_new.cc should remove these lines: +- #include "tests/cxx/direct.h" // lines XX-XX +- #include "tests/cxx/placement_new-d1.h" // lines XX-XX + +The full include-list for tests/cxx/placement_new.cc: +#include <new> // for align_val_t, nothrow, operator new +#include "tests/cxx/indirect.h" // for IndirectClass +#include "tests/cxx/placement_new-i1.h" // for ClassTemplate + +***** IWYU_SUMMARY */ diff --git a/tests/cxx/precomputed_tpl_args.cc b/tests/cxx/precomputed_tpl_args.cc index a98a09e..cfb9e0a 100644 --- a/tests/cxx/precomputed_tpl_args.cc +++ b/tests/cxx/precomputed_tpl_args.cc @@ -61,11 +61,10 @@ std::bitset<5> bitset; // for map<T, SpecializationClass>, we should only consider T. template<typename T> class TemplatedClass { - // TODO(csilvers): IWYU: SpecializationClass is...*precomputed_tpl_args-i1.h - // TODO(csilvers): IWYU: std::less is...*precomputed_tpl_args-i1.h + // IWYU: SpecializationClass is...*precomputed_tpl_args-i1.h // IWYU: SpecializationClass needs a declaration std::map<SpecializationClass, T> t1; - // TODO(csilvers): IWYU: IndirectClass is...*precomputed_tpl_args-i1.h + // IWYU: IndirectClass is...*precomputed_tpl_args-i1.h // IWYU: IndirectClass needs a declaration std::map<T, IndirectClass> t3; }; diff --git a/tests/cxx/sizeof_in_template_arg.cc b/tests/cxx/sizeof_in_template_arg.cc new file mode 100644 index 0000000..1f555d8 --- /dev/null +++ b/tests/cxx/sizeof_in_template_arg.cc @@ -0,0 +1,41 @@ +//===--- sizeof_in_template_arg.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. +// +//===----------------------------------------------------------------------===// + +#include "tests/cxx/direct.h" + +// This verifies that using sizeof(...) means that the argument of sizeof +// doesn't count as being in a forward-declare context. In particular, when +// it's used as a template argument. + +template <unsigned long Size> +struct Storage { + char storage[Size]; +}; + +template <typename T> +struct Public { + Storage<sizeof(T)> storage; +}; + +// IWYU: IndirectClass is...*indirect.h +// IWYU: IndirectClass needs a declaration +Public<IndirectClass> p; + +/**** IWYU_SUMMARY + +tests/cxx/sizeof_in_template_arg.cc should add these lines: +#include "tests/cxx/indirect.h" + +tests/cxx/sizeof_in_template_arg.cc should remove these lines: +- #include "tests/cxx/direct.h" // lines XX-XX + +The full include-list for tests/cxx/sizeof_in_template_arg.cc: +#include "tests/cxx/indirect.h" // for IndirectClass + +***** IWYU_SUMMARY */ diff --git a/tests/cxx/specialization_needs_decl-d1.h b/tests/cxx/specialization_needs_decl-d1.h index 2b0151d..9d5eff3 100644 --- a/tests/cxx/specialization_needs_decl-d1.h +++ b/tests/cxx/specialization_needs_decl-d1.h @@ -7,6 +7,8 @@ // //===----------------------------------------------------------------------===// +#include "tests/cxx/specialization_needs_decl-i1.h" + template <typename T> struct TplStruct { }; template <> struct TplStruct<float> { }; diff --git a/tests/cxx/specialization_needs_decl-i1.h b/tests/cxx/specialization_needs_decl-i1.h new file mode 100644 index 0000000..42e8778 --- /dev/null +++ b/tests/cxx/specialization_needs_decl-i1.h @@ -0,0 +1,18 @@ +//===--- specialization_needs_decl-i1.h - test input file -------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +// This is employed to show that when the template specialization is used, the +// base template is not required in full. Issue #735. + +// Base template +template<typename> +struct Template; + +// Specialization for int +template<> struct Template<int> { int x; }; diff --git a/tests/cxx/specialization_needs_decl.cc b/tests/cxx/specialization_needs_decl.cc index 91b3e61..750cbdc 100644 --- a/tests/cxx/specialization_needs_decl.cc +++ b/tests/cxx/specialization_needs_decl.cc @@ -19,15 +19,26 @@ template<> struct TplStruct<int> { }; // the definition. template<> struct TplStruct<float>; +// Full-using a specialization requires definition of the specialization to be +// included. Not the base template. + +// IWYU: Template needs a declaration +int f(Template<int>& t) { + // IWYU: Template is...*specialization_needs_decl-i1.h + return t.x; +} + /**** IWYU_SUMMARY tests/cxx/specialization_needs_decl.cc should add these lines: +#include "tests/cxx/specialization_needs_decl-i1.h" template <typename T> struct TplStruct; tests/cxx/specialization_needs_decl.cc should remove these lines: - #include "tests/cxx/specialization_needs_decl-d1.h" // lines XX-XX The full include-list for tests/cxx/specialization_needs_decl.cc: +#include "tests/cxx/specialization_needs_decl-i1.h" // for Template template <typename T> struct TplStruct; ***** IWYU_SUMMARY */ diff --git a/tests/cxx/template_specialization.cc b/tests/cxx/template_specialization.cc index 0e37fa9..12fa374 100644 --- a/tests/cxx/template_specialization.cc +++ b/tests/cxx/template_specialization.cc @@ -11,6 +11,7 @@ // it to the right location. #include "tests/cxx/template_specialization-d1.h" +#include "tests/cxx/direct.h" template<typename T> class Foo; @@ -38,18 +39,30 @@ TplTplStruct<> tts; TplTplStruct<Foo> tts2; +template<typename T> +struct Specialized; + +template<> +// IWYU: IndirectClass is...*indirect.h +struct Specialized<int> : IndirectClass {}; + + /**** IWYU_SUMMARY tests/cxx/template_specialization.cc should add these lines: +#include "tests/cxx/indirect.h" #include "tests/cxx/template_specialization-i1.h" #include "tests/cxx/template_specialization-i2.h" tests/cxx/template_specialization.cc should remove these lines: +- #include "tests/cxx/direct.h" // lines XX-XX - #include "tests/cxx/template_specialization-d1.h" // lines XX-XX - template <typename T> class Foo; // lines XX-XX The full include-list for tests/cxx/template_specialization.cc: +#include "tests/cxx/indirect.h" // for IndirectClass #include "tests/cxx/template_specialization-i1.h" // for Foo #include "tests/cxx/template_specialization-i2.h" // for Foo +template <typename T> struct Specialized; // lines XX-XX+1 ***** IWYU_SUMMARY */ diff --git a/tests/cxx/typedef_in_template.cc b/tests/cxx/typedef_in_template.cc index 48721d7..8e27810 100644 --- a/tests/cxx/typedef_in_template.cc +++ b/tests/cxx/typedef_in_template.cc @@ -7,6 +7,7 @@ // //===----------------------------------------------------------------------===// +#include "tests/cxx/direct.h" #include "tests/cxx/typedef_in_template-d1.h" template<class T> @@ -24,34 +25,87 @@ class Container { void Declarations() { - // These do not need the full type for Class because they're template params. + // Just using Container does not need the full type for Class because there + // are only aliases made, which do not require full-uses. - // TODO: This is almost certainly wrong, see bug #431 - // We should not require the full definition of Class for passing it as a - // template argument, but we must require it when the typedef it's aliasing - // is full-used. - // The bug has instructions for how to provoke the error more obviously. + // TODO: But currently this is counted as a full-use because Class is used + // inside a template specialization (of Pair) within the definition of + // Container. + // IWYU: Class is...*typedef_in_template-i1.h + // IWYU: Class needs a declaration + Container<Class> c; + + // Full-using any of those aliases *should* require a full use of Class. + // IWYU: Class is...*typedef_in_template-i1.h // IWYU: Class needs a declaration Container<Class>::value_type vt; + // IWYU: Class is...*typedef_in_template-i1.h // IWYU: Class needs a declaration Container<Class>::pair_type pt; + // IWYU: Class is...*typedef_in_template-i1.h // IWYU: Class needs a declaration Container<Class>::alias_type at; } +// STL containers are often implemented via a complex web of type aliases and +// helper classes. Tracking uses through all these layers can be non-trivial. +// The following are some reduced examples in roughly increasing order of +// complexity which can serve as helpful test cases while debugging such +// issues. They were inspired by libstdc++'s implementation of +// std::unordered_map, but don't directly correspond to it. + +// Verify that a full-use of an alias of a template parameter is treated as a +// full-use of that parameter. +template <typename T> +struct UsesAliasedParameter { + using TAlias = T; + TAlias t; +}; + +// IWYU: IndirectClass is...*indirect.h +// IWYU: IndirectClass needs a declaration +UsesAliasedParameter<IndirectClass> a; + +// IWYU: IndirectClass is...*indirect.h +// IWYU: IndirectClass needs a declaration +UsesAliasedParameter<IndirectClass>::TAlias a2; + +// Try a more complex example, through an additional layer of indirection. +template <typename T> +struct IndirectlyUsesAliasedParameter { + using TAlias = typename UsesAliasedParameter<T>::TAlias; + TAlias t; +}; + +// IWYU: IndirectClass is...*indirect.h +// IWYU: IndirectClass needs a declaration +IndirectlyUsesAliasedParameter<IndirectClass> b; + +template <typename T> +struct NestedUseOfAliasedParameter { + using UserAlias = UsesAliasedParameter<T>; + UserAlias a; +}; + +// IWYU: IndirectClass is...*indirect.h +// IWYU: IndirectClass needs a declaration +NestedUseOfAliasedParameter<IndirectClass> c; /**** IWYU_SUMMARY tests/cxx/typedef_in_template.cc should add these lines: +#include "tests/cxx/indirect.h" #include "tests/cxx/typedef_in_template-i1.h" tests/cxx/typedef_in_template.cc should remove these lines: +- #include "tests/cxx/direct.h" // lines XX-XX - #include "tests/cxx/typedef_in_template-d1.h" // lines XX-XX The full include-list for tests/cxx/typedef_in_template.cc: -#include "tests/cxx/typedef_in_template-i1.h" // for Class (ptr only), Pair +#include "tests/cxx/indirect.h" // for IndirectClass +#include "tests/cxx/typedef_in_template-i1.h" // for Class, Pair ***** IWYU_SUMMARY */ |