From 989d67b22df287987d1471398630d1e72693dcd1 Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sat, 30 Nov 2024 13:59:08 -0500 Subject: [PATCH 1/8] API for storing and retrieving "private" data Adds an API that allows a consumer of Luerl to store private data that can be retrieved from functions called within Lua. This is useful in applications that may need to retrieve secrets from within Luerl, but don't want Lua scripts to be able to access these values. I've chatted with a number of users of Luerl, including Sam Aaron, who have had the need to expose secrets into their Luerl programs. Some have achieved this by using closures to close over private data and expose those functions to Luerl. I have used the process dictionary to acheive the same thing. If Luerl had an API to store and retrieve private data, as proposed by this PR, then both techniques would be unnecessary. Provide a `update_private/2` function instead that takes a function that can modify the private map - [ ] Get a nod from Robert - [ ] Implement in other "Elixir" API - [ ] Decide if we need to implement for "old" - [ ] Tests - [ ] Documentation --- src/luerl.erl | 10 ++++++++++ src/luerl.hrl | 3 ++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/luerl.erl b/src/luerl.erl index a90dfc1..ee04c49 100644 --- a/src/luerl.erl +++ b/src/luerl.erl @@ -46,6 +46,9 @@ %% Helping with storing VM state -export([externalize/1,internalize/1]). +%% Storing and retrieving private data +-export([put_private/3,get_private/2]). + %% init() -> State. init() -> @@ -506,3 +509,10 @@ externalize(S) -> internalize(S) -> luerl_lib_math:internalize(S). + +put_private(Key, Value, S) -> + Private = maps:put(Key, Value, S#luerl.private), + S#luerl{private=Private}. + +get_private(Key, S) -> + maps:get(Key, S#luerl.private). diff --git a/src/luerl.hrl b/src/luerl.hrl index 21ce276..824dcac 100644 --- a/src/luerl.hrl +++ b/src/luerl.hrl @@ -34,7 +34,8 @@ rand, %Random state tag, %Unique tag trace_func=none, %Trace function - trace_data %Trace data + trace_data, %Trace data + private=#{} }). %% Table structure. From ddb42d8ac0284c89638822c0382e200f1fc40fab Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sat, 30 Nov 2024 14:27:04 -0500 Subject: [PATCH 2/8] add tests --- src/luerl.erl | 4 ++++ test/luerl_tests.erl | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/src/luerl.erl b/src/luerl.erl index ee04c49..95c78e0 100644 --- a/src/luerl.erl +++ b/src/luerl.erl @@ -510,6 +510,10 @@ externalize(S) -> internalize(S) -> luerl_lib_math:internalize(S). +%% put_private(Key, Value, State) -> +%% State. +%% get_private(Key, State) -> +%% Value. put_private(Key, Value, S) -> Private = maps:put(Key, Value, S#luerl.private), S#luerl{private=Private}. diff --git a/test/luerl_tests.erl b/test/luerl_tests.erl index 26de63e..73b6c28 100644 --- a/test/luerl_tests.erl +++ b/test/luerl_tests.erl @@ -42,3 +42,9 @@ invalid_value_test() -> State = luerl:init(), ?assertException(error, {badarg, {invalid, value}}, luerl:encode({invalid, value}, State)). + +private_test() -> + State1 = luerl:init(), + State2 = luerl:put_private(secret, <<"mysecret">>, State1), + ?assertMatch(<<"mysecret">>, luerl:get_private(secret, State2)), + ?assertException(error, {badkey, missing}, luerl:get_private(missing, State2)). From 04f42055e01c84696f0df9a1cdccd94085064c31 Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sat, 30 Nov 2024 14:31:26 -0500 Subject: [PATCH 3/8] implement elixir api --- src/Elixir.Luerl.erl | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Elixir.Luerl.erl b/src/Elixir.Luerl.erl index d29b72c..04dec8b 100644 --- a/src/Elixir.Luerl.erl +++ b/src/Elixir.Luerl.erl @@ -49,6 +49,9 @@ %%Helping with storing VM state -export([externalize/1,internalize/1]). +%% Storing and retrieving private data +-export([put_private/3,get_private/2]). + init() -> luerl:init(). @@ -180,3 +183,9 @@ externalize(St) -> internalize(St) -> luerl:internalize(St). + +put_private(St, K, V) -> + luerl:put_private(K, V, St). + +get_private(St, K) -> + luerl:get_private(K, St). From f0ff5ca159e64d1183bc32d6de29720c8c12efc2 Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sat, 30 Nov 2024 14:33:48 -0500 Subject: [PATCH 4/8] docs --- doc/luerl.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/luerl.txt b/doc/luerl.txt index c515a4f..1504e7e 100644 --- a/doc/luerl.txt +++ b/doc/luerl.txt @@ -161,6 +161,13 @@ Interface functions - New Version luerl:decode_list([LuerlTerm], State) -> [Term] Decode a list of Luerl terms into a list of Erlang representations. + luerl:put_private(Key, Value, State) -> State. + Put a private value into Luerl that is not exposed to the runtime. + + luerl:get_private(Key, State) -> Term. + Get a private value from Luerl. + + AUTHORS Jean Chassoul, Robert Virding. From 6f5cff266f6a61fbc76fcdf2d70ae33730a4839c Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sat, 14 Dec 2024 08:20:02 -0500 Subject: [PATCH 5/8] cleanup --- doc/luerl.txt | 7 ----- doc/src/luerl.3.md | 9 ++++++ include/luerl.hrl | 72 ++++++++++++++++++++++++-------------------- src/luerl.erl | 8 ++++- test/luerl_tests.erl | 4 ++- 5 files changed, 58 insertions(+), 42 deletions(-) diff --git a/doc/luerl.txt b/doc/luerl.txt index 1504e7e..c515a4f 100644 --- a/doc/luerl.txt +++ b/doc/luerl.txt @@ -161,13 +161,6 @@ Interface functions - New Version luerl:decode_list([LuerlTerm], State) -> [Term] Decode a list of Luerl terms into a list of Erlang representations. - luerl:put_private(Key, Value, State) -> State. - Put a private value into Luerl that is not exposed to the runtime. - - luerl:get_private(Key, State) -> Term. - Get a private value from Luerl. - - AUTHORS Jean Chassoul, Robert Virding. diff --git a/doc/src/luerl.3.md b/doc/src/luerl.3.md index d07ca8f..592c9a8 100644 --- a/doc/src/luerl.3.md +++ b/doc/src/luerl.3.md @@ -113,3 +113,12 @@ Decode a term in the Luerl form into its Erlang representation. #### luerl:decode_list([LuerlTerm], State) -> [Term] Decode a list of Luerl terms into a list of Erlang representations. + +#### luerl:put_private(Key, Term, State) -> State. +Puts a private value under key that is not exposed to the runtime. + +#### luerl:get_private(Key, State) -> Term. +Get a private value for the given key. + +#### luerl:get_private(Key, State) -> Term. +Deletes the private value for the given key. diff --git a/include/luerl.hrl b/include/luerl.hrl index 41cd5f0..824dcac 100644 --- a/include/luerl.hrl +++ b/include/luerl.hrl @@ -1,4 +1,4 @@ -%% Copyright (c) 2013-2019 Robert Virding +%% Copyright (c) 2013-2024 Robert Virding %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -25,16 +25,17 @@ envs, %Environment table usds, %Userdata table fncs, %Function table - g, %Global table - %% - stk=[], %Current stack - cs=[], %Current call stack - %% - meta=[], %Data type metatables - rand, %Random state + g, %Global table + %% + stk=[], %Current stack + cs=[], %Current call stack + %% + meta=[], %Data type metatables + rand, %Random state tag, %Unique tag trace_func=none, %Trace function - trace_data %Trace data + trace_data, %Trace data + private=#{} }). %% Table structure. @@ -46,10 +47,10 @@ %% Metatables for atomic datatypes. -record(meta, {nil=nil, - boolean=nil, - number=nil, - string=nil - }). + boolean=nil, + number=nil, + string=nil + }). %% Frames for the call stack. %% Call return frame @@ -111,10 +112,10 @@ body}). %Code block -define(IS_LUAFUNC(F), is_record(F, lua_func)). --record(erl_func,{code}). %Erlang code (fun) +-record(erl_func,{code}). %Erlang code (fun) -define(IS_ERLFUNC(F), is_record(F, erl_func)). --record(erl_mfa,{m,f,a}). %Erlang code (MFA) +-record(erl_mfa,{m,f,a}). %Erlang code (MFA) -define(IS_ERLMFA(F), is_record(F, erl_mfa)). %% Test if it a function, of either sort. @@ -145,28 +146,31 @@ -define(SET_TABLE(N, T, Ts), maps:put(N, T, Ts)). -define(UPD_TABLE(N, Upd, Ts), maps:update_with(N, Upd, Ts)). -define(DEL_TABLE(N, Ts), maps:remove(N, Ts)). +-define(CHK_TABLE(N, Ts), maps:is_key(N, Ts)). -define(FILTER_TABLES(Pred, Ts), maps:filter(Pred, Ts)). -define(FOLD_TABLES(Fun, Acc, Ts), maps:fold(Fun, Acc, Ts)). -endif. -ifdef(TS_USE_ARRAY). -%% Use arrays to handle tables. +%% Use arrays to handle tables. We leave the default value as undefined. -define(MAKE_TABLE(), array:new()). -define(GET_TABLE(N, Ar), array:get(N, Ar)). -define(SET_TABLE(N, T, Ar), array:set(N, T, Ar)). -define(UPD_TABLE(N, Upd, Ar), - array:set(N, (Upd)(array:get(N, Ar)), Ar)). + array:set(N, (Upd)(array:get(N, Ar)), Ar)). -define(DEL_TABLE(N, Ar), array:reset(N, Ar)). +-define(CHK_TABLE(N, Ar), + ((N >= 0) andalso (array:get(N, Ar) =/= undefined))). -define(FILTER_TABLES(Pred, Ar), - ((fun (___Def) -> - ___Fil = fun (___K, ___V) -> - case Pred(___K, ___V) of - true -> ___V; - false -> ___Def - end - end, - array:sparse_map(___Fil, Ar) - end)(array:default(Ar)))). + ((fun (___Def) -> + ___Fil = fun (___K, ___V) -> + case Pred(___K, ___V) of + true -> ___V; + false -> ___Def + end + end, + array:sparse_map(___Fil, Ar) + end)(array:default(Ar)))). -define(FOLD_TABLES(Fun, Acc, Ar), array:sparse_foldl(Fun, Acc, Ar)). -endif. @@ -177,6 +181,7 @@ -define(SET_TABLE(N, T, Ts), orddict:store(N, T, Ts)). -define(UPD_TABLE(N, Upd, Ts), orddict:update(N, Upd, Ts)). -define(DEL_TABLE(N, Ts), orddict:erase(N, Ts)). +-define(CHK_TABLE(N, Ts), orddict:is_key(N, Ts)). -define(FILTER_TABLES(Pred, Ts), orddict:filter(Pred, Ts)). -define(FOLD_TABLES(Fun, Acc, Ts), orddict:fold(Fun, Acc, Ts)). -endif. @@ -188,8 +193,9 @@ -define(SET_TABLE(N, T, Pd), put(N, T)). -define(UPD_TABLE(N, Upd, Pd), put(N, (Upd)(get(N)))). -define(DEL_TABLE(N, Pd), erase(N)). --define(FILTER_TABLES(Pred, Pd), Pd). %This needs work --define(FOLD_TABLES(Fun, Acc, Pd), Pd). %This needs work +-define(CHK_TABLE(N, Pd), (get(N) =/= undefined)). +-define(FILTER_TABLES(Pred, Pd), Pd). %This needs work +-define(FOLD_TABLES(Fun, Acc, Pd), Pd). %This needs work -endif. -ifdef(TS_USE_ETS). @@ -198,13 +204,13 @@ -define(GET_TABLE(N, E), ets:lookup_element(E, N, 2)). -define(SET_TABLE(N, T, E), begin ets:insert(E, {N,T}), E end). -define(UPD_TABLE(N, Upd, E), - begin ets:update_element(E, N, {2,(Upd)(ets:lookup_element(E, N, 2))}), - E end). + begin ets:update_element(E, N, {2,(Upd)(ets:lookup_element(E, N, 2))}), + E end). -define(DEL_TABLE(N, E), begin ets:delete(E, N), E end). --define(FILTER_TABLES(Pred, E), E). %This needs work +-define(FILTER_TABLES(Pred, E), E). %This needs work -define(FOLD_TABLES(Fun, Acc, E), - ets:foldl(fun ({___K, ___T}, ___Acc) -> Fun(___K, ___T, ___Acc) end, - Acc, E)). + ets:foldl(fun ({___K, ___T}, ___Acc) -> Fun(___K, ___T, ___Acc) end, + Acc, E)). -endif. %% Define CATCH to handle deprecated get_stacktrace/0 diff --git a/src/luerl.erl b/src/luerl.erl index 95c78e0..5f92f10 100644 --- a/src/luerl.erl +++ b/src/luerl.erl @@ -47,7 +47,7 @@ -export([externalize/1,internalize/1]). %% Storing and retrieving private data --export([put_private/3,get_private/2]). +-export([put_private/3,get_private/2,delete_private/2]). %% init() -> State. @@ -514,9 +514,15 @@ internalize(S) -> %% State. %% get_private(Key, State) -> %% Value. +%% delete_private(Key, State) -> +%% Value. put_private(Key, Value, S) -> Private = maps:put(Key, Value, S#luerl.private), S#luerl{private=Private}. get_private(Key, S) -> maps:get(Key, S#luerl.private). + +delete_private(Key, S) -> + Private = maps:remove(Key, S#luerl.private), + S#luerl{private=Private}. diff --git a/test/luerl_tests.erl b/test/luerl_tests.erl index 73b6c28..dc026eb 100644 --- a/test/luerl_tests.erl +++ b/test/luerl_tests.erl @@ -47,4 +47,6 @@ private_test() -> State1 = luerl:init(), State2 = luerl:put_private(secret, <<"mysecret">>, State1), ?assertMatch(<<"mysecret">>, luerl:get_private(secret, State2)), - ?assertException(error, {badkey, missing}, luerl:get_private(missing, State2)). + ?assertException(error, {badkey, missing}, luerl:get_private(missing, State2)), + State3 = luerl:delete_private(secret, State2), + ?assertException(error, {badkey, secret}, luerl:get_private(secret, State3)). From 95e0f1202eff15c128012dbd34e8961ca52bf157 Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sat, 14 Dec 2024 08:36:01 -0500 Subject: [PATCH 6/8] update the elixir implementation --- src/Elixir.Luerl.erl | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/Elixir.Luerl.erl b/src/Elixir.Luerl.erl index 04dec8b..c13e56d 100644 --- a/src/Elixir.Luerl.erl +++ b/src/Elixir.Luerl.erl @@ -23,6 +23,8 @@ -module('Elixir.Luerl'). +-include("luerl.hrl"). + %% Basic user API to luerl. -export([init/0,gc/1, load/2,load/3,loadfile/2,loadfile/3, @@ -50,7 +52,7 @@ -export([externalize/1,internalize/1]). %% Storing and retrieving private data --export([put_private/3,get_private/2]). +-export([put_private/3,get_private/2,delete_private/2]). init() -> luerl:init(). @@ -187,5 +189,13 @@ internalize(St) -> put_private(St, K, V) -> luerl:put_private(K, V, St). -get_private(St, K) -> - luerl:get_private(K, St). +get_private(St, Key) -> + maps:get(Key, St#luerl.private, nil). + +delete_private(St, K) -> + try + luerl:delete_private(K, St) + catch + error:{badkey, _} -> + St + end. From e5f87adee74fe0609921922cc0125d6488d35dd6 Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sat, 14 Dec 2024 09:16:40 -0500 Subject: [PATCH 7/8] add tests for Elixir --- test/Elixir.Luerl_tests.erl | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 test/Elixir.Luerl_tests.erl diff --git a/test/Elixir.Luerl_tests.erl b/test/Elixir.Luerl_tests.erl new file mode 100644 index 0000000..dd392a4 --- /dev/null +++ b/test/Elixir.Luerl_tests.erl @@ -0,0 +1,25 @@ +%% Copyright (C) 2024 Robert Virding +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. + +-module('Elixir.Luerl_tests'). + +-include_lib("eunit/include/eunit.hrl"). + +private_test() -> + State1 = 'Elixir.Luerl':init(), + State2 = 'Elixir.Luerl':put_private(State1, secret, <<"mysecret">>), + ?assertMatch(<<"mysecret">>, 'Elixir.Luerl':get_private(State2, secret)), + ?assertMatch(nil, 'Elixir.Luerl':get_private(State2, missing)), + State3 = 'Elixir.Luerl':delete_private(State2, secret), + ?assertMatch(nil, 'Elixir.Luerl':get_private(State3, secret)). From 2306485d84d8c16385be620a0f17c4e4aa44952e Mon Sep 17 00:00:00 2001 From: Dave Lucia Date: Sun, 15 Dec 2024 13:50:57 -0500 Subject: [PATCH 8/8] fix docs --- doc/src/luerl.3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/src/luerl.3.md b/doc/src/luerl.3.md index 592c9a8..4575feb 100644 --- a/doc/src/luerl.3.md +++ b/doc/src/luerl.3.md @@ -120,5 +120,5 @@ Puts a private value under key that is not exposed to the runtime. #### luerl:get_private(Key, State) -> Term. Get a private value for the given key. -#### luerl:get_private(Key, State) -> Term. +#### luerl:delete_private(Key, State) -> Term. Deletes the private value for the given key.