]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
allow "break" inside of "case"
authorAlan T. DeKok <aland@freeradius.org>
Thu, 1 May 2025 11:19:44 +0000 (07:19 -0400)
committerAlan T. DeKok <aland@freeradius.org>
Thu, 1 May 2025 11:52:02 +0000 (07:52 -0400)
and "switch" is then marked as the break point.

Also update the "break" checks to use the flags instead of
unlang types

doc/antora/modules/reference/pages/unlang/break.adoc
doc/antora/modules/reference/pages/unlang/case.adoc
src/lib/unlang/compile.c
src/lib/unlang/switch.c
src/lib/unlang/unlang_priv.h
src/tests/keywords/switch-break [new file with mode: 0644]

index ca105a8166c2282c5ae0f531d79d96725dae1ba4..7d73843f29674a1898a2f17a5b7ef635eb85496f 100644 (file)
@@ -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.
index ee6ae70c72ce6ee6f163f5321bde64f8383cb37d..1fa872f56f4dd395ff3e7d65ee7fb6147e0f98d3 100644 (file)
@@ -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 _<match>_ 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]
index 4ce5372ccd4081ce30ea0a52627fbfb6f3c4e3f1..f396f0617969e7cea12be27b6fb27b2aba983534 100644 (file)
@@ -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;
        }
index 719dd8299c31de58ae7958d10c9a8b9755eb06fa..fefab2abd10e5ca073d52a7a4cdb6ad546ebf7e9 100644 (file)
@@ -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
                           });
 }
index 5f8f28ff6c487306837582f3527faa3d00b3002a..18e1a95b5f4e296aa30a55ad25a9ee9380898ebb 100644 (file)
@@ -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 (file)
index 0000000..c857cb7
--- /dev/null
@@ -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
+       }
+}