Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

object_with_zone.system_clock_impl_min fails (undefined behavior) #881

Closed
jamessan opened this issue Jun 9, 2020 · 26 comments
Closed

object_with_zone.system_clock_impl_min fails (undefined behavior) #881

jamessan opened this issue Jun 9, 2020 · 26 comments

Comments

@jamessan
Copy link
Contributor

jamessan commented Jun 9, 2020

The Debian armhf build of 3.3.0 is failing the mentioned test.

Since it's using EXPECT_TRUE() instead of EXPECT_EQ(), there's not much diagnostic information in the build log (although there are a lot of warnings in the build). I was able to reproduce the issue on one of our porter systems, and changed the assertion to

EXPECT_EQ(obj.as<std::chrono::system_clock::time_point>(), v);

I was hoping that would give more clarity about why the test failed, but instead the test passes whenever I run with that change.

If it helps, I could provide the annotated assembly that's generated for that test function.

@redboltz
Copy link
Contributor

redboltz commented Jun 9, 2020

It's weird.

The error line is https://github.com/msgpack/msgpack-c/blob/cpp-3.3.0/test/object_with_zone.cpp#L1031

The function is

TEST(object_with_zone, system_clock_impl_min)
{
    std::chrono::system_clock::time_point v(std::chrono::system_clock::time_point::min());
    msgpack::zone z;
    msgpack::object obj(v, z);
    EXPECT_TRUE(obj.as<std::chrono::system_clock::time_point>() == v);
}

I'm interested in what happens if the temporary variable v2 is introduced as follows:

TEST(object_with_zone, system_clock_impl_min)
{
    std::chrono::system_clock::time_point v(std::chrono::system_clock::time_point::min());
    msgpack::zone z;
    msgpack::object obj(v, z);
    std::chrono::system_clock::time_point v2 = obj.as<std::chrono::system_clock::time_point>();
    EXPECT_TRUE(v2 == v);
}

If the error is disappeared, I guess that there is some macro and < or > evaluation issue on the target compiler.

@jamessan
Copy link
Contributor Author

jamessan commented Jun 10, 2020

I'm interested in what happens if the temporary variable v2 is introduced as follows:

That doesn't help. However, both of these changes do make the test pass:

EXPECT_EQ(obj.as<std::chrono::system_clock::time_point>(), v);
EXPECT_EQ(v2, v);

I'm re-building 3.0.1 since that successfully built when it was uploaded to Debian to see if that now fails. If so, that would seem to be a good indication that this is a compiler problem.

This is the compiler I'm running:

abel% gcc --version
gcc (Debian 9.3.0-13) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
abel% g++ --version
g++ (Debian 9.3.0-13) 9.3.0
Copyright (C) 2019 Free Software Foundation, Inc.
This is free software; see the source for copying conditions.  There is NO
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

I'll also try with clang and gcc-10.

@redboltz
Copy link
Contributor

Inserting the following code to v2 introduced one might help to get more information.

    auto nsec = std::chrono::duration_cast<std::chrono::nanoseconds>(v2 - v);
    auto sec = std::chrono::duration_cast<std::chrono::seconds>(v2 - v);
    
    std::cout << nsec.count() << std::endl;
    std::cout << sec.count() << std::endl;

@jamessan
Copy link
Contributor Author

With those changes, the failure goes away, even when keeping the original EXPECT_TRUE(... == v);.

Building with -O0 works, but not with -O1 or -O2.

This seems pretty clearly to be a compiler issue. I'll try to narrow down what optimization is causing the problem and report it to gcc.

@redboltz
Copy link
Contributor

I think that the issue is related to optimizer too.

@jamessan
Copy link
Contributor Author

@jamessan
Copy link
Contributor Author

jamessan commented Jun 23, 2020

The issue is signed overflow in the test:

$ cat foo.cc
#include <msgpack.hpp>

bool check_equality()
{
    std::chrono::system_clock::time_point v(std::chrono::system_clock::time_point::min());
    msgpack::zone z;
    msgpack::object obj(v, z);
    return obj.as<std::chrono::system_clock::time_point>() == v;
}

int main()
{
    return check_equality() ? 42 : 0;
}
$ g++ foo.cc -fsanitize=undefined -I include
$ env UBSAN_OPTIONS=print_stacktrace=1 ./a.out
/usr/include/c++/9/chrono:166:38: runtime error: signed integer overflow: -9223372037 * 1000000000 cannot be represented in type 'long int'
    #0 0x55ed6152ea17 in std::chrono::duration<long, std::ratio<1l, 1000000000l> > std::chrono::__duration_cast_impl<std::chrono::duration<long, std::ratio<1l, 1000000000l> >, std::ratio<1000000000l, 1l>, long, false, true>::__cast<long, std::ratio<1l, 1l> >(std::chrono::duration<long, std::ratio<1l, 1l> > const&) /usr/include/c++/9/chrono:166
    #1 0x55ed6152ea17 in std::enable_if<std::chrono::__is_duration<std::chrono::duration<long, std::ratio<1l, 1000000000l> > >::value, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >::type std::chrono::duration_cast<std::chrono::duration<long, std::ratio<1l, 1000000000l> >, long, std::ratio<1l, 1l> >(std::chrono::duration<long, std::ratio<1l, 1l> > const&) /usr/include/c++/9/chrono:200
    #2 0x55ed6152ea17 in std::chrono::duration<long, std::ratio<1l, 1000000000l> >::duration<long, std::ratio<1l, 1l>, void>(std::chrono::duration<long, std::ratio<1l, 1l> > const&) /usr/include/c++/9/chrono:339
    #3 0x55ed6152ea17 in msgpack::v1::adaptor::as<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >, void>::operator()(msgpack::v2::object const&) const include/msgpack/v1/adaptor/cpp11/chrono.hpp:56
    #4 0x55ed6152ea17 in std::enable_if<msgpack::v3::has_as<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > >::value, std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > >::type msgpack::v1::object::as<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > >() const include/msgpack/v1/object.hpp:1122
    #5 0x55ed6152ea17 in check_equality() /home/jamessan/src/debian.org/msgpack-c/foo.cc:8
    #6 0x55ed6152eabe in main /home/jamessan/src/debian.org/msgpack-c/foo.cc:13
    #7 0x7f266b9c1e0a in __libc_start_main ../csu/libc-start.c:308
    #8 0x55ed6152e139 in _start (/home/jamessan/src/debian.org/msgpack-c/foo+0x2139)

/usr/include/c++/9/chrono:383:8: runtime error: signed integer overflow: 9223372036709551616 + 145224192 cannot be represented in type 'long int'
    #0 0x55ed6152ea2e in std::chrono::duration<long, std::ratio<1l, 1000000000l> >::operator+=(std::chrono::duration<long, std::ratio<1l, 1000000000l> > const&) /usr/include/c++/9/chrono:383
    #1 0x55ed6152ea2e in std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >::operator+=(std::chrono::duration<long, std::ratio<1l, 1000000000l> > const&) /usr/include/c++/9/chrono:656
    #2 0x55ed6152ea2e in msgpack::v1::adaptor::as<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > >, void>::operator()(msgpack::v2::object const&) const include/msgpack/v1/adaptor/cpp11/chrono.hpp:56
    #3 0x55ed6152ea2e in std::enable_if<msgpack::v3::has_as<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > >::value, std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > >::type msgpack::v1::object::as<std::chrono::time_point<std::chrono::_V2::system_clock, std::chrono::duration<long, std::ratio<1l, 1000000000l> > > >() const include/msgpack/v1/object.hpp:1122
    #4 0x55ed6152ea2e in check_equality() /home/jamessan/src/debian.org/msgpack-c/foo.cc:8
    #5 0x55ed6152eabe in main /home/jamessan/src/debian.org/msgpack-c/foo.cc:13
    #6 0x7f266b9c1e0a in __libc_start_main ../csu/libc-start.c:308
    #7 0x55ed6152e139 in _start (/home/jamessan/src/debian.org/msgpack-c/foo+0x2139)

@jamessan jamessan reopened this Jun 23, 2020
@jamessan jamessan changed the title object_with_zone.system_clock_impl_min fails on armhf build object_with_zone.system_clock_impl_min fails on armhf build (undefined behavior) Jun 24, 2020
@jamessan
Copy link
Contributor Author

Running the test suite with -fsanitize=undefined shows a number of problems. Test log is attached.
test.txt

@redboltz
Copy link
Contributor

@jamessan , thank you for detailed reporting.
I don't have much time to fix them, so far. @ygj6 , could you handle the issues if you have time ?

@jamessan jamessan changed the title object_with_zone.system_clock_impl_min fails on armhf build (undefined behavior) object_with_zone.system_clock_impl_min fails (undefined behavior) Jun 24, 2020
@ygj6
Copy link
Collaborator

ygj6 commented Jun 29, 2020

In the attachment you uploaded, two types of issues are mentioned:

  • runtime error: null pointer passed as argument 2, which is declared to never be null

It can be fixed by checking the null pointer.
ygj6@aba3227
ygj6@259cbb2

  • runtime error: signed integer overflow: -9223372037 * 1000000000 cannot be represented in type 'long int'

I have not found a solution. I just guessed that the problem was caused by the compiler, so I need to spend some time to study it.

@redboltz
Copy link
Contributor

@ygj6 , thank you for analyzing and fixing.
I will apply your fix when you will send PRs for the fix.

I've checked the rest problem.

I inserted __LINE__ macro.

[----------] 24 tests from MSGPACK_CHRONO
[ RUN      ] MSGPACK_CHRONO.system_clock
[       OK ] MSGPACK_CHRONO.system_clock (0 ms)
[ RUN      ] MSGPACK_CHRONO.system_clock_32
[       OK ] MSGPACK_CHRONO.system_clock_32 (0 ms)
[ RUN      ] MSGPACK_CHRONO.system_clock_32_max
[       OK ] MSGPACK_CHRONO.system_clock_32_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.system_clock_64
[       OK ] MSGPACK_CHRONO.system_clock_64 (0 ms)
[ RUN      ] MSGPACK_CHRONO.system_clock_64_max
[       OK ] MSGPACK_CHRONO.system_clock_64_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.system_clock_impl_min
58
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:197:38: runtime error: signed integer overflow: -9223372037 * 1000000000 cannot be represented in type 'long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:197:38 in
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:474:8: runtime error: signed integer overflow: 145224192 + 9223372036709551616 cannot be represented in type 'long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:474:8 in
60
100
102
[       OK ] MSGPACK_CHRONO.system_clock_impl_min (0 ms)
[ RUN      ] MSGPACK_CHRONO.system_clock_impl_max
[       OK ] MSGPACK_CHRONO.system_clock_impl_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.system_clock_impl_now
[       OK ] MSGPACK_CHRONO.system_clock_impl_now (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock
[       OK ] MSGPACK_CHRONO.steady_clock (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock_32
[       OK ] MSGPACK_CHRONO.steady_clock_32 (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock_32_max
[       OK ] MSGPACK_CHRONO.steady_clock_32_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock_64
[       OK ] MSGPACK_CHRONO.steady_clock_64 (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock_64_max
[       OK ] MSGPACK_CHRONO.steady_clock_64_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock_impl_min
58
60
100
102
[       OK ] MSGPACK_CHRONO.steady_clock_impl_min (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock_impl_max
[       OK ] MSGPACK_CHRONO.steady_clock_impl_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.steady_clock_impl_now
[       OK ] MSGPACK_CHRONO.steady_clock_impl_now (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock
[       OK ] MSGPACK_CHRONO.high_resolution_clock (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock_32
[       OK ] MSGPACK_CHRONO.high_resolution_clock_32 (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock_32_max
[       OK ] MSGPACK_CHRONO.high_resolution_clock_32_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock_64
[       OK ] MSGPACK_CHRONO.high_resolution_clock_64 (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock_64_max
[       OK ] MSGPACK_CHRONO.high_resolution_clock_64_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock_impl_min
58
60
100
102
[       OK ] MSGPACK_CHRONO.high_resolution_clock_impl_min (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock_impl_max
[       OK ] MSGPACK_CHRONO.high_resolution_clock_impl_max (0 ms)
[ RUN      ] MSGPACK_CHRONO.high_resolution_clock_impl_now
[       OK ] MSGPACK_CHRONO.high_resolution_clock_impl_now (0 ms)
[----------] 24 tests from MSGPACK_CHRONO (0 ms total)

The error is reported in as

tp += std::chrono::seconds(sec);

but not reported in convert

tp += std::chrono::seconds(sec);

.

In addition, the error is only reported only system_clock. steady_clock and high_resolution_clock don't report errors.

I don't understand why the difference happens, so far.

@redboltz
Copy link
Contributor

redboltz commented Jun 29, 2020

template <typename Clock>
struct as<std::chrono::time_point<Clock>> {
    typename std::chrono::time_point<Clock> operator()(msgpack::object const& o) const {
        using duration_t = typename std::chrono::time_point<Clock>::duration;
        if(o.type != msgpack::type::EXT) { throw msgpack::type_error(); }
        if(o.via.ext.type() != -1) { throw msgpack::type_error(); }
        std::chrono::time_point<Clock> tp;
        switch(o.via.ext.size) {
        case 4: {
            uint32_t sec;
            _msgpack_load32(uint32_t, o.via.ext.data(), &sec);
            tp += std::chrono::seconds(sec);
        } break;
        case 8: {
            uint64_t value;
            _msgpack_load64(uint64_t, o.via.ext.data(), &value);
            uint32_t nanosec = static_cast<uint32_t>(value >> 34);
            uint64_t sec = value & 0x00000003ffffffffLL;
            tp += std::chrono::duration_cast<duration_t>(
                std::chrono::nanoseconds(nanosec));
            tp += std::chrono::seconds(sec);
        } break;
        case 12: {
            uint32_t nanosec;
            _msgpack_load32(uint32_t, o.via.ext.data(), &nanosec);
            int64_t sec;
            _msgpack_load64(int64_t, o.via.ext.data() + 4, &sec);
            tp += std::chrono::duration_cast<duration_t>(
                std::chrono::nanoseconds(nanosec));
            std::cout << __LINE__ << std::endl; // Line 58
            tp += std::chrono::seconds(sec);
            std::cout << __LINE__ << std::endl; // Line 60
        } break;
        default:
            throw msgpack::type_error();
        }
        return tp;
    }
};

template <typename Clock>
struct convert<std::chrono::time_point<Clock>> {
    msgpack::object const& operator()(msgpack::object const& o, std::chrono::time_point<Clock>& v) const {
        using duration_t = typename std::chrono::time_point<Clock>::duration;
        if(o.type != msgpack::type::EXT) { throw msgpack::type_error(); }
        if(o.via.ext.type() != -1) { throw msgpack::type_error(); }
        std::chrono::time_point<Clock> tp;
        switch(o.via.ext.size) {
        case 4: {
            uint32_t sec;
            _msgpack_load32(uint32_t, o.via.ext.data(), &sec);
            tp += std::chrono::seconds(sec);
            v = tp;
        } break;
        case 8: {
            uint64_t value;
            _msgpack_load64(uint64_t, o.via.ext.data(), &value);
            uint32_t nanosec = static_cast<uint32_t>(value >> 34);
            uint64_t sec = value & 0x00000003ffffffffLL;
            tp += std::chrono::duration_cast<duration_t>(
                std::chrono::nanoseconds(nanosec));
            tp += std::chrono::seconds(sec);
            v = tp;
        } break;
        case 12: {
            uint32_t nanosec;
            _msgpack_load32(uint32_t, o.via.ext.data(), &nanosec);
            int64_t sec;
            _msgpack_load64(int64_t, o.via.ext.data() + 4, &sec);
            tp += std::chrono::duration_cast<duration_t>(
                std::chrono::nanoseconds(nanosec));
            std::cout << __LINE__ << std::endl; // Line 100
            tp += std::chrono::seconds(sec);
            std::cout << __LINE__ << std::endl; // Line 102
            v = tp;
        } break;
        default:
            throw msgpack::type_error();
        }
        return o;
    }
};

@redboltz
Copy link
Contributor

The result was:

order clock memfun result
1 system as error
2 system convert no error
3 steady as no error
4 steady convert no error
5 high resolution as no error
6 high resolution convert no error

I suspect that the first one is false positive.

So I re-ordered as follwos:

TEST(MSGPACK_CHRONO, system_clock_impl_min)
{
    std::chrono::system_clock::time_point val1(std::chrono::system_clock::time_point::min());
    msgpack::sbuffer sbuf;
    msgpack::pack(sbuf, val1);

    msgpack::object_handle oh =
        msgpack::unpack(sbuf.data(), sbuf.size());

    // convert first
    std::chrono::system_clock::time_point val3;
    oh.get().convert(val3);
    EXPECT_EQ(val1, val3);

    // then as
    std::chrono::system_clock::time_point val2 = oh.get().as<std::chrono::system_clock::time_point>();
    EXPECT_EQ(val1, val2);

}

Then I got very interesting result:

order clock memfun result
1 system convert error
2 system as no error
3 steady as no error
4 steady convert no error
5 high resolution as no error
6 high resolution convert no error

If I `#if 0"ed system clock test case,

#if 0
TEST(MSGPACK_CHRONO, system_clock_impl_min)
{
    std::chrono::system_clock::time_point val1(std::chrono::system_clock::time_point::min());
    msgpack::sbuffer sbuf;
    msgpack::pack(sbuf, val1);

    msgpack::object_handle oh =
        msgpack::unpack(sbuf.data(), sbuf.size());

    std::chrono::system_clock::time_point val3;
    oh.get().convert(val3);
    EXPECT_EQ(val1, val3);


    std::chrono::system_clock::time_point val2 = oh.get().as<std::chrono::system_clock::time_point>();
    EXPECT_EQ(val1, val2);

}
#endif

then

order clock memfun result
3 steady as error
4 steady convert no error
5 high resolution as no error
6 high resolution convert no error

the first one (steady clock, as) starts reporting the error.

It seems that the error is reported the first one only.

I think that it is not msgpack-c implementation problem.

@jamessan, what do you think?

@redboltz
Copy link
Contributor

I noticed that there is a possibiliy that the first one is true positive and the rests are false negative.
We need investigating more.

@redboltz
Copy link
Contributor

I wrote the code that reproduce the same issue.
It is almost minimal.

#include <chrono>
#include <iostream>

void test() {
    using duration_t = typename std::chrono::time_point<std::chrono::system_clock>::duration;

    std::chrono::time_point<std::chrono::system_clock> tp;
    uint32_t nanosec = 145224192L;
    int64_t sec = -9223372037LL;

    std::cout << "nanosec:" << nanosec << std::endl;
    std::cout << "sec:" << sec << std::endl;

    tp += std::chrono::duration_cast<duration_t>(
        std::chrono::nanoseconds(nanosec));
    auto tmp = std::chrono::seconds(sec);
    std::cout << "before" << std::endl;
    tp += tmp;
    std::cout << "after" << std::endl;
}

int main() {
    std::cout << "first" << std::endl;
    test();
    std::cout << "second" << std::endl;
    test();
}

output

first
nanosec:145224192
sec:-9223372037
before
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:197:38: runtime error: signed integer overflow: -9223372037 * 1000000000 cannot be represented in type 'long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:197:38 in
/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:474:8: runtime error: signed integer overflow: 145224192 + 9223372036709551616 cannot be represented in type 'long'
SUMMARY: UndefinedBehaviorSanitizer: undefined-behavior /usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/10.1.0/../../../../include/c++/10.1.0/chrono:474:8 in
after
second
nanosec:145224192
sec:-9223372037
before
after

@redboltz
Copy link
Contributor

compiler library result wandbox
g++ libstdc++ error https://wandbox.org/permlink/RBsbPnrELQpvc4mT
clang++ libc++ no error https://wandbox.org/permlink/MAr3RAnyOb4V5MMD
clangg++ libstdc++ error not supported

@redboltz
Copy link
Contributor

redboltz commented Jun 29, 2020

libstdc++ chrono

    template<typename _ToDur, typename _CF, typename _CR>
      struct __duration_cast_impl<_ToDur, _CF, _CR, false, true>
      {
	template<typename _Rep, typename _Period>
	  static constexpr _ToDur
	  __cast(const duration<_Rep, _Period>& __d)
	  {
	    typedef typename _ToDur::rep			__to_rep;
	    return _ToDur(static_cast<__to_rep>(
	      static_cast<_CR>(__d.count()) * static_cast<_CR>(_CF::num))); // error happens here
	  }
      };

libc++ chrono

template <class _FromDuration, class _ToDuration, class _Period>
struct __duration_cast<_FromDuration, _ToDuration, _Period, false, true>
{
    _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
    _ToDuration operator()(const _FromDuration& __fd) const
    {
        typedef typename common_type<typename _ToDuration::rep, typename _FromDuration::rep, intmax_t>::type _Ct;
        return _ToDuration(static_cast<typename _ToDuration::rep>(
                           static_cast<_Ct>(__fd.count()) * static_cast<_Ct>(_Period::num)));
    }
};

It seems that libc++ defines _Ct as the common_type of _ToDuration::rep, _FromDuration::rep, and intmax_t.
However, libstdc++ doesn't.
In libstc++, if _CR is smaller type that __to_rep, then __d.count() and _CF::num should be casted as __to_rep.

I'm not 100% sure but that difference makes the error.

@redboltz
Copy link
Contributor

redboltz commented Jun 30, 2020

I guess that the following decrement code is something wrong.

@redboltz
Copy link
Contributor

@redboltz
Copy link
Contributor

It seems that signed integer overflow message output once is not an essential problem.
I guess that the signed integer overflow happens 3 times in the following code:
https://wandbox.org/permlink/63sPVgZoQ6Vsd06Q

MessagePack TimeStamp format is similar to std::timespec (since C++17)

According to the following Q&A, std::chrono::time_point cannot express complete data of std::timespec.
https://stackoverflow.com/questions/44269533/why-stdchronotime-point-is-not-large-enough-to-store-struct-timespec

Consider the test case again:

TEST(MSGPACK_CHRONO, system_clock_impl_min)
{
    std::chrono::system_clock::time_point val1(std::chrono::system_clock::time_point::min());
    msgpack::sbuffer sbuf;
    msgpack::pack(sbuf, val1); // maybe some data lost here

    msgpack::object_handle oh =
        msgpack::unpack(sbuf.data(), sbuf.size()); // losing accuracy is acceptable but need to avoid signed integer overflow

    std::chrono::system_clock::time_point val3;
    oh.get().convert(val3); // losing accuracy is acceptable but need to avoid signed integer overflow
    EXPECT_EQ(val1, val3);


    std::chrono::system_clock::time_point val2 = oh.get().as<std::chrono::system_clock::time_point>();
    EXPECT_EQ(val1, val2);

}

I guess that std::chrono::time_point doesn't have much bytes to express packed data.
So we need to cut some of data in convert/as function.
The current implementation make signed integer overflow. We need to avoid it.

Maybe it is related comment #886 (comment) mentioned by @LuisAyuso .

I think that we need to fix the library code to avoid signed integer overflow and accepting data accuracy loss.
In addition, the test code should be fixed.
After the library fix, EXPECT_EQ(val1, val3); no longer satisfied.

I'm not sure how to fix it. Any ideas?

@redboltz
Copy link
Contributor

I'm not sure that avoiding signed integer overflow is user(msgpack-c) responsibility or chrono(libstdc++) responsibility.

I reported https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95992 .

If it is user (msgpack-c) responsibility, how to check the value?

@redboltz
Copy link
Contributor

redboltz commented Jul 1, 2020

I got a comment https://gcc.gnu.org/bugzilla/show_bug.cgi?id=95992#c1

According to the comment, It is NOT a libstdc++ bug.
So avoiding signed integer overflow is caller (user, msgpack-c) 's responsibility.

template <typename Clock>
struct pack<std::chrono::time_point<Clock>> {
template <typename Stream>
msgpack::packer<Stream>& operator()(msgpack::packer<Stream>& o, std::chrono::time_point<Clock> const& v) const {
using duration_t = typename std::chrono::time_point<Clock>::duration;
int64_t count = static_cast<int64_t>(v.time_since_epoch().count());
int64_t nano_num =
duration_t::period::ratio::num *
(1000000000 / duration_t::period::ratio::den);
int64_t nanosec = count % (1000000000 / nano_num) * nano_num;
int64_t sec = 0;
if (nanosec < 0) {
nanosec = 1000000000 + nanosec;
--sec;
}
sec += count
* duration_t::period::ratio::num
/ duration_t::period::ratio::den;

When packing, if the value is std::chrono::system_clock::time_point::min(), then

  • count:8000000000000000
  • nanosec:8a7f200
  • sec :fffffffdda3e82fb

and convert/as process, from these values:

  • nanosec:8a7f200
  • sec :fffffffdda3e82fb

got

  • count:8000000000000000

with signed integeroverflow.

@redboltz
Copy link
Contributor

redboltz commented Jul 1, 2020

I'm digging into timestamp (msgpack spec) manipulation in msgpack-c.

It seems that
msgpack/msgpack#275
relates to singed integer overflow.

@redboltz
Copy link
Contributor

redboltz commented Jul 1, 2020

Finally, I fixed the chrono problem. #893

<-past                  epoch           future->
-------+------------------+---------------------
      min()               0

min() count is negative.
It can be separated seconds part and nanoseconds part.
MessagePack 96bit timestamp nanoseconds part is unsigned 32bit.
It is considered to a positive value.

Let's say min is -10.4 second.
The seconds part is -10.
The nanoseconds part is -400,000,000
In order to convert to MessagePack 96bit timestamp,
seconds part become -11, and nanoseconds part is 600,000,000.

When do as/convert, time_point is default constructed. Let's say the value is tp.
tp is 0 (epoch).
Then do tp += -11.
In this line, singed interger overflow is happended.

In order to fix it,
If nanosecond part is exist, and second part is negatice,
then increment second part.
Now second part becomes -10.

Then nanosecond part becomes nanosecond - 1,000,000,000.
Now nanosecond part becomes -400,000,000.

Finally, add second part and nanosecond part to tp.
tp is the original value without singned integer overflow.

@redboltz
Copy link
Contributor

redboltz commented Jul 2, 2020

Solved by #890, #891, and #893

@redboltz redboltz closed this as completed Jul 2, 2020
@jamessan
Copy link
Contributor Author

jamessan commented Jul 2, 2020

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants