From 217bc357c223c6e0b400aa2b27936f96c132f0cd Mon Sep 17 00:00:00 2001 From: isaac yonemoito Date: Thu, 2 May 2024 19:27:56 -0500 Subject: [PATCH] type ingress and egress basic checking --- lib/zig/sema.ex | 11 +++++++++-- lib/zig/templates/basic.zig.eex | 10 +++++----- lib/zig/type.ex | 10 ++++++++++ lib/zig/type/array.ex | 3 ++- lib/zig/type/bool.ex | 1 + lib/zig/type/enum.ex | 1 + lib/zig/type/float.ex | 1 + lib/zig/type/integer.ex | 1 + lib/zig/type/manypointer.ex | 5 ++++- lib/zig/type/optional.ex | 1 + lib/zig/type/slice.ex | 1 + lib/zig/type/struct.ex | 5 ++++- .../types/errors/beam_env_fails.exs | 10 ++++++++++ .../manypointer_return_fails.exs} | 2 +- test/integration/types/manypointer_test.exs | 4 ++-- test/integration/types/other_test.exs | 13 +++++++++++++ test/integration/types/term_test.exs | 18 ++++++++++++++++++ 17 files changed, 84 insertions(+), 13 deletions(-) create mode 100644 test/integration/types/errors/beam_env_fails.exs rename test/integration/types/{_manypointer_forbidden_output.exs => errors/manypointer_return_fails.exs} (64%) create mode 100644 test/integration/types/other_test.exs create mode 100644 test/integration/types/term_test.exs diff --git a/lib/zig/sema.ex b/lib/zig/sema.ex index fb4121b2..185ed4f9 100644 --- a/lib/zig/sema.ex +++ b/lib/zig/sema.ex @@ -166,11 +166,18 @@ defmodule Zig.Sema do end defp validate_nif!(nif) do - Enum.each(nif.params, &validate_param!/1) + Enum.each(nif.params, &validate_param!(&1, nif)) validate_return!(nif) end - defp validate_param!(param), do: :ok + defp validate_param!({_, param}, nif) do + unless Type.param_allowed?(param.type) do + raise CompileError, + description: "functions cannot have #{Type.render_zig(param.type)} as a parameter", + file: nif.file, + line: nif.line + end + end defp validate_return!(nif) do unless Type.return_allowed?(nif.return.type) do diff --git a/lib/zig/templates/basic.zig.eex b/lib/zig/templates/basic.zig.eex index 32967844..cff55304 100644 --- a/lib/zig/templates/basic.zig.eex +++ b/lib/zig/templates/basic.zig.eex @@ -1,4 +1,4 @@ -<% needs_make? = @params |> Map.values |> Enum.any?(&Type.needs_make?/1) %> +<% needs_make? = @params |> Map.values |> Enum.any?(&Type.needs_make?(&1.type)) %> fn <%= @name %>(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callconv(.C) e.ErlNifTerm { beam.context = .{ @@ -22,17 +22,17 @@ fn <%= @name %>(env: beam.env, argc: c_int, args: [*c] const e.ErlNifTerm) callc var error_index: u8 = undefined; const payload = beam.payload.build(nif.<%= @name %>, argc, args, &error_index, payload_opts) catch |err| { - <%= if @signature.arity == 0 do %> + <%= if needs_make? do %> + return e.enif_raise_exception(env, beam.make(.{err, error_index, error_info}, .{}).v); + <% else %> _ = err; unreachable; - <% else %> - return e.enif_raise_exception(env, beam.make(.{err, error_index, error_info}, .{}).v); <% end %> }; // defer beam.payload.cleanup(payload, cleanup_opts); - const result = @call(.auto, nif.<%= @name %>, payload) <%= Nif.maybe_catch(@signature.return) %>; + const result = @call(.auto, nif.<%= @name %>, payload) <%= Nif.maybe_catch(@return.type) %>; <%= Return.render(@return) %> }; diff --git a/lib/zig/type.ex b/lib/zig/type.ex index 35061009..e774ef28 100644 --- a/lib/zig/type.ex +++ b/lib/zig/type.ex @@ -45,6 +45,9 @@ defprotocol Zig.Type do # validations: + @spec param_allowed?(t) :: boolean + def param_allowed?(type) + @spec return_allowed?(t) :: boolean def return_allowed?(type) @@ -259,9 +262,16 @@ end defimpl Zig.Type, for: Atom do alias Zig.Type + def param_allowed?(type), do: type in ~w(term erl_nif_term pid)a def return_allowed?(type), do: type in ~w(term erl_nif_term pid void)a def can_cleanup?(_), do: false + def render_zig(:term), do: "beam.term" + def render_zig(:erl_nif_term), do: "e.erl_nif_term" + def render_zig(:pid), do: "beam.pid" + def render_zig(:env), do: "beam.env" + def render_zig(atom), do: "#{atom}" + def render_return(:void, _), do: "_ = result; break :result_block beam.make(.ok, .{}).v;" def render_return(_, _), do: Type._default_return() diff --git a/lib/zig/type/array.ex b/lib/zig/type/array.ex index a9a4fc86..51d49dfd 100644 --- a/lib/zig/type/array.ex +++ b/lib/zig/type/array.ex @@ -26,10 +26,11 @@ defmodule Zig.Type.Array do } end + def param_allowed?(array), do: Type.param_allowed?(array.child) def return_allowed?(array), do: Type.return_allowed?(array.child) def can_cleanup?(_), do: false - def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_payload_options(_, _, _), do: Type._default_payload_options() def marshal_param(_, _, _, _), do: Type._default_marshal() def marshal_return(_, _, _), do: Type._default_marshal() def render_return(_, opts), do: Type._default_return(opts) diff --git a/lib/zig/type/bool.ex b/lib/zig/type/bool.ex index caefc095..ec6b2247 100644 --- a/lib/zig/type/bool.ex +++ b/lib/zig/type/bool.ex @@ -8,6 +8,7 @@ defmodule Zig.Type.Bool do def from_json(_), do: %__MODULE__{} + def param_allowed?(_), do: true def return_allowed?(_), do: true def can_cleanup?(_), do: false diff --git a/lib/zig/type/enum.ex b/lib/zig/type/enum.ex index 1a64da8e..8a59645f 100644 --- a/lib/zig/type/enum.ex +++ b/lib/zig/type/enum.ex @@ -16,6 +16,7 @@ defmodule Zig.Type.Enum do ~s(%Zig.Type.Enum{name: "#{enum.name}", tags: #{Kernel.inspect(enum.tags, opts)}}) end + def param_allowed?(_), do: true def return_allowed?(_), do: true def can_cleanup?(_), do: false diff --git a/lib/zig/type/float.ex b/lib/zig/type/float.ex index 907cdd2a..d3efadca 100644 --- a/lib/zig/type/float.ex +++ b/lib/zig/type/float.ex @@ -21,6 +21,7 @@ defmodule Zig.Type.Float do def spec(_, _, _), do: Type.spec(:float) + def param_allowed?(_), do: true def return_allowed?(_), do: true def can_cleanup?(_), do: false diff --git a/lib/zig/type/integer.ex b/lib/zig/type/integer.ex index a4e72b1f..15ee0038 100644 --- a/lib/zig/type/integer.ex +++ b/lib/zig/type/integer.ex @@ -240,6 +240,7 @@ defmodule Zig.Type.Integer do defp typemax(%{signedness: :signed, bits: 1}), do: 0 defp typemax(%{signedness: :signed, bits: bits}), do: Bitwise.<<<(1, bits - 1) - 1 + def param_allowed?(_), do: true def return_allowed?(_), do: true def can_cleanup?(_), do: false diff --git a/lib/zig/type/manypointer.ex b/lib/zig/type/manypointer.ex index a8cb1818..e636d707 100644 --- a/lib/zig/type/manypointer.ex +++ b/lib/zig/type/manypointer.ex @@ -1,7 +1,7 @@ defmodule Zig.Type.Manypointer do alias Zig.Type alias Zig.Type.Optional - + use Type import Type, only: :macros @@ -25,6 +25,7 @@ defmodule Zig.Type.Manypointer do } end + def param_allowed?(pointer), do: Type.return_allowed?(pointer.child) def return_allowed?(pointer), do: pointer.has_sentinel? and Type.return_allowed?(pointer.child) def can_cleanup?(_), do: true @@ -37,8 +38,10 @@ defmodule Zig.Type.Manypointer do case type do %{has_sentinel?: false} -> "[*]#{Type.render_zig(type.child)}" + %{child: ~t(u8)} -> "[*:0]u8" + %{child: %Optional{}} -> "[*:null]#{Type.render_zig(type.child)}" end diff --git a/lib/zig/type/optional.ex b/lib/zig/type/optional.ex index bad8ca43..189aa895 100644 --- a/lib/zig/type/optional.ex +++ b/lib/zig/type/optional.ex @@ -9,6 +9,7 @@ defmodule Zig.Type.Optional do def from_json(%{"child" => child}, module), do: %__MODULE__{child: Type.from_json(child, module)} + def param_allowed?(optional), do: Type.param_allowed?(optional.child) def return_allowed?(optional), do: Type.return_allowed?(optional.child) def can_cleanup?(optional), do: Type.can_cleanup?(optional.child) diff --git a/lib/zig/type/slice.ex b/lib/zig/type/slice.ex index 03aedd13..5698bf7e 100644 --- a/lib/zig/type/slice.ex +++ b/lib/zig/type/slice.ex @@ -80,6 +80,7 @@ defmodule Zig.Type.Slice do # ETC + def param_allowed?(slice), do: Type.param_allowed?(slice.child) def return_allowed?(slice), do: Type.return_allowed?(slice.child) def can_cleanup?(_), do: true diff --git a/lib/zig/type/struct.ex b/lib/zig/type/struct.ex index 3fdbc643..cb3a5ab0 100644 --- a/lib/zig/type/struct.ex +++ b/lib/zig/type/struct.ex @@ -109,6 +109,9 @@ defmodule Zig.Type.Struct do |> Enum.sort() end + # for now. Later, we will need to do more sophisticated checks + def param_allowed?(_), do: true + def return_allowed?(struct) do struct.required |> Map.values() @@ -119,7 +122,7 @@ defmodule Zig.Type.Struct do def can_cleanup?(_), do: false - def render_payload_options(type, index, _), do: Type._default_payload_options() + def render_payload_options(_, _, _), do: Type._default_payload_options() def render_return(_, _), do: Type._default_return() def marshal_param(_, _, _, _), do: Type._default_marshal() def marshal_return(_, _, _), do: Type._default_marshal() diff --git a/test/integration/types/errors/beam_env_fails.exs b/test/integration/types/errors/beam_env_fails.exs new file mode 100644 index 00000000..927df282 --- /dev/null +++ b/test/integration/types/errors/beam_env_fails.exs @@ -0,0 +1,10 @@ +defmodule ZiglerTest.Types.Errors.BeamEnvFails do + use Zig, otp_app: :zigler + + ~Z""" + const beam = @import("beam"); + pub fn forbidden(env: beam.env) void { + _ = env; + } + """ +end diff --git a/test/integration/types/_manypointer_forbidden_output.exs b/test/integration/types/errors/manypointer_return_fails.exs similarity index 64% rename from test/integration/types/_manypointer_forbidden_output.exs rename to test/integration/types/errors/manypointer_return_fails.exs index 0a233db0..f81eb12c 100644 --- a/test/integration/types/_manypointer_forbidden_output.exs +++ b/test/integration/types/errors/manypointer_return_fails.exs @@ -1,4 +1,4 @@ -defmodule ZiglerTest.Types.ManypointerForbiddenOutput do +defmodule ZiglerTest.Types.Errors.ManypointerReturnFails do use Zig, otp_app: :zigler ~Z""" diff --git a/test/integration/types/manypointer_test.exs b/test/integration/types/manypointer_test.exs index 25b3f836..a520aaab 100644 --- a/test/integration/types/manypointer_test.exs +++ b/test/integration/types/manypointer_test.exs @@ -68,9 +68,9 @@ defmodule ZiglerTest.Types.ManypointerTest do describe "for normal manypointers" do test "having a return value is prohibited" do assert_raise CompileError, - "test/integration/types/_manypointer_forbidden_output.exs:5: functions returning [*]u8 cannot be nifs", + "test/integration/types/errors/manypointer_return_fails.exs:5: functions returning [*]u8 cannot be nifs", fn -> - Code.compile_file("_manypointer_forbidden_output.exs", __DIR__) + Code.compile_file("errors/manypointer_return_fails.exs", __DIR__) end end end diff --git a/test/integration/types/other_test.exs b/test/integration/types/other_test.exs new file mode 100644 index 00000000..dccb7729 --- /dev/null +++ b/test/integration/types/other_test.exs @@ -0,0 +1,13 @@ +defmodule ZiglerTest.Types.OtherTest do + use ZiglerTest.IntegrationCase, async: true + + describe "beam.env" do + test "cannot generally be used as a parameter" do + assert_raise CompileError, + "test/integration/types/errors/beam_env_fails.exs:6: functions cannot have beam.env as a parameter", + fn -> + Code.compile_file("errors/beam_env_fails.exs", __DIR__) + end + end + end +end diff --git a/test/integration/types/term_test.exs b/test/integration/types/term_test.exs new file mode 100644 index 00000000..d7bc182e --- /dev/null +++ b/test/integration/types/term_test.exs @@ -0,0 +1,18 @@ +defmodule ZiglerTest.Types.TermTest do + use ZiglerTest.IntegrationCase, async: true + + use Zig, otp_app: :zigler + + ~Z""" + const beam = @import("beam"); + pub fn term_test(term: beam.term) beam.term { + return term; + } + """ + + describe "for a generic term" do + test "you can return a term" do + assert 1 == term_test(1) + end + end +end