"Stack overflow -2706" - Need help on logic flow

One of my applet’s users has been getting seemingly random crashes with the error “Stack overflow -2706”. It may be relevant that the user keeps the applet open for days (perhaps weeks). When needed, they switch to the applet to use it.

I suspect the problem is nesting of handlers. So, for example, Handler A calls Handler B which calls Handler A which calls Handler B and so on. Both handlers display a dialog to do things. The calls do not pass parameters as all variables required are global.

Could that cause a Stack Overflow if done enough times ? If so, can someone suggest a better way to handle program flow from dialog to dialog ?

Thanks,

That is a good way to cause an infinite recursion and run out of stack! When a handler’s called, the return address and the current values of any local variables are placed on the stack. On the return from the handler, they’re retrieved and the stack space they occupied is released. If the handler calls another handler or calls itself, more data are added to the stack. Since nested handlers return in the reverse order from that in which they were called, the data are retrieved from the stack in the reverse order from that in which they were called. But if the handlers keep calling each other without ever returning, the stack eventually fills up and overflows.

Nigel, thanks. I’ve been trying to design a better way to control program flow amongst the handlers.

I have three handlers in particular which are stored in the one script file and which provide three of the applet’s most used dialogs. The user is using one of those dialogs at nearly all times. One dialog is the “main” dialog the others are “admin” and “settings”. On main dialog the user requests admin or settings, does what they need (e.g. changing settings) then returns to the main dialog. Thus currently, the main dialog handler has a call to “admin()” and admin has a call to “main()”. Those calls are necessary so that processing goes back to code which creates the dialogs. Without those calls, processing would continue after the dialog code and the applet would stop running.

One idea I’ve had is to add a “return flag” to the calls in each handler and a repeat loop outside of those handlers which catches the content of the return flags when they come back and based on that content calls the required handler. The repeat loop would be in the implied on run handler.

For example, the repeat loop might have this:

set branch_execution to main()
repeat
   if branch_execution is "admin" then
      set branch_execution to admin()
   end if
   if branch_execution is "main" then
      set branch_execution to main()
   end if
   if branch_execution is "settings" then
      set branch_execution to settings()
   end if
end repeat

The “admin” and “settings” handlers would have at the end:

set branch_execution to "main"
return branch_execution

Thus the “admin” handler is sending processing via the repeat loop to the “main” handler and so on. main() already has a call to quit_applet() which does housekeeping before quitting the applet.

Using a repeat loop seems crude but I can’t think of another way to keep processing going. Can anyone suggest alternatives ?

Depending on the complexity of your dialogs (and any particular requirement that the main one is always shown), one option to avoid the repeat statement (and any recursion) would be to create a NSStatusItem with menu items that would call their respective handlers. There wouldn’t be any recursion issues since the individual handlers would just be called as their menu items are selected.

Neophyte, It would be nice if you posted your script.

Thanks. The dialogs are created with Shane Stanley’s Dialog Toolkit. A while back I tried adding some ASOC but got a pile of grief. Had to isolate the ASOC code inside a script object and subsequently moved it to another script file. I have tried a few times to learn ASOC and failed. If I was starting today, I would develop the applet in SwiftUI.

Here’s a link to the text and a truncated listing – beware, it’s over 4,000 lines:

Thanks all.

Wow, that thing is huge. I thought some of my scripts were long. Looks like you’ve been working on that for a while - I also have one of those projects that has been getting tweaked over the last 10 years or so as I’ve learned stuff, but it hasn’t been AppleScript for quite a while. I see some easy(ish) optimizations for localizations and some of those simple if...then...else assignments, but it must be really tricky to maintain and/or adjust with those massive handlers.

Putting some functionality into a status item isn’t too bad, most of it is creating the menu. The menu and action handlers can be grouped together, they would just call other handlers. I know what you are talking about with script objects, though - that is some voodoo/jedi stuff. The main thing would be to try not using a repeat statement at all, letting the app be event driven - the user selects items for the app to perform from the menu.

Many thanks for the suggestions and ideas. Yes, it’s been a while – started in 2017. I wanted a tool to download videos and thought AppleScript could do it. I posted an early version on a web site and got a good response then moved to GitHub. I get a few messages from users each month which help keep me going. main.scpt is big – every year or so I have to move handlers to another file to avoid [overflows ?].

I botched most of the solutions to problems from posts on MacScripter, Stack Overflow, Apple Discussions and especially the Late Night Software forum. The project would have died years ago but for Shane Stanley’s help and his free tools. I do lament the coming cessation of Script Debugger.

After all that, I still don’t really understand a lot. What are: status item, action handler, event driven ?

That sounds very useful – I’ve not had time to keep the localisation strings up to date. Spanish is the best because one user has sent suggestions. The rest are about 3 years old.

At present I don’t have skill enough to do more than the repeat loop idea. I’ve looked at getting menus working but the dialogs created with Dialog Toolkit are modal and so inhibit menu items, even Quit.

By the way, regarding that one handler which contains ASOC, putting it inside a script object meant I didn’t have to rename every variable in the project – I didn’t follow best practice with variable naming. That’s another reason why I’ve resisted doing more ASOC.

Cheers.

Without wading through the entire script, it’s difficult to understand the problem. Unless admin() and settings() also have to be able to call each other, the simplest solution would seem to be to have main() in a repeat and make its last action a call whichever of the other two is required. The chosen handler simply returns when finished, the program flow returns to main() and then immediately to the repeat, which then goes round again. But there may be something in the query I haven’t grasped.

A slightly simpler version of this idea, since you’re using globals anyway, would be to have the exiting handler set a global (or property) to the next handler. This can then be used instead of the actual label to call the next handler. eg.:

global branch_execution

-- Initialise the global to one of the handlers (not to what the handler returns).
set branch_execution to main
repeat
	-- Use the global instead of the handler label to call the handler.
	branch_execution()
end repeat

on main()
	-- The 'my's are needed to distinguish the handler labels from variables local to this handler.
	if (blah) then
		set branch_execution to my admin
	else -- if (blah_blah)
		set branch_execution to my settings
	end if
end main

on admin()
	-- Blah blah blah
	set branch_execution to my main
end admin

on settings()
	-- Blah blah blah
	set branch_execution to my main
end settings

A NSStatusItem is one of those user menus in the right side of the menu bar. It is handy for a menu you want visible/available all the time like a timer/clock or utility. The menu bar is a limited resource, but sometimes it can work better than keeping an app window around.

An action handler is one that gets called by various UI objects (menu items, buttons, etc) when they are used.

Most macOS applications are event driven - that is, they don’t have a set program flow or use repeats or poll or whatever, the user decides the flow by interacting with the UI, and the system sends the app the corresponding events or calls its action handlers. A status item is like that - it just sits there until a menu item is selected. For an example (be aware that compiling a script will remove connections to any of its remaining UI objects):

use framework "Foundation"
use scripting additions

property |+| : current application -- just a shortcut (never mind that it resembles a first aid kit)

# outlets
property statusItem : missing value -- button title, etc
property statusMenu : missing value -- item enable, state, etc


on run -- example
   if |+|'s NSThread's isMainThread() as boolean then -- applet
      initialize()
   else -- running from a script editor
      my performSelectorOnMainThread:"initialize" withObject:(missing value) waitUntilDone:true
   end if
end run

to initialize() -- set stuff up
   buildStatusItem()
   # other setup stuff as desired
end initialize

on admin() -- single action handler with no argument
   display dialog "admin..." giving up after 2
   # whatever
end admin

on otherStuff:sender -- common action handler with argument (the object sending the event)
   set theItem to sender's title as text -- or other distinguishing property
   display dialog theItem & "..." giving up after 2
   # whatever
end otherStuff:

to terminate() -- used as a selector for the scripting term "quit"
   quit
end terminate

to quit
   display dialog "Terminating Status Item..." giving up after 2
   # clean up, save preferences, etc
   |+|'s NSStatusBar's systemStatusBar's removeStatusItem:statusItem -- just the current item
   # continue quit -- for an app
end quit

to buildStatusItem()
   tell (|+|'s NSStatusBar's systemStatusBar's statusItemWithLength:(|+|'s NSVariableStatusItemLength))
      set my statusItem to it
      its (button's setTitle:"Example")
      its setMenu:(my buildStatusMenu())
   end tell
end buildStatusItem

to buildStatusMenu()
   tell (|+|'s NSMenu's alloc()'s initWithTitle:"")
      set my statusMenu to it
      its setAutoenablesItems:false -- manual enable/disable
      my (addMenuItem to it given title:"Administration", action:"admin")
      my (addMenuItem to it given title:"Settings", action:"otherStuff:")
      my (addMenuItem to it given title:"Other Stuff", action:"otherStuff:")
      my (addMenuItem to it) ----
      my (addMenuItem to it given title:"Quit", action:"terminate")
      return it
   end tell
end buildStatusMenu

# Add a menuItem to a menu - sectionHeaderWithTitle: convenience method is for macOS 14+, so just use an attributedString.
to addMenuItem to theMenu given title:title as text : "", header:header as boolean : false, action:action as text : "", theKey:theKey as text : "", tag:tag as integer : 0, enabled:enabled : (missing value), state:state : (missing value), target:target : (missing value) -- given parameters are optional
   if title is in {"", "missing value"} then return theMenu's addItem:(current application's NSMenuItem's separatorItem)
   if action is in {"", "missing value"} then set action to missing value
   if header then tell (theMenu's addItemWithTitle:"" action:(missing value) keyEquivalent:"")
      set attrTitle to |+|'s NSMutableAttributedString's alloc()'s initWithString:title
      attrTitle's addAttribute:(|+|'s NSFontAttributeName) value:(|+|'s NSFont's fontWithName:"System Font Bold" |size|:11) range:{0, attrTitle's |length|()}
      its setAttributedTitle:attrTitle
      its setEnabled:false
      return it
   end tell
   tell (theMenu's addItemWithTitle:title action:action keyEquivalent:theKey)
      if action is not missing value then its setTarget:(item (((target is missing value) as integer) + 1) of {target, me})
      if tag > 0 then its setTag:tag
      if enabled is not missing value then its setEnabled:(item (((enabled is false) as integer) + 1) of {true, false})
      if state is not missing value then its setState:(my clamp(-1, state, 1)) -- 1=on (✓), 0=off, -1=mixed (-)
      return it
   end tell
end addMenuItem

to clamp(min as integer, value as integer, max as integer)
   if value < min then return min
   if value > max then return max
   return value
end clamp

I mentioned localization because I noticed that there are a lot places where you use a bunch of individual statements to set variables to localized strings, which then just get used to set another variable or are passed to a handler. The localizations mostly use the same table/bundle, so a couple of utility handlers with optional/default parameters could be used. I don’t have any localized scripts to test, but for a piece of the Formats.applescript file it would be something like:

# Localize a single string.
on localized_string for (term as text) given table:(table as text) : "MacYTDL", bundlePath:(bundlePath as text) : "" -- given parameters are optional
   if bundlePath is in {"", "missing value"} then
      return localized string term from table table
   else
      return localized string term from table table in bundle (file bundlePath)
   end if
end localized_string

# Localize a list of strings.
on localized_list for (terms as list) given table:(table as text) : "MacYTDL", bundlePath:(bundlePath as text) : "" -- given parameters are optional
   set output to {}
   repeat with anItem in terms
      set end of output to (localized_string for anItem given table:table, bundlePath:bundlePath)
   end repeat
   return output
end localized_list

# the heading localization statements could then be reduced to:
set format_chooser_headings to {"", "ID"} & ¬
   (localized_list for {"Extension", ¬
      "Resolution", ¬
      "File size", ¬
      "Bitrate", ¬
      "Video Codec", ¬
      "Audio Codec"} given bundlePath:path_to_MacYTDL)

Fewer variables, slightly shorter script (but could add up), a little easier to read.

First, I’d just like to mention that there’s nothing wrong with your script. You get these “overflow” errors due to bugs in the AppleScript engine itself. It’s long overdue for Apple to eliminate the “stack overflow” and “internal table overflow” errors. There’s no reason for them to happen. We also need to keep bombarding Apple with demands to eliminate these errors (which is a good reminder to myself to do just that).

Now, in addition to other suggestions:

You may want to reduce the number of global variables (preferably to zero). This is based on Shane Stanley’s recommendations as well as my own experience.

For example, you can write them to a file (or files), then read from there when needed. Yes, it will make your code more verbose in some places. But it can, potentially, help eliminate the overflow errors (based on my experience). It might as well not make any difference.

All we can do is try to deal with these errors by trying various workarounds, none of which is guaranteed to help. That’s why Apple must address it as soon as possible so that developers don’t waste their time trying to fix these bugs that should never have had to happen in the first place.

Many thanks Nigel and Leo. I should explain that I’ve had one report of Stack Overflow in the life of the project from a user who keeps the applet open. I’ve advised the user that there is a Service which automates the applet’s process and which can be assigned to a shortcut meaning no need to leave the applet open.

Luckily, they don’t call each other but, they do call themselves during error checking. For example if user turns on a setting but doesn’t provide needed details: dialog closes; do error checking; error found; tell user; [if users asks for it] redisplay dialog.

Haven’t seen that done before. Your suggestion should work for this applet. I’ll give it a try – but, will take a few days.

Many thanks for the definitions. Will do some background reading too.

I’ve seen something like that before but not understood. I need to read up on “given”. Does this approach reduce overheads or speed up processing ? My current approach often means the localization is done once and the resulting label variable used many times. For example,

set theButtonOKLabel to localized string "OK" from table "MacYTDL"

I pass theButtonOKLabel to handlers in dozens of places including every “display dialog”. I have assumed that would require less overhead than doing a localization lookup every time.

I doubt Apple takes notice of any suggestions or complaints about AppleScript. This opens up the whole issue of what future is there for scripting on macOS. I get the feeling GUI scripting is dying if not dead.

Interesting idea given the significant number of globals I have. I guess your idea would make most variables inside handlers local.

Many thanks,

cheers.

There are some built-in labels that can be used in a handler declaration with labeled parameters, any user labels need to use given. The main advantage is that labeled parameters allow setting up default values, and if at least one labeled argument is used then those with default values are also optional. For example, in the localization handler sample I posted, you would only need to use a given label if you wanted to use something different than the default. I chose the format_chooser_headings statements as an example because that looked like the only place those particular intermediate variables were used. I also posted a recent topic in Code Exchange that has another example of using labeled parameters.

You wouldn’t necessarily be doing a localization lookup every time, it is just a way to reduce the statement count by using a localization handler call directly instead of a single-use intermediate variable. The handler call is also shorter where the default values can be used, particularly if you are localizing a list.