llvm / llvm-project

The LLVM Project is a collection of modular and reusable compiler and toolchain technologies.
http://llvm.org
Other
29.13k stars 12.01k forks source link

Clang hangs in template instantiation after diagnosing C++11 narrowing in NTTP. #26231

Open llvmbot opened 8 years ago

llvmbot commented 8 years ago
Bugzilla Link 25856
Version trunk
OS All
Attachments reproducer.cpp
Reporter LLVM Bugzilla Contributor
CC @DougGregor,@zygoloid

Extended Description

The attached reproducer causes clang to hang after in instantiates the __make template with -3. Clang will emit a C++11 narrowing error before silently hanging. My guess is that clang trys to instantiate the base class of __parity with the converted value 4294967293.

metaprogramming often uses an integer NTTP to control the number of instantiations. If clang instantiates a template with a narrowed NTTP it could be in for a long ride.

I would be willing to work on a fix if somebody could offer direction on the solution.

rnk commented 2 years ago

mentioned in issue llvm/llvm-project#26230

rnk commented 8 years ago

Bug llvm/llvm-project#26230 has been marked as a duplicate of this bug.

wheatman commented 9 months ago

This still happens with post 17 trunk(a2d68b4bece54bcfa7bfde1e80058ab19b6d1775) https://godbolt.org/z/cbehKnEqa

code

// clang++ -std=c++14 test.cpp
typedef unsigned  long size_t;

template<class _Tp, _Tp... _Ip>
struct  integer_sequence
{
};

namespace __detail {

template<typename _Tp, size_t ..._Extra> struct __repeat;
template<typename _Tp, _Tp ..._Np, size_t ..._Extra> struct __repeat<integer_sequence<_Tp, _Np...>, _Extra...> {
  typedef integer_sequence<_Tp,
                           _Np...,
                           sizeof...(_Np) + _Np...,
                           2 * sizeof...(_Np) + _Np...,
                           3 * sizeof...(_Np) + _Np...,
                           4 * sizeof...(_Np) + _Np...,
                           5 * sizeof...(_Np) + _Np...,
                           6 * sizeof...(_Np) + _Np...,
                           7 * sizeof...(_Np) + _Np...,
                           _Extra...> type;
};

template<size_t _Np> struct __parity;
template<size_t _Np> struct __make : __parity<_Np % 8>::template __pmake<_Np> {};

template<> struct __make<0> { typedef integer_sequence<size_t> type; };
template<> struct __make<1> { typedef integer_sequence<size_t, 0> type; };
template<> struct __make<2> { typedef integer_sequence<size_t, 0, 1> type; };
template<> struct __make<3> { typedef integer_sequence<size_t, 0, 1, 2> type; };
template<> struct __make<4> { typedef integer_sequence<size_t, 0, 1, 2, 3> type; };
template<> struct __make<5> { typedef integer_sequence<size_t, 0, 1, 2, 3, 4> type; };
template<> struct __make<6> { typedef integer_sequence<size_t, 0, 1, 2, 3, 4, 5> type; };
template<> struct __make<7> { typedef integer_sequence<size_t, 0, 1, 2, 3, 4, 5, 6> type; };

template<> struct __parity<0> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type> {}; };
template<> struct __parity<1> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 1> {}; };
template<> struct __parity<2> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 2, _Np - 1> {}; };
template<> struct __parity<3> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<4> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<5> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<6> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };
template<> struct __parity<7> { template<size_t _Np> struct __pmake : __repeat<typename __make<_Np / 8>::type, _Np - 7, _Np - 6, _Np - 5, _Np - 4, _Np - 3, _Np - 2, _Np - 1> {}; };

template<typename _Tp, typename _Up> struct __convert {
  template<typename> struct __result;
  template<_Tp ..._Np> struct __result<integer_sequence<_Tp, _Np...> > { typedef integer_sequence<_Up, _Np...> type; };
};
template<typename _Tp> struct __convert<_Tp, _Tp> { template<typename _Up> struct __result { typedef _Up type; }; };

}

template<typename _Tp, _Tp _Np> using __make_integer_sequence_unchecked =
  typename __detail::__convert<size_t, _Tp>::template __result<typename __detail::__make<_Np>::type>::type;

template <class _Tp, _Tp _Ep>
struct make_integer_sequence
{
    // Clang hangs if you put the typedef before the assert.
    typedef __make_integer_sequence_unchecked<_Tp, _Ep> type;
    static_assert(0 <= _Ep, "std::make_integer_sequence must have a non-negative sequence length");
    // GCC fails if you put it after...
    // and my IDE hangs whenever I touch it.
};

int main() {
    typedef make_integer_sequence<int, -3>::type MakeSeqT;
}

output

<source>:55:90: error: non-type template argument evaluates to -3, which cannot be narrowed to type 'size_t' (aka 'unsigned long') [-Wc++11-narrowing]
   55 |   typename __detail::__convert<size_t, _Tp>::template __result<typename __detail::__make<_Np>::type>::type;
      |                                                                                          ^
<source>:61:13: note: in instantiation of template type alias '__make_integer_sequence_unchecked' requested here
   61 |     typedef __make_integer_sequence_unchecked<_Tp, _Ep> type;
      |             ^
<source>:69:13: note: in instantiation of template class 'make_integer_sequence<int, -3>' requested here
   69 |     typedef make_integer_sequence<int, -3>::type MakeSeqT;
      |             ^
Killed - processing time exceeded
Program terminated with signal: SIGKILL
Compiler returned: 143
shafik commented 9 months ago

So if we use a really large number as opposed to -3 we also get the same result sans diagnostic: https://godbolt.org/z/xcezdMd96

Likely what is going on is the instiations is happening even though we obtain a diagnostic and the recursion is too deep. I am not sure why we don't stop because we have done too many steps.

shafik commented 9 months ago

The diagnostic is happening in BuildConvertedConstantExpression(...) and it looks like it then goes on to evaluate the expression further. From what I can see this is expected behavior that the narrowing diagnostic does not halt the evaluation.

CC @erichkeane

llvmbot commented 9 months ago

@llvm/issue-subscribers-clang-frontend

Author: None (llvmbot)

| | | | --- | --- | | Bugzilla Link | [25856](https://llvm.org/bz25856) | | Version | trunk | | OS | All | | Attachments | [reproducer.cpp](https://user-images.githubusercontent.com/60944935/143752637-7ec963c8-6127-4fac-b159-ff113d8591fb.gz) | | Reporter | LLVM Bugzilla Contributor | | CC | @DougGregor,@zygoloid | ## Extended Description The attached reproducer causes clang to hang after in instantiates the `__make` template with `-3`. Clang will emit a C++11 narrowing error before silently hanging. My guess is that clang trys to instantiate the base class of `__parity` with the converted value 4294967293. metaprogramming often uses an integer NTTP to control the number of instantiations. If clang instantiates a template with a narrowed NTTP it could be in for a long ride. I would be willing to work on a fix if somebody could offer direction on the solution.
erichkeane commented 9 months ago

So if we use a really large number as opposed to -3 we also get the same result sans diagnostic: https://godbolt.org/z/xcezdMd96

Likely what is going on is the instiations is happening even though we obtain a diagnostic and the recursion is too deep. I am not sure why we don't stop because we have done too many steps.

The diagnostic is just a warning-as-error, so we NEED to continue with it (or at least be able to, since there might be a -Wno flag).

Instantiating the templates are really expensive, so we are probably just stuck in a really long loop. It should finish EVENTUALLY, but we're probably exceeding an imp-limits here.

This is why we have the __make_integer_seq builtin, which does a much better job in these large sizes.