FR_SBUFF_SET_RETURN(in, &our_in);
}
+/** Match the bareword `null` and return a TMPL_TYPE_DATA carrying an FR_TYPE_NULL box
+ *
+ * Used as an explicit "no value" placeholder by callers that want the
+ * argument slot to remain present (so positional xlat arguments line
+ * up) without carrying any bytes. Downstream code distinguishes an
+ * intentional null from an uninitialised one by checking
+ * `fr_type_is_null(vb->type)` after the box has made it into an arg
+ * list - if it reaches the xlat body, the author put it there.
+ *
+ * @param[in] ctx to allocate tmpl to.
+ * @param[out] out where to write tmpl.
+ * @param[in] in sbuff to parse.
+ * @param[in] p_rules formatting rules.
+ * @return
+ * - 0 sbuff does not contain the `null` keyword.
+ * - > 0 how many bytes were parsed.
+ */
+static fr_slen_t tmpl_afrom_null_substr(TALLOC_CTX *ctx, tmpl_t **out, fr_sbuff_t *in,
+ fr_sbuff_parse_rules_t const *p_rules)
+{
+ fr_sbuff_t our_in = FR_SBUFF(in);
+ tmpl_t *vpt;
+
+ if (!fr_sbuff_adv_past_strcase_literal(&our_in, "null")) return 0;
+ if (!tmpl_substr_terminal_check(&our_in, p_rules)) return 0;
+
+ MEM(vpt = tmpl_alloc(ctx, TMPL_TYPE_DATA, T_BARE_WORD,
+ fr_sbuff_start(&our_in), fr_sbuff_used(&our_in)));
+ fr_value_box_init(&vpt->data.literal, FR_TYPE_NULL, NULL, false);
+
+ *out = vpt;
+
+ FR_SBUFF_SET_RETURN(in, &our_in);
+}
+
/** Parse a truth value
*
* @param[in] ctx to allocate tmpl to.
fr_assert(!*out);
}
+ /*
+ * See if it's the `null` keyword. Matched before the
+ * numeric / address / enum branches so it isn't
+ * shadowed by a dictionary attribute literally named
+ * "null".
+ */
+ slen = tmpl_afrom_null_substr(ctx, out, &our_in, p_rules);
+ if (slen > 0) goto done_bareword;
+ fr_assert(!*out);
+
/*
* See if it's a boolean value
*/
file, line);
}
- if (fr_type_is_null(tmpl_value_type(vpt))) {
- fr_fatal_assert_fail("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA type was "
- "FR_TYPE_NULL (uninitialised)", file, line);
- }
-
+ /*
+ * An FR_TYPE_NULL box inside a TMPL_TYPE_DATA used to
+ * fire here as the "you forgot to init the box" signal,
+ * but the `null` keyword (see tmpl_afrom_null_substr)
+ * deliberately constructs one. Accept it.
+ */
if (tmpl_value_type(vpt) >= FR_TYPE_MAX) {
fr_fatal_assert_fail("CONSISTENCY CHECK FAILED %s[%u]: TMPL_TYPE_DATA type was "
"%i (outside the range of fr_type_ts)", file, line, tmpl_value_type(vpt));
fr_value_box_init(dst, FR_TYPE_STRING, dst_enumv, false);
switch (src->type) {
+ /*
+ * Explicit null casts to an empty string.
+ */
+ case FR_TYPE_NULL:
+ return fr_value_box_bstrndup(ctx, dst, dst_enumv, "", 0, src->tainted);
+
/*
* The presentation format of octets is hex
* What we actually want here is the raw string
fr_value_box_safety_copy_changed(dst, src);
switch (src->type) {
+ /*
+ * An explicit null (e.g. the `null` keyword in unlang)
+ * casts to a zero-length octets box.
+ */
+ case FR_TYPE_NULL:
+ return fr_value_box_memdup(ctx, dst, dst_enumv, NULL, 0, src->tainted);
+
/*
* <string> (excluding terminating \0)
*/
case FR_TYPE_VENDOR:
case FR_TYPE_UNION:
case FR_TYPE_INTERNAL:
- case FR_TYPE_NULL:
case FR_TYPE_ATTR:
case FR_TYPE_COMBO_IP_ADDR: /* the types should have been realized to ipv4 / ipv6 */
case FR_TYPE_COMBO_IP_PREFIX: