poke-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[PATCH 3/9] pkl: disallow immediate break/continue/return in EXCOND


From: Mohammad-Reza Nabipoor
Subject: [PATCH 3/9] pkl: disallow immediate break/continue/return in EXCOND
Date: Thu, 28 Dec 2023 02:19:28 +0100

This commit disallow change in control flow from EXCOND expressions
which the LHS of the operator is a compound statement.

The following Poke snippets are now illegal:

Sample 1:

  whlie (condition)
    { break; } ?! E_elem;

Sample 2:

  whlie (condition)
    { continue; } ?! E_elem;

Sample 3:

  fun f = int:
    {
      { return 1; } ?! E_elem;
      return 0;
    }

2023-12-27  Mohammad-Reza Nabipoor  <mnabipoor@gnu.org>

        * libpoke/pkl-anal.h (struct pkl_anal_excond_ctx): New struct.
        (struct pkl_anal_payload): Add new fields for EXCOND-related
        analysis.
        * libpoke/pkl-anal.c (pkl_anal1_pr_comp_stmt): New phase.
        (pkl_anal1_pr_loop_stmt): Likewise.
        (pkl_anal1_ps_loop_stmt): Likewise.
        (pkl_anal1_ps_comp_stmt): Add checks to disallow
        break/continue/return statements in EXCOND's compound statement.
        (pkl_anal1_pr_func): Likewise.
        (pkl_anal1_ps_func): Likewise.
        (pkl_anal1_ps_break_continue_stmt): Likewise.
        (pkl_anal1_ps_return_stmt): Likewise.
        (pkl_phase_anal1): Register new phases.
        * testsuite/poke.pkl/return-3.pk: Adapt to new semantics.
        * testsuite/poke.pkl/return-4.pk: Likewise.
        * testsuite/poke.pkl/return-5.pk: Likewise.
        * testsuite/poke.pkl/return-6.pk: Removed.
        * testsuite/poke.pkl/return-diag-5.pk: New test.
        * testsuite/poke.pkl/excond-diag-2.pk: Likewise.
        * testsuite/poke.pkl/excond-diag-3.pk: Likewise.
        * testsuite/poke.pkl/excond-6.pk: Likewise.
        * testsuite/Makefile.am (EXTRA_DIST): Update.
---
 ChangeLog                           |  25 ++++++
 libpoke/pkl-anal.c                  | 118 ++++++++++++++++++++++++++--
 libpoke/pkl-anal.h                  |  24 +++++-
 testsuite/Makefile.am               |   5 +-
 testsuite/poke.pkl/excond-6.pk      |  20 +++++
 testsuite/poke.pkl/excond-diag-2.pk |   4 +
 testsuite/poke.pkl/excond-diag-3.pk |   9 +++
 testsuite/poke.pkl/return-3.pk      |   6 +-
 testsuite/poke.pkl/return-4.pk      |   2 +-
 testsuite/poke.pkl/return-5.pk      |   2 +-
 testsuite/poke.pkl/return-6.pk      |   6 --
 testsuite/poke.pkl/return-diag-5.pk |   3 +
 12 files changed, 206 insertions(+), 18 deletions(-)
 create mode 100644 testsuite/poke.pkl/excond-6.pk
 create mode 100644 testsuite/poke.pkl/excond-diag-2.pk
 create mode 100644 testsuite/poke.pkl/excond-diag-3.pk
 delete mode 100644 testsuite/poke.pkl/return-6.pk
 create mode 100644 testsuite/poke.pkl/return-diag-5.pk

diff --git a/ChangeLog b/ChangeLog
index 20b1356a..bb2738a4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,28 @@
+2023-12-27  Mohammad-Reza Nabipoor  <mnabipoor@gnu.org>
+
+       * libpoke/pkl-anal.h (struct pkl_anal_excond_ctx): New struct.
+       (struct pkl_anal_payload): Add new fields for EXCOND-related
+       analysis.
+       * libpoke/pkl-anal.c (pkl_anal1_pr_comp_stmt): New phase.
+       (pkl_anal1_pr_loop_stmt): Likewise.
+       (pkl_anal1_ps_loop_stmt): Likewise.
+       (pkl_anal1_ps_comp_stmt): Add checks to disallow
+       break/continue/return statements in EXCOND's compound statement.
+       (pkl_anal1_pr_func): Likewise.
+       (pkl_anal1_ps_func): Likewise.
+       (pkl_anal1_ps_break_continue_stmt): Likewise.
+       (pkl_anal1_ps_return_stmt): Likewise.
+       (pkl_phase_anal1): Register new phases.
+       * testsuite/poke.pkl/return-3.pk: Adapt to new semantics.
+       * testsuite/poke.pkl/return-4.pk: Likewise.
+       * testsuite/poke.pkl/return-5.pk: Likewise.
+       * testsuite/poke.pkl/return-6.pk: Removed.
+       * testsuite/poke.pkl/return-diag-5.pk: New test.
+       * testsuite/poke.pkl/excond-diag-2.pk: Likewise.
+       * testsuite/poke.pkl/excond-diag-3.pk: Likewise.
+       * testsuite/poke.pkl/excond-6.pk: Likewise.
+       * testsuite/Makefile.am (EXTRA_DIST): Update.
+
 2023-12-25  Mohammad-Reza Nabipoor  <mnabipoor@gnu.org>
 
        * libpoke/pkl-ast.h (PKL_AST_STRUCT_TYPE_FIELD_CONSTRAINT_LOC):
diff --git a/libpoke/pkl-anal.c b/libpoke/pkl-anal.c
index 00c94c54..5914bae5 100644
--- a/libpoke/pkl-anal.c
+++ b/libpoke/pkl-anal.c
@@ -71,6 +71,34 @@
       PKL_ANAL_PAYLOAD->next_context--;                 \
     } while (0)
 
+#define PKL_ANAL_EXCOND                                                 \
+  (PKL_ANAL_PAYLOAD->next_excond == 0                                   \
+   ? NULL                                                               \
+   : &PKL_ANAL_PAYLOAD->exconds[PKL_ANAL_PAYLOAD->next_excond - 1])
+
+#define PKL_ANAL_PUSH_EXCOND                                                  \
+  do                                                                          \
+    {                                                                         \
+      if (PKL_ANAL_PAYLOAD->next_excond >= PKL_ANAL_MAX_COMP_STMT_NEST)       \
+        {                                                                     \
+          PKL_ERROR (PKL_AST_NOLOC,                                           \
+                     "maximum nested EXCOND nesting level reached");          \
+          PKL_PASS_ERROR;                                                     \
+        }                                                                     \
+      PKL_ANAL_PAYLOAD->exconds[PKL_ANAL_PAYLOAD->next_excond].nfunc = 0;     \
+      PKL_ANAL_PAYLOAD->exconds[PKL_ANAL_PAYLOAD->next_excond].nloop = 0;     \
+      PKL_ANAL_PAYLOAD->next_excond++;                                        \
+    }                                                                         \
+  while (0)
+
+#define PKL_ANAL_POP_EXCOND                                                   \
+  do                                                                          \
+    {                                                                         \
+      assert (PKL_ANAL_PAYLOAD->next_excond > 0);                             \
+      PKL_ANAL_PAYLOAD->next_excond--;                                        \
+    }                                                                         \
+  while (0)
+
 /* The following handler is used in all anal phases, and initializes
    the phase payload.  */
 
@@ -239,8 +267,25 @@ PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_type_struct)
 }
 PKL_PHASE_END_HANDLER
 
+/* If the compound statement is the first operand of an excond
+   operator, then having any kind of constructs that escape the
+   current lexical scope (break, continue, return) is disallowed.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_anal1_pr_comp_stmt)
+{
+  if (PKL_PASS_PARENT
+      && PKL_AST_CODE (PKL_PASS_PARENT) == PKL_AST_EXP
+      && PKL_AST_EXP_CODE (PKL_PASS_PARENT) == PKL_AST_OP_EXCOND)
+    {
+      PKL_ANAL_PUSH_EXCOND;
+    }
+}
+PKL_PHASE_END_HANDLER
+
 /* Builtin compound statements can't contain statements
-   themselves.  */
+   themselves.
+
+   For EXCOND's comp_stmt, see the comments for PKL_ANAL_PR_COMP_STMT.  */
 
 PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_comp_stmt)
 {
@@ -254,6 +299,13 @@ PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_comp_stmt)
       PKL_ANAL_PAYLOAD->errors++;
       PKL_PASS_ERROR;
     }
+
+  if (PKL_PASS_PARENT
+      && PKL_AST_CODE (PKL_PASS_PARENT) == PKL_AST_EXP
+      && PKL_AST_EXP_CODE (PKL_PASS_PARENT) == PKL_AST_OP_EXCOND)
+    {
+      PKL_ANAL_POP_EXCOND;
+    }
 }
 PKL_PHASE_END_HANDLER
 
@@ -315,19 +367,26 @@ PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_funcall)
 }
 PKL_PHASE_END_HANDLER
 
-/* Methods introduce an analysis context.  */
+/* Methods introduce an analysis context.
+
+   Track functions when we're in EXCOND's comp_stmt.  */
 
 PKL_PHASE_BEGIN_HANDLER (pkl_anal1_pr_func)
 {
   if (PKL_AST_FUNC_METHOD_P (PKL_PASS_NODE))
     PKL_ANAL_PUSH_CONTEXT (PKL_ANAL_CONTEXT_METHOD);
+
+  if (PKL_ANAL_EXCOND)
+    PKL_ANAL_EXCOND->nfunc++;
 }
 PKL_PHASE_END_HANDLER
 
 /* Check that all optional formal arguments in a function specifier
    are at the end of the arguments list, and other checks.
 
-   Also, pop the analysis context if this was a method.  */
+   Also, pop the analysis context if this was a method.
+
+   Track functions when we're in EXCOND's comp_stmt.  */
 
 PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_func)
 {
@@ -365,6 +424,9 @@ PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_func)
 
   if (PKL_AST_FUNC_METHOD_P (PKL_PASS_NODE))
     PKL_ANAL_POP_CONTEXT;
+
+  if (PKL_ANAL_EXCOND)
+    PKL_ANAL_EXCOND->nfunc--;
 }
 PKL_PHASE_END_HANDLER
 
@@ -393,8 +455,28 @@ PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_type_function)
 }
 PKL_PHASE_END_HANDLER
 
+/* Track loops when we're in EXCOND's comp_stmt.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_anal1_pr_loop_stmt)
+{
+  if (PKL_ANAL_EXCOND)
+    PKL_ANAL_EXCOND->nloop++;
+}
+PKL_PHASE_END_HANDLER
+
+/* Track loops when we're in EXCOND's comp_stmt.  */
+
+PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_loop_stmt)
+{
+  if (PKL_ANAL_EXCOND)
+    PKL_ANAL_EXCOND->nloop--;
+}
+PKL_PHASE_END_HANDLER
+
 /* Make sure every BREAK and CONTINUE statement has an associated
-   entity.  */
+   entity.
+
+   Disallow immediate BREAK or CONTINUE in EXCOND's comp_stmt.  */
 
 PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_break_continue_stmt)
 {
@@ -411,11 +493,25 @@ PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_break_continue_stmt)
       PKL_ANAL_PAYLOAD->errors++;
       PKL_PASS_ERROR;
     }
+
+  if (PKL_ANAL_EXCOND && PKL_ANAL_EXCOND->nloop == 0)
+    {
+      int kind = PKL_AST_BREAK_CONTINUE_STMT_KIND (stmt);
+
+      PKL_ERROR (PKL_AST_LOC (stmt),
+                 "%s statement without containing statement in EXCOND (?!) "
+                 "operator", kind == PKL_AST_BREAK_CONTINUE_STMT_KIND_BREAK
+                 ? "`break'" : "`continue'");
+      PKL_ANAL_PAYLOAD->errors++;
+      PKL_PASS_ERROR;
+    }
 }
 PKL_PHASE_END_HANDLER
 
 /* Every return statement should be associated with a containing
-   function.  */
+   function.
+
+   Disallow immediate RETURN statement in EXCOND's comp_stmt.  */
 
 PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_return_stmt)
 {
@@ -428,6 +524,15 @@ PKL_PHASE_BEGIN_HANDLER (pkl_anal1_ps_return_stmt)
       PKL_ANAL_PAYLOAD->errors++;
       PKL_PASS_ERROR;
     }
+
+  if (PKL_ANAL_EXCOND && PKL_ANAL_EXCOND->nfunc == 0)
+    {
+      PKL_ERROR (PKL_AST_LOC (return_stmt),
+                 "`return' statement without containing function in "
+                 "EXCOND (?!) operator");
+      PKL_ANAL_PAYLOAD->errors++;
+      PKL_PASS_ERROR;
+    }
 }
 PKL_PHASE_END_HANDLER
 
@@ -759,7 +864,10 @@ struct pkl_phase pkl_phase_anal1 =
    PKL_PHASE_PR_HANDLER (PKL_AST_PROGRAM, pkl_anal_pr_program),
    PKL_PHASE_PS_HANDLER (PKL_AST_PROGRAM, pkl_anal_ps_program),
    PKL_PHASE_PS_HANDLER (PKL_AST_STRUCT, pkl_anal1_ps_struct),
+   PKL_PHASE_PR_HANDLER (PKL_AST_COMP_STMT, pkl_anal1_pr_comp_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_COMP_STMT, pkl_anal1_ps_comp_stmt),
+   PKL_PHASE_PR_HANDLER (PKL_AST_LOOP_STMT, pkl_anal1_pr_loop_stmt),
+   PKL_PHASE_PS_HANDLER (PKL_AST_LOOP_STMT, pkl_anal1_ps_loop_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_BREAK_CONTINUE_STMT, 
pkl_anal1_ps_break_continue_stmt),
    PKL_PHASE_PS_HANDLER (PKL_AST_FUNCALL, pkl_anal1_ps_funcall),
    PKL_PHASE_PR_HANDLER (PKL_AST_FUNC, pkl_anal1_pr_func),
diff --git a/libpoke/pkl-anal.h b/libpoke/pkl-anal.h
index 29c2dd47..e181a845 100644
--- a/libpoke/pkl-anal.h
+++ b/libpoke/pkl-anal.h
@@ -22,6 +22,19 @@
 #include <config.h>
 #include "pkl-pass.h"
 
+/* In order to disallow immediate break/continue/return statements
+   in EXCOND's compound statement, we need to identifiy them by tracking
+   function definitions, lambda definitions and loops inside them.
+
+   NFUNC and NLOOP are, respectively, the number of functions and loops
+   being visited inside EXCOND's compound statement.  */
+
+struct pkl_anal_excond_ctx
+{
+  int nfunc;
+  int nloop;
+};
+
 /* The following struct defines the payload of the analysis phases.
 
    ERRORS is the number of errors detected while running the phase.
@@ -31,9 +44,16 @@
 
    If NEXT_CONTEXT is 0 then we are not in any particular context
    (NO_CONTEXT) otherwise NEXT_CONTEXT - 1 is the index of the current
-   context in the array CONTEXT.  */
+   context in the array CONTEXT.
+
+   EXCONDS is a stack of EXCOND's analysis data.
+
+   If NEXT_EXCOND is 0 then we are not in any compound statement of
+   EXCOND, otherwise NEXT_EXCOND - 1 is the index of the current
+   active entry in the array EXCONDS.  */
 
 #define PKL_ANAL_MAX_CONTEXT_NEST 32
+#define PKL_ANAL_MAX_COMP_STMT_NEST 32
 
 #define PKL_ANAL_NO_CONTEXT 0
 #define PKL_ANAL_CONTEXT_STRUCT_TYPE 1
@@ -44,6 +64,8 @@ struct pkl_anal_payload
   int errors;
   int context[PKL_ANAL_MAX_CONTEXT_NEST];
   int next_context;
+  struct pkl_anal_excond_ctx exconds[PKL_ANAL_MAX_COMP_STMT_NEST];
+  int next_excond;
 };
 
 typedef struct pkl_anal_payload *pkl_anal_payload;
diff --git a/testsuite/Makefile.am b/testsuite/Makefile.am
index ae6fb924..91183eed 100644
--- a/testsuite/Makefile.am
+++ b/testsuite/Makefile.am
@@ -1392,7 +1392,10 @@ EXTRA_DIST = \
   poke.pkl/excond-3.pk \
   poke.pkl/excond-4.pk \
   poke.pkl/excond-5.pk \
+  poke.pkl/excond-6.pk \
   poke.pkl/excond-diag-1.pk \
+  poke.pkl/excond-diag-2.pk \
+  poke.pkl/excond-diag-3.pk \
   poke.pkl/field-init-1.pk \
   poke.pkl/field-init-2.pk \
   poke.pkl/field-init-3.pk \
@@ -2310,11 +2313,11 @@ EXTRA_DIST = \
   poke.pkl/return-3.pk \
   poke.pkl/return-4.pk \
   poke.pkl/return-5.pk \
-  poke.pkl/return-6.pk \
   poke.pkl/return-diag-1.pk \
   poke.pkl/return-diag-2.pk \
   poke.pkl/return-diag-3.pk \
   poke.pkl/return-diag-4.pk \
+  poke.pkl/return-diag-5.pk \
   poke.pkl/rla-int-1.pk \
   poke.pkl/rla-offset-1.pk \
   poke.pkl/rtrace-1.pk \
diff --git a/testsuite/poke.pkl/excond-6.pk b/testsuite/poke.pkl/excond-6.pk
new file mode 100644
index 00000000..3f588284
--- /dev/null
+++ b/testsuite/poke.pkl/excond-6.pk
@@ -0,0 +1,20 @@
+/* { dg-do run } */
+
+fun f = int:
+  {
+    var i = 0;
+
+    if ({
+          while (1)
+            break;
+          while (0)
+            continue;
+          i = lambda int: { return 1; } ();
+        } ?! E_elem)
+     return 2;
+
+    return i;
+  }
+
+/* { dg-command { f } } */
+/* { dg-output "1" } */
diff --git a/testsuite/poke.pkl/excond-diag-2.pk 
b/testsuite/poke.pkl/excond-diag-2.pk
new file mode 100644
index 00000000..de355b17
--- /dev/null
+++ b/testsuite/poke.pkl/excond-diag-2.pk
@@ -0,0 +1,4 @@
+/* { dg-do compile } */
+
+while (1)
+  { break; } ?! E_elem; /* { dg-error "`break' statement without containing 
statement in EXCOND" } */
diff --git a/testsuite/poke.pkl/excond-diag-3.pk 
b/testsuite/poke.pkl/excond-diag-3.pk
new file mode 100644
index 00000000..a5257bcd
--- /dev/null
+++ b/testsuite/poke.pkl/excond-diag-3.pk
@@ -0,0 +1,9 @@
+/* { dg-do compile } */
+
+while (1)
+  {
+    if ({ continue; } ?! E_elem) /* { dg-error "`continue' statement without 
containing statement in EXCOND" } */
+      continue;
+    else
+      break;
+  }
diff --git a/testsuite/poke.pkl/return-3.pk b/testsuite/poke.pkl/return-3.pk
index 5501d3d9..7e42e3c9 100644
--- a/testsuite/poke.pkl/return-3.pk
+++ b/testsuite/poke.pkl/return-3.pk
@@ -1,6 +1,6 @@
 /* { dg-do run } */
 
-fun f = int: { { return 1; } ?! E_elem; return 0; }
+fun f = int: { var i = 0; { i = 1; } ?! E_elem; return i; }
 
-/* { dg-command {f} } */
-/* { dg-output "1" } */
+/* { dg-command {try { f; raise E_inval; } catch if E_inval { print "caught"; 
} } } */
+/* { dg-output "caught" } */
diff --git a/testsuite/poke.pkl/return-4.pk b/testsuite/poke.pkl/return-4.pk
index 178f2671..be8d1567 100644
--- a/testsuite/poke.pkl/return-4.pk
+++ b/testsuite/poke.pkl/return-4.pk
@@ -1,6 +1,6 @@
 /* { dg-do run } */
 
-fun f = int: { { raise E_elem; return 1; } ?! E_elem; return 0; }
+fun f = int: { var i = 0; { raise E_elem; i = 1; } ?! E_elem; return i; }
 
 /* { dg-command {f} } */
 /* { dg-output "0" } */
diff --git a/testsuite/poke.pkl/return-5.pk b/testsuite/poke.pkl/return-5.pk
index 0396d823..b6859e7e 100644
--- a/testsuite/poke.pkl/return-5.pk
+++ b/testsuite/poke.pkl/return-5.pk
@@ -1,6 +1,6 @@
 /* { dg-do run } */
 
-fun f = int: { { raise E_eof; return 1; } ?! E_elem; return 0; }
+fun f = int: { var i = 0; { raise E_eof; i = 1; } ?! E_elem; return i; }
 
 /* { dg-command {try f; catch if E_eof { print "caught"; } } } */
 /* { dg-output "caught" } */
diff --git a/testsuite/poke.pkl/return-6.pk b/testsuite/poke.pkl/return-6.pk
deleted file mode 100644
index d95ef78d..00000000
--- a/testsuite/poke.pkl/return-6.pk
+++ /dev/null
@@ -1,6 +0,0 @@
-/* { dg-do run } */
-
-fun f = int: { { return 1; } ?! E_elem; return 0; }
-
-/* { dg-command {try { f; raise E_inval; } catch if E_inval { print "caught"; 
} } } */
-/* { dg-output "caught" } */
diff --git a/testsuite/poke.pkl/return-diag-5.pk 
b/testsuite/poke.pkl/return-diag-5.pk
new file mode 100644
index 00000000..d228b38f
--- /dev/null
+++ b/testsuite/poke.pkl/return-diag-5.pk
@@ -0,0 +1,3 @@
+/* { dg-do compile } */
+
+fun f = int: { { return 1; } ?! E_elem; return 0; }  /* { dg-error "`return' 
statement without containing function in EXCOND" } */
-- 
2.42.1




reply via email to

[Prev in Thread] Current Thread [Next in Thread]