GithubHelp home page GithubHelp logo

Comments (10)

bitwalker avatar bitwalker commented on July 18, 2024 2

Interesting - I wonder what happened in OTP 26 to break things, I'll take a look at this ASAP

from uniq.

josevalim avatar josevalim commented on July 18, 2024 1

Thanks, reported upstream here: erlang/otp#7469

from uniq.

alappe avatar alappe commented on July 18, 2024

I can confirm the issue with OTP v26 and v0.6.0 still exists:

  1) test can autogenerate primary keys (Uniq.Ecto.Test)
     test/ecto_test.exs:47
     match (=) failed
     code:  assert {:ok, %UUID{version: 4}} = UUID.parse(uuid)
     left:  {:ok, %Uniq.UUID{version: 4}}
     right: {:error, {:invalid_format, <<3>>, 2, 6, 14}}
     stacktrace:
       test/ecto_test.exs:52: (test)

.

  2) test parsing can parse version 1 (Uniq.Test)
     test/uniq_test.exs:47
     match (=) failed
     The following variables were pinned:
       version = 1
     code:  assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
     left:  {:ok, %Uniq.UUID{format: :raw, version: ^version}}
     right: {:error, {:invalid_format, <<226>>, 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:207: Uniq.Test.parse/2
       test/uniq_test.exs:48: (test)

...

  3) doctest Uniq.UUID.parse/1 (3) (Uniq.Test)
     test/uniq_test.exs:6
     match (=) failed
     code:  {:ok, uuid} = Uniq.UUID.parse("f81d4fae-7dec-11d0-a765-00a0c91e6bf6")
     left:  {:ok, uuid}
     right: {:error, {:invalid_format, <<157>>, 2, 6, 14}}
     stacktrace:
       (for doctest at) lib/uuid.ex:443: (test)

...

  4) doctest Uniq.UUID.info/2 (2) (Uniq.Test)
     test/uniq_test.exs:6
     Doctest failed
     doctest:
       iex> Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf")
       {:ok, %Uniq.UUID{
        format: :default,
        version: 4,
        variant: <<2::2>>,
        time: 326283406408022248,
        seq: 790,
        node: <<129, 224, 137, 184, 194, 207>>,
        bytes: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
       }}
     code:  Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf") === {:ok, %Uniq.UUID{
             format: :default,
             version: 4,
             variant: <<2::2>>,
             time: 326283406408022248,
             seq: 790,
             node: <<129, 224, 137, 184, 194, 207>>,
             bytes: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
            }}
     left:  {:error, {:invalid_format, "\f", 2, 6, 14}}
     right: {:ok, #UUIDv4<870df8e8-3107-4487-8316-81e089b8c2cf>}
     stacktrace:
       lib/uuid.ex:371: Uniq.UUID (module)

.

  5) test parsing can parse version 5 (Uniq.Test)
     test/uniq_test.exs:59
     match (=) failed
     The following variables were pinned:
       version = 5
     code:  assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
     left:  {:ok, %Uniq.UUID{format: :raw, version: ^version}}
     right: {:error, {:invalid_format, "_", 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:207: Uniq.Test.parse/2
       test/uniq_test.exs:60: (test)



  6) doctest Uniq.UUID.info/2 (1) (Uniq.Test)
     test/uniq_test.exs:6
     Doctest failed
     doctest:
       iex> Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf", :keyword)
       {:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf",
        binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
        type: :default,
        version: 4,
        variant: :rfc4122]}
     code:  Uniq.UUID.info("870df8e8-3107-4487-8316-81e089b8c2cf", :keyword) === {:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf",
             binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>,
             type: :default,
             version: 4,
             variant: :rfc4122]}
     left:  {:error, {:invalid_format, "\f", 2, 6, 14}}
     right: {:ok, [uuid: "870df8e8-3107-4487-8316-81e089b8c2cf", binary: <<135, 13, 248, 232, 49, 7, 68, 135, 131, 22, 129, 224, 137, 184, 194, 207>>, type: :default, version: 4, variant: :rfc4122]}
     stacktrace:
       lib/uuid.ex:364: Uniq.UUID (module)

.

  7) test generating can generate version 1 (Uniq.Test)
     test/uniq_test.exs:139
     match (=) failed
     code:  assert {:ok, %UUID{format: :default, version: 1}} = UUID.parse(default)
     left:  {:ok, %Uniq.UUID{format: :default, version: 1}}
     right: {:error, {:invalid_format, <<2>>, 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:143: (test)

....

  8) test generating can generate version 3 (Uniq.Test)
     test/uniq_test.exs:147
     match (=) failed
     code:  assert {:ok, %UUID{format: :default, version: 3}} = UUID.parse(default)
     left:  {:ok, %Uniq.UUID{format: :default, version: 3}}
     right: {:error, {:invalid_format, "W", 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:154: (test)



  9) property parsing can parse any 32-byte hex string which represents a valid uuid (Uniq.Test)
     test/uniq_test.exs:83
     Failed with generated values (after 0 successful runs):
     
         * Clause:    {version, variant, uuid} <- valid_uuid(:hex)
           Generated: {4, <<7::size(3)>>, "0000000000004000E000000000000000"}
     
     match (=) failed
     The following variables were pinned:
       version = 4
       variant = <<7::size(3)>>
     code:  assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid)
     left:  {:ok, %Uniq.UUID{version: ^version, variant: ^variant}}
     right: {:error, {:invalid_format, <<0>>, 3, 5, 13}}
     stacktrace:
       test/uniq_test.exs:85: anonymous fn/4 in Uniq.Test."property parsing can parse any 32-byte hex string which represents a valid uuid"/1
       (stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6
       (stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7
       test/uniq_test.exs:84: (test)



 10) property parsing can parse any 22-byte base64-encoded string which represents a valid uuid (Uniq.Test)
     test/uniq_test.exs:95
     Failed with generated values (after 0 successful runs):
     
         * Clause:    {version, variant, uuid} <- valid_uuid(:slug)
           Generated: {4, <<7::size(3)>>, "AAAAAAAAQADgAAAAAAAAAA"}
     
     match (=) failed
     The following variables were pinned:
       version = 4
       variant = <<7::size(3)>>
     code:  assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid)
     left:  {:ok, %Uniq.UUID{version: ^version, variant: ^variant}}
     right: {:error, {:invalid_format, <<0>>, 3, 5, 13}}
     stacktrace:
       test/uniq_test.exs:97: anonymous fn/4 in Uniq.Test."property parsing can parse any 22-byte base64-encoded string which represents a valid uuid"/1
       (stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6
       (stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7
       test/uniq_test.exs:96: (test)



 11) doctest Uniq.UUID.parse/1 (4) (Uniq.Test)
     test/uniq_test.exs:6
     Doctest failed
     doctest:
       iex> match?({:ok, %Uniq.UUID{format: :default, version: 1}}, Uniq.UUID.uuid1() |> Uniq.UUID.parse())
       true
     code:  match?({:ok, %Uniq.UUID{format: :default, version: 1}}, Uniq.UUID.uuid1() |> Uniq.UUID.parse()) === true
     left:  false
     right: true
     stacktrace:
       lib/uuid.ex:456: Uniq.UUID (module)



 12) test parsing can parse version 4 (Uniq.Test)
     test/uniq_test.exs:55
     match (=) failed
     The following variables were pinned:
       version = 4
     code:  assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
     left:  {:ok, %Uniq.UUID{format: :raw, version: ^version}}
     right: {:error, {:invalid_format, "`", 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:207: Uniq.Test.parse/2
       test/uniq_test.exs:56: (test)



 13) test generating can generate version 4 (Uniq.Test)
     test/uniq_test.exs:164
     match (=) failed
     code:  assert {:ok, %UUID{format: :default, version: 4}} = UUID.parse(default)
     left:  {:ok, %Uniq.UUID{format: :default, version: 4}}
     right: {:error, {:invalid_format, <<220>>, 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:168: (test)



 14) property parsing can parse any 128-bit binary with valid version/variant values (Uniq.Test)
     test/uniq_test.exs:71
     Failed with generated values (after 0 successful runs):
     
         * Clause:    {version, variant, uuid} <- valid_uuid()
           Generated: {4, <<7::size(3)>>, <<0, 0, 0, 0, 0, 0, 64, 0, 224, 0, 0, 0, 0, 0, 0, 0>>}
     
     match (=) failed
     The following variables were pinned:
       version = 4
       variant = <<7::size(3)>>
     code:  assert {:ok, %UUID{version: ^version, variant: ^variant}} = UUID.parse(uuid)
     left:  {:ok, %Uniq.UUID{version: ^version, variant: ^variant}}
     right: {:error, {:invalid_format, <<0>>, 3, 5, 13}}
     stacktrace:
       test/uniq_test.exs:73: anonymous fn/4 in Uniq.Test."property parsing can parse any 128-bit binary with valid version/variant values"/1
       (stream_data 0.5.0) lib/stream_data.ex:2148: StreamData.shrink_failure/6
       (stream_data 0.5.0) lib/stream_data.ex:2108: StreamData.check_all/7
       test/uniq_test.exs:72: (test)

.

 15) test parsing can parse version 3 (Uniq.Test)
     test/uniq_test.exs:51
     match (=) failed
     The following variables were pinned:
       version = 3
     code:  assert {:ok, %UUID{format: :raw, version: ^version}} = UUID.parse(uuids[:raw][version])
     left:  {:ok, %Uniq.UUID{format: :raw, version: ^version}}
     right: {:error, {:invalid_format, "B", 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:207: Uniq.Test.parse/2
       test/uniq_test.exs:52: (test)



 16) test generating can generate version 5 (Uniq.Test)
     test/uniq_test.exs:172
     match (=) failed
     code:  assert {:ok, %UUID{format: :default, version: 5}} = UUID.parse(default)
     left:  {:ok, %Uniq.UUID{format: :default, version: 5}}
     right: {:error, {:invalid_format, <<128>>, 2, 6, 14}}
     stacktrace:
       test/uniq_test.exs:179: (test)

..
Finished in 15.2 seconds (0.1s async, 15.0s sync)
4 doctests, 6 properties, 22 tests, 16 failures

Randomized with seed 870698

from uniq.

bitwalker avatar bitwalker commented on July 18, 2024

Something really weird is going on here, to the point that I suspect a miscompilation.

Parsing is failing here, because the binary constructed by <<clock::unsigned-integer-size(clock_size>> = <<clock_hi::bitstring-size(clock_hi_size), clock_lo::bitstring-size(8)>> is inexplicably being truncated to 8 bits from 14 bits. For reference, I'm looking at the intermediate results when running mix test test/uniq_test:47, which tests parsing a Version 1 uuid. For that test, here's the values of the bindings in that expression:

clock_size = 14
clock_hi = <<56::size(6)>>
clock_hi_size = 6
clock_lo = <<188>>

If I open up IEx and type in the exact same expression with all of the same values, I get <<226, 60::size(6)>> as output, which is correct; but the compiled code produces <<226>>! However, if I change clock_hi_size to 6 in the code, it correctly produces <<226, 60::size(6)>> and the test passes.

I can only assume this is a JIT issue, because binary constructing/matching in the shell uses the eval_bits module in the Erlang standard library, whereas compiled code uses highly specialized bytecode instructions for the same tasks, some/all of which get JITed I would imagine.

@josevalim Has anyone reported similar issues in Elixir with OTP 26? I'm not actually sure whether this is an issue in Erlang or in Elixir. It is a bit tough to reproduce minimally because the bug here only seems to appear when none of the values are known at compile-time, so presumably optimizations are interfering in less dynamic, but equivalent programs. I would assume it's an issue in Erlang due to the difference in behavior between compiled code and interpreted code, but that might just be a coincidence. Sorry to ping you like this, again 😅, but this definitely appears to be a compiler bug of some sort or another. I'm happy to put in some effort on a minimal reproducer, but wanted to make sure this wasn't already a known issue that I'm not aware of.

from uniq.

josevalim avatar josevalim commented on July 18, 2024

From the description it looks like an Erlang bug. So I would try 26.0.2 and, if it persists, we need a way to reproduce it so I can report it upstream.

from uniq.

bitwalker avatar bitwalker commented on July 18, 2024

Yeah I'm testing with 26.0.2, so I'll put a reproducer together and follow up. Thanks!

from uniq.

bitwalker avatar bitwalker commented on July 18, 2024

@josevalim Here's the reproducer:

# lib/app.ex
defmodule BinMatchBugOtp26 do
  defmacrop bits(n), do: quote(do: bitstring - size(unquote(n)))
  defmacrop uint(n), do: quote(do: unsigned - integer - size(unquote(n)))

  @rfc_variant <<2::2>>

  def parse(<<_::64, @rfc_variant, rest::bits(62)>>) do
    parse_raw(@rfc_variant, rest)
  end

  defp parse_raw(variant, rest) do
    variant_size = bit_size(variant)
    clock_hi_size = 8 - variant_size
    clock_size = 8 + clock_hi_size

    with <<clock_hi::bits(clock_hi_size), clock_lo::bits(8), _::bits(48)>> <-
           rest,
         <<clock::uint(clock_size)>> <-
           IO.inspect(<<clock_hi::bits(clock_hi_size), clock_lo::bits(8)>>) do
      {:ok, clock}
    else
      other ->
        {:error, {:invalid_format, other}}
    end
  end
end

With the following test harness:

# test/app_test.exs
defmodule BinMatchBugOtp26Test do
  use ExUnit.Case

  test "reproduces the bug" do
    bin = <<146, 254, 245, 214, 198, 57, 17, 235, 184, 188, 2, 66, 172, 19, 0, 3>>
    assert {:ok, 14524} = BinMatchBugOtp26.parse(bin)
  end
end

If you modify it to change variant_size = bit_size(variant) to variant_size = 2, the test passes, presumably due to optimizations, but I haven't looked at the bytecode to see for sure. I think this should be minimal enough for debugging/reporting upstream.

Just to reiterate, we're expecting that the binary which is being inspected has the value <<226, 60::size(6)>>, but the bug causes it to be <<226>>.

from uniq.

josevalim avatar josevalim commented on July 18, 2024

Btw, it may be worth writing the alternative variant and shipping a new release to unblock others. :)

from uniq.

bitwalker avatar bitwalker commented on July 18, 2024

There isn't a great alternative unfortunately, the code I used for the repro was just for a single UUID variant, and there are quite a few. I can probably use macros to generate more specialized parsing functions that bypass the issue for the time being though.

For anyone reading, I wouldn't consider running OTP 26 in production until this bug gets patched, we're lucky that it is so easy to trigger with uniq, but that is only because I was careful to write assertions into core parts of the parsing code. I have to imagine there is a lot of parsing code out there affected by this bug, just with more subtle failure modes.

from uniq.

bitwalker avatar bitwalker commented on July 18, 2024

I've pushed a workaround in main, and I'll push a new release (0.6.1) shortly

from uniq.

Related Issues (8)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.