Skip to content

Commit 2bfa768

Browse files
committed
tmpl, value, xlat: carry null through arg lists instead of casting it
Reverses the cast coercion added in 3b51650. An explicit `null` should not silently become "" or zero-length octets - callers that wrote `null` meant "no value at all", which is a different shape from "the empty string". value.c: fr_value_box_cast_to_{string,octets} now return a clean fr_strerror() on FR_TYPE_NULL source instead of falling through to the catch-all fr_assert(0). xlat_tokenize.c: xlat_validate_function_arg skips the compile-time cast for FR_TYPE_NULL literals so a bareword `null` survives arg validation. xlat_eval.c: the runtime concat and per-box cast paths both pass an FR_TYPE_NULL source through to the xlat body unchanged, so implementations can check fr_type_is_null() on the incoming box and react accordingly.
1 parent 998a682 commit 2bfa768

3 files changed

Lines changed: 33 additions & 6 deletions

File tree

src/lib/unlang/xlat_eval.c

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -462,8 +462,12 @@ static xlat_action_t xlat_process_arg_list(TALLOC_CTX *ctx, fr_value_box_list_t
462462

463463
/*
464464
* Concatenate child boxes, then cast to the desired type.
465+
* Skip for an explicit `null` - concatenating would force
466+
* a cast of FR_TYPE_NULL to the arg's declared type, which
467+
* would either error or silently coerce to a zero-length
468+
* value. Let the null box reach check_types intact.
465469
*/
466-
if (concat) {
470+
if (concat && !(fr_value_box_list_num_elements(list) == 1 && fr_type_is_null(vb->type))) {
467471
if (fr_value_box_list_concat_in_place(ctx, vb, list, type, FR_VALUE_BOX_LIST_FREE, true, SIZE_MAX) < 0) {
468472
RPEDEBUG("Function \"%s\" failed concatenating arguments to type %s", name, fr_type_to_str(type));
469473
return XLAT_ACTION_FAIL;
@@ -473,6 +477,8 @@ static xlat_action_t xlat_process_arg_list(TALLOC_CTX *ctx, fr_value_box_list_t
473477
goto check_types;
474478
}
475479

480+
if (concat) goto check_types;
481+
476482
/*
477483
* Only a single child box is valid here. Check there is
478484
* just one, cast to the correct type
@@ -490,6 +496,16 @@ static xlat_action_t xlat_process_arg_list(TALLOC_CTX *ctx, fr_value_box_list_t
490496
check_types:
491497
if (!fr_type_is_leaf(arg->type)) goto check_non_leaf;
492498

499+
/*
500+
* FR_TYPE_NULL is an explicit "no value" placeholder
501+
* (the `null` keyword). Passing it through to the
502+
* xlat body lets the implementation distinguish it
503+
* from a zero-length value of the declared type; the
504+
* author opted-in by writing `null` in the source.
505+
* Casting it would paper over the distinction.
506+
*/
507+
if (fr_type_is_null(vb->type)) return XLAT_ACTION_DONE;
508+
493509
/*
494510
* Cast to the correct type if necessary.
495511
*/

src/lib/unlang/xlat_tokenize.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,14 @@ static int xlat_validate_function_arg(xlat_arg_parser_t const *arg_p, xlat_exp_t
340340
return 0;
341341
}
342342

343+
/*
344+
* An explicit `null` literal is preserved unchanged - the xlat
345+
* body receives an FR_TYPE_NULL box in this arg slot and can
346+
* decide what to do with it. Casting would collapse it into a
347+
* zero-length value of the declared type and hide the intent.
348+
*/
349+
if (fr_type_is_null(node->data.type)) return 0;
350+
343351
/*
344352
* Cast (or parse) the input data to the expected argument data type.
345353
*/

src/lib/util/value.c

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2616,10 +2616,12 @@ static inline int fr_value_box_cast_to_strvalue(TALLOC_CTX *ctx, fr_value_box_t
26162616

26172617
switch (src->type) {
26182618
/*
2619-
* Explicit null casts to an empty string.
2619+
* An explicit `null` has no representation to cast from.
2620+
* Refuse rather than silently coerce to an empty string.
26202621
*/
26212622
case FR_TYPE_NULL:
2622-
return fr_value_box_bstrndup(ctx, dst, dst_enumv, "", 0, src->tainted);
2623+
fr_strerror_const("Cannot cast null to a string");
2624+
return -1;
26232625

26242626
/*
26252627
* The presentation format of octets is hex
@@ -2674,11 +2676,12 @@ static inline int fr_value_box_cast_to_octets(TALLOC_CTX *ctx, fr_value_box_t *d
26742676

26752677
switch (src->type) {
26762678
/*
2677-
* An explicit null (e.g. the `null` keyword in unlang)
2678-
* casts to a zero-length octets box.
2679+
* An explicit `null` has no representation to cast from.
2680+
* Refuse rather than silently coerce to zero-length octets.
26792681
*/
26802682
case FR_TYPE_NULL:
2681-
return fr_value_box_memdup(ctx, dst, dst_enumv, NULL, 0, src->tainted);
2683+
fr_strerror_const("Cannot cast null to octets");
2684+
return -1;
26822685

26832686
/*
26842687
* <string> (excluding terminating \0)

0 commit comments

Comments
 (0)