Comments (10)
Interesting - I wonder what happened in OTP 26 to break things, I'll take a look at this ASAP
from uniq.
Thanks, reported upstream here: erlang/otp#7469
from uniq.
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.
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.
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.
Yeah I'm testing with 26.0.2, so I'll put a reproducer together and follow up. Thanks!
from uniq.
@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.
Btw, it may be worth writing the alternative variant and shipping a new release to unblock others. :)
from uniq.
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.
I've pushed a workaround in main
, and I'll push a new release (0.6.1) shortly
from uniq.
Related Issues (8)
- Confusion on many_to_many (uuid::bytea coercion) HOT 8
- `uniq` causes constant recompilations and causes tooling issues HOT 10
- Invalid typespec HOT 4
- False positive on `Uniq.UUID.valid?/1`?
- Comparing UUID7 doesn't seem to produce the expected results HOT 1
- Ecto is not really an optional dependency HOT 2
- Compilation Issue due to Elixir compiled_env usage HOT 4
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from uniq.