From 1117d7ed6e664d774605a118731f42ccc61aa590 Mon Sep 17 00:00:00 2001 From: "Alan T. DeKok" Date: Thu, 1 May 2025 07:19:44 -0400 Subject: [PATCH] allow "break" inside of "case" and "switch" is then marked as the break point. Also update the "break" checks to use the flags instead of unlang types --- .../modules/reference/pages/unlang/break.adoc | 35 ++++++++++++++++--- .../modules/reference/pages/unlang/case.adoc | 14 ++++---- src/lib/unlang/compile.c | 17 ++++----- src/lib/unlang/switch.c | 2 +- src/lib/unlang/unlang_priv.h | 2 +- src/tests/keywords/switch-break | 24 +++++++++++++ 6 files changed, 72 insertions(+), 22 deletions(-) create mode 100644 src/tests/keywords/switch-break diff --git a/doc/antora/modules/reference/pages/unlang/break.adoc b/doc/antora/modules/reference/pages/unlang/break.adoc index ca105a8166..7d73843f29 100644 --- a/doc/antora/modules/reference/pages/unlang/break.adoc +++ b/doc/antora/modules/reference/pages/unlang/break.adoc @@ -7,10 +7,15 @@ break ---- The `break` statement is used to exit an enclosing -xref:unlang/foreach.adoc[foreach] loop. The `break` statement only be used -inside of a xref:unlang/foreach.adoc[foreach] loop. +xref:unlang/foreach.adoc[foreach] loop or a +xref:unlang/case.adoc[case] statement. The `break` statement cannot +be used in any other location. -.Example +In this example, a `break` is used to exit a +xref:unlang/foreach.adoc[foreach] loop when a particular condition +matches. + +.Example of break within foreach [source,unlang] ---- foreach i (Class) { @@ -24,5 +29,27 @@ foreach i (Class) { } ---- -// Copyright (C) 2021 Network RADIUS SAS. Licenced under CC-by-NC 4.0. +In the next example, a `break` is used to exit a +xref:unlang/case.adoc[case] statement, which then also exits the +parent xref:unlang/switch.adoc[switch] statement + +.Example of break within case / switch +[source,unlang] +---- +switch User-Name { + case 'bob' { + if (NAS-IP-Address == 192.0.2.1) { + break + } + + reject + } + + default { + ok + } +} +---- + +// Copyright (C) 2025 Network RADIUS SAS. Licenced under CC-by-NC 4.0. // This documentation was developed by Network RADIUS SAS. diff --git a/doc/antora/modules/reference/pages/unlang/case.adoc b/doc/antora/modules/reference/pages/unlang/case.adoc index ee6ae70c72..1fa872f56f 100644 --- a/doc/antora/modules/reference/pages/unlang/case.adoc +++ b/doc/antora/modules/reference/pages/unlang/case.adoc @@ -17,12 +17,14 @@ cannot be an attribute expansion, or an `xlat` xref:xlat/index.adoc[string]. The keyword `default` can be used to specify the default action to -take inside of a xref:unlang/switch.adoc[switch] statement. - -If no __ text is given, it means that the `case` statement is -the "default" and will match all which is not matched by another -`case` statement inside of the same xref:unlang/switch.adoc[switch]. -This syntax is deprecated, and will be removed in a future release. +take inside of a xref:unlang/switch.adoc[switch] statement. The older +syntax of using `case { ... }` is deprecated, and will be removed un a +future release. + +It is possible to xref:unlang/break.adoc[break] out of `case` +statement. Any xref:unlang/break.adoc[break] in a `case` statement +will cause the interpreter to exit both the current `case` statement, +and also the parent xref:unlang/switch.adoc[switch] statement. .Example [source,unlang] diff --git a/src/lib/unlang/compile.c b/src/lib/unlang/compile.c index 4ce5372ccd..f396f06179 100644 --- a/src/lib/unlang/compile.c +++ b/src/lib/unlang/compile.c @@ -3426,7 +3426,7 @@ static unlang_t *compile_foreach(unlang_t *parent, unlang_compile_t *unlang_ctx, static unlang_t *compile_break(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF_ITEM const *ci) { - unlang_t *foreach; + unlang_t *unlang; static unlang_ext_t const break_ext = { .type = UNLANG_TYPE_BREAK, @@ -3434,21 +3434,18 @@ static unlang_t *compile_break(unlang_t *parent, unlang_compile_t *unlang_ctx, C .type_name = "unlang_group_t", }; - for (foreach = parent; foreach != NULL; foreach = foreach->parent) { + for (unlang = parent; unlang != NULL; unlang = unlang->parent) { /* - * A "break" inside of a "policy" is an error. - * We CANNOT allow "break" inside of a policy to - * affect a "foreach" loop outside of that - * policy. + * "break" doesn't go past a return point. */ - if (foreach->type == UNLANG_TYPE_POLICY) goto error; + if ((unlang_ops[unlang->type].flag & UNLANG_OP_FLAG_RETURN_POINT) != 0) goto error; - if (foreach->type == UNLANG_TYPE_FOREACH) break; + if ((unlang_ops[unlang->type].flag & UNLANG_OP_FLAG_BREAK_POINT) != 0) break; } - if (!foreach) { + if (!unlang) { error: - cf_log_err(ci, "'break' can only be used in a 'foreach' section"); + cf_log_err(ci, "Invalid location for 'break' - it can only be used inside 'foreach' or 'switch'"); cf_log_err(ci, DOC_KEYWORD_REF(break)); return NULL; } diff --git a/src/lib/unlang/switch.c b/src/lib/unlang/switch.c index 719dd8299c..fefab2abd1 100644 --- a/src/lib/unlang/switch.c +++ b/src/lib/unlang/switch.c @@ -147,6 +147,6 @@ void unlang_switch_init(void) &(unlang_op_t){ .name = "case", .interpret = unlang_case, - .flag = UNLANG_OP_FLAG_DEBUG_BRACES + .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_BREAK_POINT }); } diff --git a/src/lib/unlang/unlang_priv.h b/src/lib/unlang/unlang_priv.h index 5f8f28ff6c..18e1a95b5f 100644 --- a/src/lib/unlang/unlang_priv.h +++ b/src/lib/unlang/unlang_priv.h @@ -57,7 +57,7 @@ typedef enum { UNLANG_TYPE_SWITCH, //!< Switch section. UNLANG_TYPE_CASE, //!< Case section (within a #UNLANG_TYPE_SWITCH). UNLANG_TYPE_FOREACH, //!< Foreach section. - UNLANG_TYPE_BREAK, //!< Break statement (within a #UNLANG_TYPE_FOREACH). + UNLANG_TYPE_BREAK, //!< Break statement (within a #UNLANG_TYPE_FOREACH or #UNLANG_TYPE_CASE). UNLANG_TYPE_RETURN, //!< Return statement. UNLANG_TYPE_MAP, //!< Mapping section (like #UNLANG_TYPE_UPDATE, but uses //!< values from a #map_proc_t call). diff --git a/src/tests/keywords/switch-break b/src/tests/keywords/switch-break new file mode 100644 index 0000000000..c857cb7c45 --- /dev/null +++ b/src/tests/keywords/switch-break @@ -0,0 +1,24 @@ +# +# PRE: switch +# + +switch User-Name { + case "bob" { + if (User-Password == "hello") { + success + break + } + + test_fail + } + + case "doug" { + Filter-Id := "doug" + test_fail + } + + default { + Filter-Id := "default" + test_fail + } +} -- 2.47.2