Bobinas P4G
  • Login
  • Public

    • Public
    • Groups
    • Popular
    • People

Conversation

Notices

  1. Bernie (codewiz@mstdn.io)'s status on Friday, 07-Jan-2022 08:53:29 UTC Bernie Bernie

    We all know that C++ inherited unsafe semantics from C, but one would expect modern library features such as std::chrono to be carefully designed to avoid the old integer conversion bugs... right?

    Not the case: it's very easy to silently cause truncation or overflow in correct-looking code, such as:

    set_timeout(300s);

    Try to get a compile-time diagnostic or at least a run-time error for this interface:
    https://godbolt.org/z/K7fYGcx8h
    #cpp #programming

    In conversation Friday, 07-Jan-2022 08:53:29 UTC from mstdn.io permalink

    Attachments

    1. Compiler Explorer - C++ (x86-64 clang 13.0.0)
      from Matt Godbolt
      using namespace std::chrono_literals; using std::chrono::seconds; using cpu_period = std::ratio<1, 200'000'000>; // 200 MHz ticks using cpu_duration = std::chrono::duration; // wraps every 21 seconds! void set_timeout(cpu_duration d) { std::cout << d.count() << std::endl; } // These causes the templated conversion constructor to be enabled static_assert(std::ratio_divide::den == 1, "period"); static_assert(std::chrono::treat_as_floating_point::value == false, "seconds is integral"); int main() { set_timeout(5min); // Silently overflows! }
    • Tenno Seremel, La ĉiela ombro (tennoseremel@mstdn.io)'s status on Friday, 07-Jan-2022 08:55:30 UTC Tenno Seremel, La ĉiela ombro Tenno Seremel, La ĉiela ombro
      in reply to

      @codewiz Modern solutions require modern bugs :blobcatderpy:

      In conversation Friday, 07-Jan-2022 08:55:30 UTC permalink
    • Bernie (codewiz@mstdn.io)'s status on Friday, 07-Jan-2022 09:51:35 UTC Bernie Bernie
      in reply to

      This is some background info on std::chrono:
      https://www.youtube.com/watch?v=P32hvk8b13M

      At 27:00 there's a hint that one could handle overflows by just using a safeint type:

      duration<safe<int32_t>>

      I don't think it's as simple as this slide hints: what are you going to do? Throw an exception at runtime? Saturate to the maximum representable value? Call abort()?

      Constants that will certainly overflow should result in a compile-time error, just like in this case:

      uint8_t byte{666};

      #programming #cpp sucks

      In conversation Friday, 07-Jan-2022 09:51:35 UTC permalink

      Attachments

      1. CppCon 2016: Howard Hinnant “A <chrono> Tutorial"
        http://CppCon.org—Presentation Slides, PDFs, Source Code and other presenter materials are available at: https://github.com/cppcon/cppcon2016—This talk start...
    • Bernie (codewiz@mstdn.io)'s status on Friday, 07-Jan-2022 16:56:29 UTC Bernie Bernie
      in reply to
      • keadamander

      @keadamander This works with ints, but there's no warning nor copile-time check if you do the same thing with a duration:

      // All these silently overflow!
      cpu_duration d1{300s};
      cpu_duration d2 = 300s;
      cpu_duration d3(seconds{300});

      Try running my testcase in the Godbolt sandbox.

      In conversation Friday, 07-Jan-2022 16:56:29 UTC permalink
    • keadamander@mastodon.social's status on Friday, 07-Jan-2022 16:56:31 UTC keadamander keadamander
      in reply to

      @codewiz

      With most compilers (c/c++) it is possible to treat warnings as errors

      GCC : with the compiler flags -Wall and -Werror

      error: unsigned conversion from ‘int’ to ‘uint8_t’ {aka ‘unsigned char’} changes value from ‘666’ to ‘154’ [-Werror=overflow]
      17 | uint8_t x = 666;
      | ^~~
      cc1: all warnings being treated as errors

      In conversation Friday, 07-Jan-2022 16:56:31 UTC permalink
    • Bernie (codewiz@mstdn.io)'s status on Friday, 07-Jan-2022 17:49:43 UTC Bernie Bernie
      in reply to
      • keadamander

      @keadamander It's in the testcase I posted on Godbolt:
      https://godbolt.org/z/K7fYGcx8h

      In conversation Friday, 07-Jan-2022 17:49:43 UTC permalink

      Attachments

      1. Compiler Explorer - C++ (x86-64 clang 13.0.0)
        from Matt Godbolt
        using namespace std::chrono_literals; using std::chrono::seconds; using cpu_period = std::ratio<1, 200'000'000>; // 200 MHz ticks using cpu_duration = std::chrono::duration; // wraps every 21 seconds! void set_timeout(cpu_duration d) { std::cout << d.count() << std::endl; } // These causes the templated conversion constructor to be enabled static_assert(std::ratio_divide::den == 1, "period"); static_assert(std::chrono::treat_as_floating_point::value == false, "seconds is integral"); int main() { set_timeout(5min); // Silently overflows! }
    • keadamander@mastodon.social's status on Friday, 07-Jan-2022 17:49:44 UTC keadamander keadamander
      in reply to

      @codewiz

      I will use g++ for that.

      As I am still stuck in C++98:

      Where is cpu_duration class defined?

      Where can I find the definition of 300s?

      In conversation Friday, 07-Jan-2022 17:49:44 UTC permalink
    • Jeeves (jeeves@mstdn.io)'s status on Saturday, 08-Jan-2022 05:51:05 UTC Jeeves Jeeves
      in reply to

      @codewiz What would Rust do? Not very familiar with Rust but it seems like it would give you a compile error if the function were a const fn , according to https://doc.rust-lang.org/reference/const_eval.html. If it weren't a const fn, you'd get a panic in debug mode and silent overflow in release? And if it were a const fn that returned Option/Error, then you'd be safe but have to unwrap at runtime I think. Would be nice if you could configure a const fn to cause a compile error when returning None/Error.

      In conversation Saturday, 08-Jan-2022 05:51:05 UTC permalink

      Attachments

      1. No result found on File_thumbnail lookup.
        Constant Evaluation - The Rust Reference
    • Bernie (codewiz@mstdn.io)'s status on Saturday, 08-Jan-2022 06:07:01 UTC Bernie Bernie
      in reply to
      • Jeeves

      @jeeves I had this same question on my mind, and I wanted to write a testcase as soon as I find some time.

      Feel free to beat me at it, and please share your code on Godbolt!

      In conversation Saturday, 08-Jan-2022 06:07:01 UTC permalink
    • Jeeves (jeeves@mstdn.io)'s status on Saturday, 08-Jan-2022 16:01:43 UTC Jeeves Jeeves
      in reply to

      @codewiz My previous Toot was wrong so I deleted it; it turns out Rust is actually panicking at runtime, not compile time on integer overflow in a const fn: https://rust.godbolt.org/z/hvPY1YvGz

      In conversation Saturday, 08-Jan-2022 16:01:43 UTC permalink

      Attachments

      1. Compiler Explorer - Rust (rustc 1.57.0)
        from Matt Godbolt
        const fn increment(x: u8) -> u8 { x + 1 } pub fn main () { print!("blah"); print!("{}", increment(255u8)); }
    • Bernie (codewiz@mstdn.io)'s status on Sunday, 09-Jan-2022 11:26:49 UTC Bernie Bernie
      in reply to
      • namark

      @namark I just tried std::chrono::duration<boost::safe_numerics::safe<int32_t>> and immediately ran into trouble because duration_cast uses std::common_type, and oddly, Boost does not provide specializations for its safe types.

      After defining a bunch of variants, I got the code to compile with exceptions. Then I tried switching to traps (since our embedded codebase is built with -fno-exceptions), and I hit another error cascade that I'm too tired to debug 😡

      In conversation Sunday, 09-Jan-2022 11:26:49 UTC permalink
    • namark (namark@qoto.org)'s status on Sunday, 09-Jan-2022 11:26:50 UTC namark namark
      in reply to

      @codewiz exception, abort or compiler error?

      yes

      https://yewtu.be/watch?v=93Cjg42bGEw

      https://www.boost.org/doc/libs/develop/libs/safe_numerics/doc/html/index.html

      In conversation Sunday, 09-Jan-2022 11:26:50 UTC permalink
    • Bernie (codewiz@mstdn.io)'s status on Sunday, 09-Jan-2022 11:32:26 UTC Bernie Bernie
      in reply to
      • namark

      @namark Here's the code:
      https://godbolt.org/z/GssW4G3ds

      It won't compile in Godbolt because it needs Boost, and it won't compile with g++ or clang either, because of some weird error:

      In template: type 'boost::safe_numerics::trap_exception' does not provide a call operator

      In conversation Sunday, 09-Jan-2022 11:32:26 UTC permalink

      Attachments


    • Bernie (codewiz@mstdn.io)'s status on Sunday, 09-Jan-2022 11:40:25 UTC Bernie Bernie
      in reply to
      • namark

      @namark Oh wait, that's how they implemented the compile-time trap:

      // emit compile time error if this is invoked.
      struct trap_exception {
      constexpr trap_exception() = default;
      // error will occur on operator call.
      // hopefully this will display arguments
      };

      Yeah, but it barfs a pageful of impenetrable template errors leading to this! I can't impose this horror on the whole team.

      In conversation Sunday, 09-Jan-2022 11:40:25 UTC permalink
    • Bernie (codewiz@mstdn.io)'s status on Sunday, 09-Jan-2022 18:10:36 UTC Bernie Bernie
      in reply to
      • namark

      @namark Sorry if it sounded like I was shooting the messenger. I find it upsetting that modern C++ comes with a sophisticated type-safe library for time conversion, but then it actually overflows just like K&R C.

      I found an issue for adding common_type specializations for safe_numerics, but it was closed 2 years ago. I added a comment asking to re-evaluate:
      https://github.com/boostorg/safe_numerics/issues/30

      In conversation Sunday, 09-Jan-2022 18:10:36 UTC permalink

      Attachments

      1. std::common_type specialization missing · Issue #30 · boostorg/safe_numerics
        std::common_type is used in a variety of generic libraries, including std::chrono. Without a specialization for safes one cannot use the safe wrappers e.g. as a representation for std::chr...
    • namark (namark@qoto.org)'s status on Sunday, 09-Jan-2022 18:10:37 UTC namark namark
      in reply to

      @codewiz you seemed to have a problem with the concept so that was an explanation and a proof of concept implementation. Pleasantly surprised it worked at all! If you care about it you should participate, cause as far as I know that is the most developed of any such concepts:
      https://github.com/boostorg/safe_numerics

      common_type trait sounds like a misnomer, but I imagine chrono didn't have anything better to use. safe_numeric may provide alternatives on that front, though it will likely never be standardized in it's current form. It's much more likely for a more generic numeric library to get in, that would also handle arbitrary precision and provide the safe_numerics functionality as its subset, and probably not called "safe" cause that's not a very good name either.

      In conversation Sunday, 09-Jan-2022 18:10:37 UTC permalink

      Attachments


    • Bernie (codewiz@mstdn.io)'s status on Sunday, 09-Jan-2022 19:14:02 UTC Bernie Bernie
      in reply to
      • Jeeves

      @jeeves Ok, overflowing in constant expressions is an error also with C++, but only with signed integers:
      https://rust.godbolt.org/z/e947Pq8s6

      In conversation Sunday, 09-Jan-2022 19:14:02 UTC permalink

      Attachments

      1. Compiler Explorer - C++ (x86-64 gcc (trunk))
        from Matt Godbolt
        // Error due to signed overflow in constant expression using T = int; // But it just works with unsigned types :-( // using T = unsigned; consteval T increment(T x) { return x + 1; } int main() { constexpr T X = increment(std::numeric_limits::max()); std::cout << "x + 1 = " << X << '\n'; }
    • Jeeves (jeeves@mstdn.io)'s status on Sunday, 09-Jan-2022 19:14:03 UTC Jeeves Jeeves
      in reply to

      @codewiz I got it to error out at compile time: https://rust.godbolt.org/z/qhhTxb8hr. Had to change from calling the const fn in the argument list of println!() to first assigning the result of the const fn to a const variable, then calling println!() with that variable, so it would evaluate the const fn in a const context (defined at https://doc.rust-lang.org/reference/const_eval.html#const-context).

      In conversation Sunday, 09-Jan-2022 19:14:03 UTC permalink

      Attachments

      1. Compiler Explorer - Rust (rustc 1.57.0)
        from Matt Godbolt
        const fn increment(x: u8) -> u8 { x + 1 } pub fn main () { print!("blah"); const X: u8 = increment(255u8); print!("{}", X); }
      2. No result found on File_thumbnail lookup.
        Constant Evaluation - The Rust Reference
    • Bernie (codewiz@mstdn.io)'s status on Monday, 17-Jan-2022 02:32:39 UTC Bernie Bernie
      in reply to
      • Jeeves

      @jeeves Followup on the unnecessarily hard problem of getting compile-time errors for overflows in conversions between std::chrono::duration constants:
      https://godbolt.org/z/7E9MMMMfz

      TL;DR: too complicated and unergonomic until C++ adds constexpr function parameters.

      #cpp #programming

      In conversation Monday, 17-Jan-2022 02:32:39 UTC permalink

      Attachments


    • Jeeves (jeeves@mstdn.io)'s status on Monday, 17-Jan-2022 03:47:12 UTC Jeeves Jeeves
      in reply to

      @codewiz could you have the conversion function return the equivalent of Rust's Result type and create a STATIC_UNWRAP macro that hides the template grossness?

      In conversation Monday, 17-Jan-2022 03:47:12 UTC permalink
    • Bernie (codewiz@mstdn.io)'s status on Monday, 17-Jan-2022 08:01:09 UTC Bernie Bernie
      in reply to
      • Jeeves

      @jeeves Not feasible in C++ for cases like initializing global constants and passing durations to constructors (without using exceptions!)

      In conversation Monday, 17-Jan-2022 08:01:09 UTC permalink
    • Bernie (codewiz@mstdn.io)'s status on Sunday, 23-Jan-2022 20:06:15 UTC Bernie Bernie
      in reply to
      • Jeeves

      I wanted to see if I could get better compile-time overflow checks with Rust, but the limitations are similar: arguments of const functions are not themselves compile-time constants that you could check with assert!().

      So this is what I could come up with:
      https://godbolt.org/z/5zf1earKT

      @jeeves #Rust #cpp #programming

      In conversation Sunday, 23-Jan-2022 20:06:15 UTC permalink

      Attachments


    • Bernie (codewiz@mstdn.io)'s status on Sunday, 23-Jan-2022 20:11:29 UTC Bernie Bernie
      in reply to
      • Jeeves

      Even in Rust nightly, support for constant evaluation is still very limited compared to C++20's:
      https://doc.rust-lang.org/reference/items/generics.html

      At this time, you can't even pass float parameters or enums, let alone structs.

      There's ongoing work to expand the capabilities of constant evaluation in Rust, but it will take years before they can close the gap with C++, which is not standing still:
      http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2022/#mailing2022-01

      @jeeves #cpp #rust #programming

      In conversation Sunday, 23-Jan-2022 20:11:29 UTC permalink

      Attachments


      1. ISO/IEC JTC1/SC22/WG21 - Papers 2022
    • Bernie (codewiz@mstdn.io)'s status on Wednesday, 02-Feb-2022 09:38:40 UTC Bernie Bernie
      in reply to
      • namark

      @namark Returning a wrapper type that's only implicitly convertible when it fits is possible only if each literal creates a different type with the value encoded in a non-type argument (like std::ratio<N,M>).

      Perhaps this is doable with user-defined literals by declaring a templated operator"" which returns auto.

      In conversation Wednesday, 02-Feb-2022 09:38:40 UTC permalink
    • namark (namark@qoto.org)'s status on Wednesday, 02-Feb-2022 09:38:41 UTC namark namark
      in reply to

      @codewiz keeping in mind that it's still a half measure, which won't save your from many potential pitfalls. For anything non trivial you'd still need to do a lot of mental (or pen and paper) calculations to make sure there is no overflow, so literals would be the least of your concerns on that front.

      In conversation Wednesday, 02-Feb-2022 09:38:41 UTC permalink
    • namark (namark@qoto.org)'s status on Wednesday, 02-Feb-2022 09:38:43 UTC namark namark
      in reply to

      @codewiz another thing you could do is define and use your own bound checked literals of different sizes. Here is an example for just plain integers
      https://git.sr.ht/~namark/libsimple_support/tree/master/item/source/simple/support/int_literals.hpp

      If you stick with just one duration type throughout the codebase that would be all you need, otherwise if you want to mix and match duration of different granularity and size, your custom literals can return a wrapper type that would only be implicitly convertible to durations that they can fit in.

      In conversation Wednesday, 02-Feb-2022 09:38:43 UTC permalink

      Attachments


    • namark (namark@qoto.org)'s status on Wednesday, 02-Feb-2022 09:38:44 UTC namark namark
      in reply to

      @codewiz the main point is that it's a nuanced issue out of scope for chrono. Any halfway solution would achieve very little and lower the quality of the library. The arithmetic bound checking is universally applicable, so it would be kind of silly to have something for durations that you couldn't use for other types. Also imposing any sort of compile-time, run-time or maintenance overhead any such checks would bring is nonsensical in a world where everyone in every other application of arithmetic is perfectly happy to just use a type that is assumed to be "large enough" to represent any value. Most just want to use a 64 bit int, and forget it can overflow, even at nanosecond precision, and some would vehemently argue that even on systems that don't have wide enough words, it should just be emulated in software.

      In conversation Wednesday, 02-Feb-2022 09:38:44 UTC permalink

Feeds

  • Activity Streams
  • RSS 2.0
  • Atom
  • Help
  • About
  • FAQ
  • Privacy
  • Source
  • Version
  • Contact

Bobinas P4G is a social network. It runs on GNU social, version 2.0.1-beta0, available under the GNU Affero General Public License.

Creative Commons Attribution 3.0 All Bobinas P4G content and data are available under the Creative Commons Attribution 3.0 license.