Repository: tv-labs/lua
Pull request #79 (2025-05-14 17:02:22)

fix: ensure that return values are properly encoded
Return values are only valid if its a list of encoded values. If they contain lists inside of them, they are not valid. This PR fixes this check


Patch:
diff --git a/lib/lua.ex b/lib/lua.ex
index c2385e7..4d91253 100644
--- a/lib/lua.ex
+++ b/lib/lua.ex
@@ -186,9 +186,9 @@ defmodule Lua do
       case func do
         func when is_function(func, 1) ->
           fn data ->
-            return = func.(data)
+            return = List.wrap(func.(data))
 
-            if not Lua.Util.encoded?(return) do
+            if not Lua.Util.list_encoded?(return) do
               raise Lua.RuntimeException,
                 function: function_name,
                 scope: scope,
@@ -205,24 +205,28 @@ defmodule Lua do
                 :luerl_lib.lua_error(reason, lua.state)
 
               {value, %__MODULE__{} = lua} ->
-                if not Lua.Util.encoded?(value) do
+                value = List.wrap(value)
+
+                if not Lua.Util.list_encoded?(value) do
                   raise Lua.RuntimeException,
                     function: function_name,
                     scope: scope,
                     message: "deflua functions must return encoded data, got #{inspect(value)}"
                 end
 
-                {List.wrap(value), lua.state}
+                {value, lua.state}
 
               value ->
-                if not Lua.Util.encoded?(value) do
+                value = List.wrap(value)
+
+                if not Lua.Util.list_encoded?(value) do
                   raise Lua.RuntimeException,
                     function: function_name,
                     scope: scope,
                     message: "deflua functions must return encoded data, got #{inspect(value)}"
                 end
 
-                {List.wrap(value), state}
+                {value, state}
             end
           end
       end
@@ -760,24 +764,28 @@ defmodule Lua do
         :luerl_lib.lua_error(reason, lua.state)
 
       {data, %Lua{} = lua} ->
-        if not Lua.Util.encoded?(data) do
+        data = List.wrap(data)
+
+        if not Lua.Util.list_encoded?(data) do
           raise Lua.RuntimeException,
             function: function_name,
             scope: module.scope(),
             message: "deflua functions must return encoded data, got #{inspect(data)}"
         end
 
-        {List.wrap(data), lua.state}
+        {data, lua.state}
 
       data ->
-        if not Lua.Util.encoded?(data) do
+        data = List.wrap(data)
+
+        if not Lua.Util.list_encoded?(data) do
           raise Lua.RuntimeException,
             function: function_name,
             scope: module.scope(),
             message: "deflua functions must return encoded data, got #{inspect(data)}"
         end
 
-        {List.wrap(data), lua.state}
+        {data, lua.state}
     end
   catch
     thrown_value ->
diff --git a/lib/lua/util.ex b/lib/lua/util.ex
index 109c98a..6ee798a 100644
--- a/lib/lua/util.ex
+++ b/lib/lua/util.ex
@@ -10,6 +10,12 @@ defmodule Lua.Util do
     Record.defrecord(name, fields)
   end
 
+  # Check if all members of the list are encoded
+  # useful for checking return values
+  def list_encoded?(list) when is_list(list) do
+    Enum.all?(list, &encoded?/1)
+  end
+
   # Returns true for identity values
   # or values that hold internal Luerl representations like tref
   def encoded?(nil), do: true
@@ -26,10 +32,6 @@ defmodule Lua.Util do
   def encoded?(record) when Record.is_record(record, :erl_func), do: true
   def encoded?(record) when Record.is_record(record, :erl_mfa), do: true
 
-  def encoded?(list) when is_list(list) do
-    Enum.all?(list, &encoded?/1)
-  end
-
   def encoded?(_), do: false
 
   def format_error(error) do


Test patch:
diff --git a/test/lua/util_test.exs b/test/lua/util_test.exs
index b9cb21c..3d2ba02 100644
--- a/test/lua/util_test.exs
+++ b/test/lua/util_test.exs
@@ -14,7 +14,6 @@ defmodule Lua.UtilTest do
       assert Lua.Util.encoded?(0)
       assert Lua.Util.encoded?(Enum.random(1..1000))
       assert Lua.Util.encoded?(:rand.uniform())
-      assert Lua.Util.encoded?(["a", "b", "c"])
 
       # tref
       {{:tref, _} = table, _lua} = Lua.encode!(Lua.new(), %{a: 1, b: 2})
@@ -51,13 +50,16 @@ defmodule Lua.UtilTest do
     test "returns false for non decoded values" do
       refute Lua.Util.encoded?(%{a: 1})
       refute Lua.Util.encoded?({:not, :valid, :lua})
+      refute Lua.Util.encoded?([])
       refute Lua.Util.encoded?([:a, :b, :c])
       refute Lua.Util.encoded?(["a", "b", :c])
+      refute Lua.Util.encoded?(["a", "b", "c"])
       refute Lua.Util.encoded?(self())
       refute Lua.Util.encoded?(&Enum.map/2)
       refute Lua.Util.encoded?(make_ref())
       refute Lua.Util.encoded?(fn a -> a end)
       refute Lua.Util.encoded?({:userdata, 1})
+      refute Lua.Util.encoded?([1, 2, [3]])
     end
   end
 
diff --git a/test/lua_test.exs b/test/lua_test.exs
index e289631..cb11d70 100644
--- a/test/lua_test.exs
+++ b/test/lua_test.exs
@@ -1074,7 +1074,7 @@ defmodule LuaTest do
 
     test "it cannot return atom values", %{lua: lua} do
       error_message =
-        "Lua runtime error: atom() failed, deflua functions must return encoded data, got :atom"
+        "Lua runtime error: atom() failed, deflua functions must return encoded data, got [:atom]"
 
       assert_raise Lua.RuntimeException, error_message, fn ->
         Lua.eval!(lua, "return example.atom()", decode: true)
@@ -1088,7 +1088,7 @@ defmodule LuaTest do
 
     test "it cannot return tuples from Elixir", %{lua: lua} do
       error =
-        "Lua runtime error: tuple() failed, deflua functions must return encoded data, got {:key, \"value\"}"
+        "Lua runtime error: tuple() failed, deflua functions must return encoded data, got [key: \"value\"]"
 
       assert_raise Lua.RuntimeException, error, fn ->
         Lua.eval!(lua, "return example.tuple()")