From 79b614f993f621166c8219ee35016152ae49b9fa Mon Sep 17 00:00:00 2001
From: Albert Graef <aggraef@gmail.com>
Date: Tue, 22 Jun 2021 15:33:20 +0200
Subject: [PATCH] mrpeach/unpackOSC: Refactored for better efficiency, fix
 stack overflows on Windows.

---
 externals/mrpeach/osc/unpackOSC.c | 94 ++++++++++++++++++++-----------
 1 file changed, 60 insertions(+), 34 deletions(-)

diff --git a/externals/mrpeach/osc/unpackOSC.c b/externals/mrpeach/osc/unpackOSC.c
index 54b8b9b97..f5330202f 100644
--- a/externals/mrpeach/osc/unpackOSC.c
+++ b/externals/mrpeach/osc/unpackOSC.c
@@ -64,7 +64,7 @@ The OSC webpage is http://cnmat.cnmat.berkeley.edu/OpenSoundControl
         on OSX anyway.
 
 */
-//define DEBUG
+//#define DEBUG
 #include "packingOSC.h"
 
 
@@ -118,31 +118,27 @@ void unpackOSC_setup(void)
 }
 
 /* unpackOSC_list expects an OSC packet in the form of a list of floats on [0..255] */
+static void unpackOSC_worker(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv, char *buf);
 static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv) 
 {
-    int             size, messageLen, i, j;
-    char            *messageName, *args, *buf;
-    OSCTimeTag      tt;
-    t_atom          data_at[MAX_MESG] ={{0}};/* symbols making up the path + payload */
-    int             data_atc = 0;/* number of symbols to be output */
-    char            raw[MAX_MESG];/* bytes making up the entire OSC message */
-    int             raw_c;/* number of bytes in OSC message */
-
-#ifdef DEBUG
-    printf(">>> unpackOSC_list: %d bytes, abort=%d, reentry_count %d recursion_level %d\n",
-        argc, x->x_abort_bundle, x->x_reentry_count, x->x_recursion_level);
-#endif
-    if(x->x_abort_bundle) return; /* if backing quietly out of the recursive stack */
-    x->x_reentry_count++;
+    // ag: This is a small wrapper around unpackOSC_worker below which does all the real
+    // work. It does some preliminary checks and then sets up a buffer populated with the
+    // binary data of the message which then gets passed to the worker. That way we don't have
+    // to reparse the data over and over again in each recursive call to the worker, as was
+    // done in the original implementation, wasting a lot of time and stack space.
+    int             i, j;
+    // This can be static as it's only read by the worker, never modified.
+    static char     raw[MAX_MESG];/* bytes making up the entire OSC message */
+    // preliminary checks
     if ((argc%4) != 0)
     {
         post("unpackOSC: Packet size (%d) not a multiple of 4 bytes: dropping packet", argc);
-        goto unpackOSC_list_out;
+        return;
     }
     if(argc > MAX_MESG)
     {
         post("unpackOSC: Packet size (%d) greater than max (%d). Change MAX_MESG and recompile if you want more.", argc, MAX_MESG);
-        goto unpackOSC_list_out;
+        return;
     }
     /* copy the list to a byte buffer, checking for bytes only */
     for (i = 0; i < argc; ++i)
@@ -160,17 +156,47 @@ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv)
             else
             {
                 post("unpackOSC: Data out of range (%d), dropping packet", argv[i].a_w.w_float);
-                goto unpackOSC_list_out;
+                return;
             }
         }
         else
         {
             post("unpackOSC: Data not float, dropping packet");
-            goto unpackOSC_list_out;
+            return;
         }
     }
-    raw_c = argc;
-    buf = raw;
+    unpackOSC_worker(x, s, argc, argv, raw);
+}
+
+static void unpackOSC_worker(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv, char *buf) 
+{
+    int             size, messageLen, i;
+    char            *messageName, *args;
+    OSCTimeTag      tt;
+    // ag: The original implementation reserved an excessive amount of storage on the stack
+    // here which caused the object to crash with a C stack overflow on Windows. We use a more
+    // reasonable limit here (as each element of an OSC message must consist of at least 4 bytes,
+    // MAX_MESG/4 should always be big enough), and we can also allocate the memory in static
+    // storage, since it is only used in the non-recursive part of the function.
+    static t_atom   data_at[MAX_MESG/4] ={{0}};/* symbols making up the path + payload */
+    int             data_atc = 0;/* number of symbols to be output */
+
+#ifdef DEBUG
+    printf(">>> unpackOSC_worker: %d bytes, abort=%d, reentry_count %d recursion_level %d\n",
+        argc, x->x_abort_bundle, x->x_reentry_count, x->x_recursion_level);
+#endif
+    if(x->x_abort_bundle) return; /* if backing quietly out of the recursive stack */
+    x->x_reentry_count++;
+    if ((argc%4) != 0)
+    {
+        post("unpackOSC: Packet size (%d) not a multiple of 4 bytes: dropping packet", argc);
+        goto unpackOSC_worker_out;
+    }
+    if(argc > MAX_MESG)
+    {
+        post("unpackOSC: Packet size (%d) greater than max (%d). Change MAX_MESG and recompile if you want more.", argc, MAX_MESG);
+        goto unpackOSC_worker_out;
+    }
 
     if ((argc >= 8) && (strncmp(buf, "#bundle", 8) == 0))
     { /* This is a bundle message. */
@@ -181,7 +207,7 @@ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv)
         if (argc < 16)
         {
             post("unpackOSC: Bundle message too small (%d bytes) for time tag", argc);
-            goto unpackOSC_list_out;
+            goto unpackOSC_worker_out;
         }
 
         x->x_bundle_flag = 1;
@@ -207,13 +233,13 @@ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv)
             if ((size % 4) != 0)
             {
                 post("unpackOSC: Bad size count %d in bundle (not a multiple of 4)", size);
-                goto unpackOSC_list_out;
+                goto unpackOSC_worker_out;
             }
             if ((size + i + 4) > argc)
             {
                 post("unpackOSC: Bad size count %d in bundle (only %d bytes left in entire bundle)",
                     size, argc-i-4);
-                goto unpackOSC_list_out;
+                goto unpackOSC_worker_out;
             }
 
             /* Recursively handle element of bundle */
@@ -225,13 +251,13 @@ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv)
             {
                 post("unpackOSC: bundle depth %d exceeded", MAX_BUNDLE_NESTING);
                 x->x_abort_bundle = 1;/* we need to back out of the recursive stack*/
-                goto unpackOSC_list_out;
+                goto unpackOSC_worker_out;
             }
 #ifdef DEBUG
-            printf("unpackOSC: bundle calling unpackOSC_list(x=%p, s=%s, size=%d, argv[%d]=%p)\n",
+            printf("unpackOSC: bundle calling unpackOSC_worker(x=%p, s=%s, size=%d, argv[%d]=%p)\n",
               x, s->s_name, size, i+4, &argv[i+4]);
 #endif
-            unpackOSC_list(x, s, size, &argv[i+4]);
+            unpackOSC_worker(x, s, size, &argv[i+4], buf+i+4);
             i += 4 + size;
         }
 
@@ -249,20 +275,20 @@ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv)
     else if ((argc == 24) && (strcmp(buf, "#time") == 0))
     {
         post("unpackOSC: Time message: %s\n :).\n", buf);
-        goto unpackOSC_list_out;
+        goto unpackOSC_worker_out;
     }
     else
     { /* This is not a bundle message or a time message */
 
         messageName = buf;
 #ifdef DEBUG
-        printf("unpackOSC: message name string: %s length %d\n", messageName, raw_c);
+        printf("unpackOSC: message name string: %s length %d\n", messageName, argc);
 #endif
-        args = unpackOSC_DataAfterAlignedString(messageName, buf+raw_c);
+        args = unpackOSC_DataAfterAlignedString(messageName, buf+argc);
         if (args == 0)
         {
             post("unpackOSC: Bad message name string: Dropping entire message.");
-            goto unpackOSC_list_out;
+            goto unpackOSC_worker_out;
         }
         messageLen = args-messageName;
         /* put the OSC path into a single symbol */
@@ -270,9 +296,9 @@ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv)
         if (data_atc == 1)
         {
 #ifdef DEBUG
-            printf("unpackOSC_list calling unpackOSC_Smessage: message length %d\n", raw_c-messageLen);
+            printf("unpackOSC_worker calling unpackOSC_Smessage: message length %d\n", argc-messageLen);
 #endif
-            unpackOSC_Smessage(data_at, &data_atc, (void *)args, raw_c-messageLen);
+            unpackOSC_Smessage(data_at, &data_atc, (void *)args, argc-messageLen);
             if (0 == x->x_bundle_flag)
                 outlet_float(x->x_delay_out, 0); /* no delay for message not in a bundle */
         }
@@ -281,7 +307,7 @@ static void unpackOSC_list(t_unpackOSC *x, t_symbol *s, int argc, t_atom *argv)
         outlet_anything(x->x_data_out, atom_getsymbol(data_at), data_atc-1, data_at+1);
     data_atc = 0;
     x->x_abort_bundle = 0;
-unpackOSC_list_out:
+unpackOSC_worker_out:
     x->x_recursion_level = 0;
     x->x_reentry_count--;
 }
-- 
GitLab