From 1522eca64c35ec26bb363565832a005ab03c14b1 Mon Sep 17 00:00:00 2001 From: Simon Halvorsen Date: Thu, 2 Jul 2026 19:15:33 +0200 Subject: [PATCH] ENT-10199: Fixed a bug regarding the resolution of namespaces inside function-calls Ticket: ENT-10199 Changelog: Title Signed-off-by: Simon Halvorsen --- libpromises/fncall.c | 15 +- .../namespace-scope_resolution_in_call.cf | 262 ++++++++++++++++++ 2 files changed, 276 insertions(+), 1 deletion(-) create mode 100644 tests/acceptance/01_vars/01_basic/namespaces/namespace-scope_resolution_in_call.cf diff --git a/libpromises/fncall.c b/libpromises/fncall.c index 3056d58132..8f79d39321 100644 --- a/libpromises/fncall.c +++ b/libpromises/fncall.c @@ -68,6 +68,8 @@ Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, cons return RlistCopy(fp->args); } + assert(fp != NULL); + const FnCallType *fn = FnCallTypeGet(fp->name); if (fn == NULL) { @@ -101,7 +103,18 @@ Rlist *NewExpArgs(EvalContext *ctx, const Policy *policy, const FnCall *fp, cons } else { - rval = ExpandPrivateRval(ctx, NULL, NULL, rp->val.item, rp->val.type); + const Bundle *caller_bundle = fp->caller ? PromiseGetBundle(fp->caller) : NULL; + const char *caller_ns = caller_bundle ? caller_bundle->ns : NULL; + rval = ExpandPrivateRval(ctx, caller_ns, NULL, rp->val.item, rp->val.type); + /* + * Scope is intentinally left as NULL, since this function is called when expanding vars inside function-calls + * E.g. A bundle write, defined in a namespace "ns" with the var "file", then scope is already set (write) + * expression => read_module_protocol( "$(write.file)" ); + * but the namespace needs to be added as it is missing. + * + * expression => read_module_protocol( "$($(this.namespace):write.file)" ); + * would insert ns correctly without this fix since the internal ns would resolve first :ENT-10199 + */ assert(rval.item); } diff --git a/tests/acceptance/01_vars/01_basic/namespaces/namespace-scope_resolution_in_call.cf b/tests/acceptance/01_vars/01_basic/namespaces/namespace-scope_resolution_in_call.cf new file mode 100644 index 0000000000..94f1537caf --- /dev/null +++ b/tests/acceptance/01_vars/01_basic/namespaces/namespace-scope_resolution_in_call.cf @@ -0,0 +1,262 @@ +############################################################################### +# ENT-10199 - namespace/scope resolution +# +# Data bundles live in ns_a, ns_b, default. Caller bundles live in ns_a, ns_b, +# default. +# Each caller tests: +# - bare same-bundle var -> always PASS +# - unqualified ref to data in ITS OWN namespace -> PASS post-fix +# - unqualified ref to data in the OTHER non-default ns -> FAIL always +# - unqualified ref to data in default -> FAIL always +# - explicit fully-qualified cross-ns ref -> always PASS +############################################################################### +# Default namespace +############################################################################### +bundle agent __main__ +{ + methods: + "data_default"; + "ns_a:data_a"; + "ns_b:data_b"; + "caller_default"; + "ns_a:caller_a"; + "ns_b:caller_b"; + "check"; +} + +bundle agent data_default +{ + vars: + "val" string => "VALUE_FROM_DEFAULT"; +} + +bundle agent caller_default +{ + vars: + "val" string => "LOCAL_DEFAULT"; + "bare" string => format("%s", "$(val)"); + "own_ns" string => format("%s", "$(data_default.val)"); + "other_ns_a" string => format("%s", "$(data_a.val)"); + "other_ns_b" string => format("%s", "$(data_b.val)"); + "qualified_a" string => format("%s", "$(ns_a:data_a.val)"); + "qualified_b" string => format("%s", "$(ns_b:data_b.val)"); + + reports: + "[caller_default] bare => '$(bare)' (expect: LOCAL_DEFAULT)"; + "[caller_default] own_ns => '$(own_ns)' (expect: VALUE_FROM_DEFAULT - baseline, always worked)"; + "[caller_default] other_ns_a => '$(other_ns_a)' (expect: unresolved - not a bug)"; + "[caller_default] other_ns_b => '$(other_ns_b)' (expect: unresolved - not a bug)"; + "[caller_default] qualified_a => '$(qualified_a)' (expect: VALUE_FROM_NS_A)"; + "[caller_default] qualified_b => '$(qualified_b)' (expect: VALUE_FROM_NS_B)"; +} + +bundle agent check +{ + classes: + # caller_a + "check_01" + expression => and( + isvariable("ns_a:caller_a.bare"), + strcmp("$(ns_a:caller_a.bare)", "LOCAL_A") + ); + + "check_02" + expression => and( + isvariable("ns_a:caller_a.own_ns"), + strcmp("$(ns_a:caller_a.own_ns)", "VALUE_FROM_NS_A") + ); + + "check_03" expression => not(isvariable("ns_a:caller_a.other_ns")); + "check_04" expression => not(isvariable("ns_a:caller_a.default_ns")); + + "check_05" + expression => and( + isvariable("ns_a:caller_a.qualified"), + strcmp("$(ns_a:caller_a.qualified)", "VALUE_FROM_NS_B") + ); + + # caller_b + "check_06" + expression => and( + isvariable("ns_b:caller_b.bare"), + strcmp("$(ns_b:caller_b.bare)", "LOCAL_B") + ); + + "check_07" + expression => and( + isvariable("ns_b:caller_b.own_ns"), + strcmp("$(ns_b:caller_b.own_ns)", "VALUE_FROM_NS_B") + ); + + "check_08" expression => not(isvariable("ns_b:caller_b.other_ns")); + "check_09" expression => not(isvariable("ns_b:caller_b.default_ns")); + + "check_10" + expression => and( + isvariable("ns_b:caller_b.qualified"), + strcmp("$(ns_b:caller_b.qualified)", "VALUE_FROM_NS_A") + ); + + # caller_default + "check_11" + expression => and( + isvariable("default:caller_default.bare"), + strcmp("$(default:caller_default.bare)", "LOCAL_DEFAULT") + ); + + "check_12" + expression => and( + isvariable("default:caller_default.own_ns"), + strcmp("$(default:caller_default.own_ns)", "VALUE_FROM_DEFAULT") + ); + + "check_13" + expression => not(isvariable("default:caller_default.other_ns_a")); + + "check_14" + expression => not(isvariable("default:caller_default.other_ns_b")); + + "check_15" + expression => and( + isvariable("default:caller_default.qualified_a"), + strcmp("$(default:caller_default.qualified_a)", "VALUE_FROM_NS_A") + ); + + "check_16" + expression => and( + isvariable("default:caller_default.qualified_b"), + strcmp("$(default:caller_default.qualified_b)", "VALUE_FROM_NS_B") + ); + + "check_17" + expression => and( + isvariable("ns_a:caller_a.qualified_a_a"), + strcmp("$(ns_a:caller_a.qualified_a_a)", "VALUE_FROM_NS_A") + ); + + "check_18" expression => not(isvariable("ns_a:caller_a.qualified_a_b")); + + "check_19" + expression => and( + isvariable("ns_b:caller_b.qualified_a_a"), + strcmp("$(ns_b:caller_b.qualified_a_a)", "VALUE_FROM_NS_A") + ); + + "check_20" expression => not(isvariable("ns_b:caller_b.qualified_a_b")); + + "all_passed" + expression => and( + check_01, + check_02, + check_03, + check_04, + check_05, + check_06, + check_07, + check_08, + check_09, + check_10, + check_11, + check_12, + check_13, + check_14, + check_15, + check_16, + check_17, + check_18, + check_19, + check_20 + ); + + reports: + all_passed:: + "$(this.promise_filename) Pass"; + + !all_passed:: + "$(this.promise_filename) Fail"; +} + +############################################################################### +# DATA bundles +############################################################################### +body file control +{ + namespace => "ns_a"; +} + +bundle agent data_a +{ + vars: + "val" string => "VALUE_FROM_NS_A"; +} + +body file control +{ + namespace => "ns_b"; +} + +bundle agent data_b +{ + vars: + "val" string => "VALUE_FROM_NS_B"; +} + +############################################################################### +# CALLER in ns_a +############################################################################### +body file control +{ + namespace => "ns_a"; +} + +bundle agent caller_a +{ + vars: + "val" string => "LOCAL_A"; + "bare" string => format("%s", "$(val)"); + "own_ns" string => format("%s", "$(data_a.val)"); + "other_ns" string => format("%s", "$(data_b.val)"); + "default_ns" string => format("%s", "$(data_default.val)"); + "qualified" string => format("%s", "$(ns_b:data_b.val)"); + "qualified_a_a" string => format("%s", "$(ns_a:data_a.val)"); + "qualified_a_b" string => format("%s", "$(ns_a:data_b.val)"); + + reports: + "[caller_a] bare => '$(bare)' (expect: LOCAL_A)"; + "[caller_a] own_ns => '$(own_ns)' (expect: VALUE_FROM_NS_A - ENT-10199 Fixes this)"; + "[caller_a] other_ns => '$(other_ns)' (expect: unresolved - not a bug)"; + "[caller_a] default_ns => '$(default_ns)' (expect: unresolved - not a bug)"; + "[caller_a] qualified => '$(qualified)' (expect: VALUE_FROM_NS_B)"; + "[caller_a] qualified_a_a => '$(qualified_a_a)' (expect: VALUE_FROM_NS_A)"; + "[caller_a] qualified_a_b => '$(qualified_a_b)' (expect: unresolved - not a bug)"; +} + +############################################################################### +# CALLER in ns_b +############################################################################### +body file control +{ + namespace => "ns_b"; +} + +bundle agent caller_b +{ + vars: + "val" string => "LOCAL_B"; + "bare" string => format("%s", "$(val)"); + "own_ns" string => format("%s", "$(data_b.val)"); + "other_ns" string => format("%s", "$(data_a.val)"); + "default_ns" string => format("%s", "$(data_default.val)"); + "qualified" string => format("%s", "$(ns_a:data_a.val)"); + "qualified_a_a" string => format("%s", "$(ns_a:data_a.val)"); + "qualified_a_b" string => format("%s", "$(ns_a:data_b.val)"); + + reports: + "[caller_b] bare => '$(bare)' (expect: LOCAL_B)"; + "[caller_b] own_ns => '$(own_ns)' (expect: VALUE_FROM_NS_B - ENT-10199 Fixes this)"; + "[caller_b] other_ns => '$(other_ns)' (expect: unresolved - not a bug)"; + "[caller_b] default_ns => '$(default_ns)' (expect: unresolved - not a bug)"; + "[caller_b] qualified => '$(qualified)' (expect: VALUE_FROM_NS_A)"; + "[caller_b] qualified_a_a => '$(qualified_a_a)' (expect: VALUE_FROM_NS_A)"; + "[caller_b] qualified_a_b => '$(qualified_a_b)' (expect: unresolved - not a bug)"; +}