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

Nested struct list segfault #108

Merged
merged 20 commits into from
Mar 4, 2024
Merged

Nested struct list segfault #108

merged 20 commits into from
Mar 4, 2024

Conversation

bartkrak
Copy link
Contributor

@bartkrak bartkrak commented Feb 8, 2024

@bartkrak bartkrak requested a review from FelonEkonom as a code owner February 8, 2024 14:03
@bartkrak
Copy link
Contributor Author

bartkrak commented Feb 8, 2024

With CNode there are two cases that cause problems.
Case 1: A struct that holds a list of structs containing a list. Exact specification used for testing below.
spec file:

type nested_struct_inner :: %Nested.StructInner{
  name: string,
  data: [int],
  id: int
}
type nested_struct_list :: %Nested.StructList{
  inner_list: [nested_struct_inner],
  id: int
}
spec test_nested_struct_list(in_struct :: nested_struct_list) :: {:ok :: label, out_struct :: nested_struct_list}

C file:

UNIFEX_TERM test_nested_struct_list(UnifexEnv *env, nested_struct_list in_struct) {
  return test_nested_struct_list_result_ok(env, in_struct);
}

and test file:

test "nested struct list", context do
    cnode = context[:cnode]
    inner = %Nested.StructInner{id: 1, name: "Jan Kowlaski", data: [1,2,3]}
    nested_struct_list = %Nested.StructList{id: 1, inner_list: [inner]}
    assert {:ok, ^nested_struct_list} = Unifex.CNode.call(cnode, :test_nested_struct_list, [nested_struct_list])
end

Running above configuration results in the following error:

** (RuntimeError) Unifex CNode: cannot parse argument 'in_struct' of type ':nested_struct_list'
     code: assert {:ok, ^nested_struct_list} = Unifex.CNode.call(cnode, :test_nested_struct_list, [nested_struct_list])
     stacktrace:
       (unifex 1.1.1) lib/unifex/cnode.ex:122: Unifex.CNode.handle_response/1
       test/example_test.exs:163: (test)

Above error is raised when CNode tries to parse the most inner list (“data” field of nested_struct_inner)
It's related to parsing arguments in generate_caller_function in lib/unifex/code_generators/cnode.ex

Case 2: A struct that holds a list of structs. Exact specification used for testing below.
spec file:

type nested_struct_inner :: %Nested.StructInner{
  name: string,
  id: int
}
type nested_struct_list :: %Nested.StructList{
  inner_list: [nested_struct_inner],
  id: int
}
spec test_nested_struct_list(in_struct :: nested_struct_list) :: {:ok :: label, out_struct :: nested_struct_list}

C file:

UNIFEX_TERM test_nested_struct_list(UnifexEnv *env, nested_struct_list in_struct) {
  return test_nested_struct_list_result_ok(env, in_struct);
}

and test file:

test "nested struct list", context do
    cnode = context[:cnode]
    inner = %Nested.StructInner{id: 1, name: "Jan Kowlaski"}
    nested_struct_list = %Nested.StructList{id: 1, inner_list: [inner]}
    assert {:ok, ^nested_struct_list} = Unifex.CNode.call(cnode, :test_nested_struct_list, [nested_struct_list])
end

Running this configuration results in a timeout to CNode

** (RuntimeError) Timeout upon call to the CNode :"bundlex_cnode_0_8bf60324-c17c-4897-bd2c-73e1df7b68c5@Barteks-MacBook-Pro-2"
     code: assert {:ok, ^nested_struct_list} = Unifex.CNode.call(cnode, :test_nested_struct_list, [nested_struct_list])
     stacktrace:
       (bundlex 1.4.5) lib/bundlex/cnode.ex:139: Bundlex.CNode.call/3
       (unifex 1.1.1) lib/unifex/cnode.ex:96: Unifex.CNode.call/4
       test/example_test.exs:163: (test)

@bartkrak bartkrak self-assigned this Feb 8, 2024
@@ -75,6 +75,8 @@ defmodule Unifex.IntegrationTest do
|> Enum.each(fn ref ->
assert File.read!("test_projects/#{project}/c_src/example/_generated/#{interface}/#{ref}") ==
File.read!("test/fixtures/#{project}_ref_generated/#{interface}/#{ref}")

true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unnecessary

Comment on lines 7 to 9
def start_link(initial_value) do
Agent.start_link(fn -> initial_value end, name: __MODULE__)
end
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can delete initial_value var since we always start counter from 0

Comment on lines 13 to 14
Agent.update(__MODULE__, &(&1 + 1))
Agent.get(__MODULE__, & &1)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use Agent.get_and_update instead

Comment on lines 13 to 14
def run(_args) do
Counter.start_link(0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start Counter in the application module, in the supervisor tree

end

@spec get_value() :: integer()
def get_value do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_value do
def get_and_increment() do

This describes better, what really happens

Comment on lines 1 to 6
#pragma once

#ifdef BUNDLEX_CNODE
#include "cnode/example.h"
#endif

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Files from _generated shouldn't be in this PR

@@ -0,0 +1,3 @@
defmodule Nested.StructInner do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
defmodule Nested.StructInner do
defmodule Nested.InnerStruct do

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this struct is unused, forgot to remove this

@@ -52,6 +51,13 @@ type nested_struct :: %Nested.Struct{

spec test_nested_struct(in_struct :: nested_struct) :: {:ok :: label, out_struct :: nested_struct}

type my_enum :: :option_one | :option_two | :option_three | :option_four | :option_five
type nested_struct_list :: %Nested.StructList{
inner_list: [my_struct],
Copy link
Member

@FelonEkonom FelonEkonom Feb 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
inner_list: [my_struct],
list_of_structs: [my_struct],

Name inner_list suggests, that this list is wrapped in something, eg. another list


spec test_nested_struct_list(in_struct :: nested_struct_list) :: {:ok :: label, out_struct :: nested_struct_list}

type(my_enum :: :option_one | :option_two | :option_three | :option_four | :option_five)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove the brackets

Comment on lines 11 to 111
assert nil == Unifex.CNode.call(context[:cnode], :test_nil, [])
end

test "atom", context do
assert {:ok, :unifex} = Unifex.CNode.call(context[:cnode], :test_atom, [:unifex])
end

test "bool", context do
assert {:ok, true} = Unifex.CNode.call(context[:cnode], :test_bool, [true])
assert {:ok, false} = Unifex.CNode.call(context[:cnode], :test_bool, [false])
refute match?({:ok, false}, Unifex.CNode.call(context[:cnode], :test_bool, [true]))
end

test "float", context do
assert {:ok, 0.0} = Unifex.CNode.call(context[:cnode], :test_float, [0.0])
assert {:ok, 0.1} = Unifex.CNode.call(context[:cnode], :test_float, [0.1])
assert {:ok, -0.1} = Unifex.CNode.call(context[:cnode], :test_float, [-0.1])
refute match?({:ok, 1}, Unifex.CNode.call(context[:cnode], :test_float, [1.0]))
end

test "unsigned int", context do
cnode = context[:cnode]
assert {:ok, 0} = Unifex.CNode.call(cnode, :test_uint, [0])
assert {:ok, 5} = Unifex.CNode.call(cnode, :test_uint, [5])

assert_raise RuntimeError, ~r/argument.*in_uint.*unsigned/i, fn ->
Unifex.CNode.call(cnode, :test_uint, [-1])
end
end

test "string", context do
cnode = context[:cnode]

big_test_string =
"unifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexu
nifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexuni
fexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifexunifex"

assert {:ok, ""} = Unifex.CNode.call(cnode, :test_string, [""])
assert {:ok, "test_string"} = Unifex.CNode.call(cnode, :test_string, ["test_string"])
assert {:ok, "-12345"} = Unifex.CNode.call(cnode, :test_string, ["-12345"])
assert {:ok, "255"} = Unifex.CNode.call(cnode, :test_string, ["255"])
assert {:ok, ^big_test_string} = Unifex.CNode.call(cnode, :test_string, [big_test_string])
end

test "list as string", context do
0..253
|> Enum.each(fn x ->
list = [x, x + 1, x + 2]
assert {:ok, ^list} = Unifex.CNode.call(context[:cnode], :test_list, [list])
end)
end

test "list", context do
cnode = context[:cnode]
assert {:ok, []} = Unifex.CNode.call(cnode, :test_list, [[]])
assert {:ok, [-1, -1, -1]} = Unifex.CNode.call(cnode, :test_list, [[-1, -1, -1]])
assert {:ok, [-10, -17, -28]} = Unifex.CNode.call(cnode, :test_list, [[-10, -17, -28]])
assert {:ok, [355, 355, 355]} = Unifex.CNode.call(cnode, :test_list, [[355, 355, 355]])
assert {:ok, [1254, 1636, 3643]} = Unifex.CNode.call(cnode, :test_list, [[1254, 1636, 3643]])
end

test "list of strings", context do
cnode = context[:cnode]
assert {:ok, []} = Unifex.CNode.call(cnode, :test_list_of_strings, [[]])

assert {:ok, ["", "", ""]} = Unifex.CNode.call(cnode, :test_list_of_strings, [["", "", ""]])

assert {:ok, ["1", "2", "3"]} =
Unifex.CNode.call(cnode, :test_list_of_strings, [["1", "2", "3"]])

assert {:ok, ["abc", "def", "ghi"]} =
Unifex.CNode.call(cnode, :test_list_of_strings, [["abc", "def", "ghi"]])
end

test "list of unsigned ints", context do
cnode = context[:cnode]
assert {:ok, [0, 1, 2]} = Unifex.CNode.call(cnode, :test_list_of_uints, [[0, 1, 2]])
end

test "list with other arguments", context do
cnode = context[:cnode]

assert {:ok, [1, 2, 3], :other_arg} =
Unifex.CNode.call(cnode, :test_list_with_other_args, [[1, 2, 3], :other_arg])

assert {:ok, [300, 400, 500], :other_arg} =
Unifex.CNode.call(cnode, :test_list_with_other_args, [[300, 400, 500], :other_arg])
end

test "payload", context do
assert {:ok, <<2, 2, 3>>} = Unifex.CNode.call(context[:cnode], :test_payload, [<<1, 2, 3>>])
end

test "pid", context do
pid = self()
assert {:ok, ^pid} = Unifex.CNode.call(context[:cnode], :test_pid, [pid])
end

test "struct", context do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😮

@bartkrak bartkrak requested a review from FelonEkonom March 1, 2024 15:26
@FelonEkonom
Copy link
Member

Bump version to 1.1.3 and merge and release this PR after #111

@bartkrak bartkrak merged commit edc3ad1 into master Mar 4, 2024
3 checks passed
@bartkrak bartkrak deleted the nested-struct-segfault branch March 4, 2024 22:35
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

Successfully merging this pull request may close these issues.

2 participants