Stack Overflow when debugging large list properties of script objects?

This is more of a curiosity than a proper need for me at this point, since I was meaning to switch away from native AppleScript lists anyway and have begun using NSArrays.

But I used to take advantage of script objects for working around AppleScript’s slow list operations. The following finishes in about half a second when executed normally. Yet I get a stack overflow when trying to debug the code. Is that just unavoidable in this particular instance?

script fastList
    property originalItems : {}
    property indexedItems : {}
end script

set fastList's originalItems to {}
repeat 20000 times
    copy "something" to the end of fastList's originalItems
end repeat
build(fastList)
return count of fastList's indexedItems

on build(fastList)
    set fastList's indexedItems to {}
    repeat with i from 1 to count of fastList's originalItems
    	    set end of fastList's indexedItems to i
	    set end of fastList's indexedItems to item i of fastList's originalItems
    end repeat
end build

Thanks!

It is supposed to work. At first I thought there may be a debugger regression in the handling of script object parameters to handlers, but this little test works fine:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

script abc
	property originalItems : {}
	property indexedItems : {}
end script

on test(anObject)
	set anObject's originalItems to {1, 2, 3, 4}
	set anObject's indexedItems to {1, 2, 3, 4}
end test

test(abc)

I notice two things:

  1. When I run your example script, buy the time we reach the build(fastList) statement, SD is reporting the value of fastList as <<empty>>. This is not a healthy sign.

  2. I find that your example works with counts up to 8112. So clearly, something SD is doing is driving AppleScript into the weeds, but its not all clear what that is.

This does not really get us anywhere. I’ll wait to see what others contribute to this topic and perhaps some piece of information will appear to help me to isolate the cause.

Great, thanks! :slight_smile:

So apparently It doesn’t really matter what happens in the handler. Even the simple example below fails in debug mode, unless the handler is called with a reference to the script object…? At that point it works. I hadn’t really tried that before.

script fastList
    property someItems : {}
end script

repeat 20000 times
    copy 1 to the end of fastList's someItems
end repeat
doNothingWith(a reference to fastList)

on doNothingWith(fastList)
    #noop
end doNothingWith

Right, but I would suggest that the failure is happening around the time the 8113th item is added to fastList's someItems. From that point onward, anything involving fastList will case a stack overflow error. Resorting to using a reference, while it works, isn’t something you should have to do.

1 Like

I just took a quick look at this time as I don’t have time to get into it in depth. I tried the script in Script editor and it works with no error.

In it’s original form script took 0.74 seconds to fail on my Mac. If I change the statement in the repeat loop to:
set fastList’s originalItems to fastList’s originalItems & "something"
it takes 4 minutes and 4.96 seconds to fail. I’m not sure that is important or not to know but it did surprise me.

I reduced the script to the simplest form that still errors which is:

use AppleScript version "2.4" -- Yosemite (10.10) or later
use scripting additions

script fastList
	property originalItems : {}
end script

on build(fastList)
	
end build

repeat 20000 times
	copy "something" to the end of fastList's originalItems
end repeat

build(fastList)

If I put a break point right at the call to build(fastList) the script runs to that point without displaying an error. Then when press the “step into” button once I get a Stack overflow error -2,706 as soon as build(fastList) is called. Given there is no code in the handler it can’t be the handler causing the problem. Just to be really sure I changed the build handler to contain the single line “set a to 5” and it still errored in exactly the same way.

Mark said it works up to 8112 iterations in the repeat loop. I tried this with the simplified script and get the same behavior 8112 works but 8113 gets an overflow error. I don’t have time now to look at this anymore. Later I can try a few more things but this looks like a fixed limit to something being reached inside of ScriptDebugger or something ScriptDebugger accesses. Once that limit is reached something corrupts and the call to handler fails. To be really, really sure I even changed the name of the handle to build2 and still got the same error.

If I remove the handler call in the “simplest form” and didn’t get any error. I put other AppleScript lines after the the repeat loop I didn’t get an error. I suspect a handler call is needed to cause the problem but do not have time to check all possible types of AS statements now.

Changing the statement in the repeat loop of the “simplest form” to

set fastList's originalItems to fastList's originalItems & "something"

the “simplest form” script runs with no errors.

I am running under El Capitan 10.11.5.

Mark,

I just noticed you had posted about the problem. You reached a lot of the same conclusions I did except I noticed changing the type assignment in the repeat loop seemed to be significant. In the simplest form (mentioned in my previous reply) it ran without errors when I changed the type of assignment, and in the original script it ran 331 times longer before erroring. The type of assignment makes some kind of difference in this problem.

The text I changed the line to was:

 set fastList's originalItems to fastList's originalItems & "something"

Bill

And yet none of these cause a stack overflow error:

script fastList
	property originalItems : {}
end script

repeat 20000 times
	set end of fastList's originalItems to "something"
end repeat
count of fastList's originalItems
originalItems of fastList
class of fastList

Yes, its interesting that the method of adding to the list makes such a difference. It could be that your approach, which creates many duplicates of the list, forces the AppleScript garbage collector into operation and that somehow clears/avoids the situation that causes the problem.

Unfortunately, since this is all happening within AppleScript we are left to guess as to what might be happening.

Hmm. The handler call in @letterman’s example is the site of the actual error, but I think AppleScript has already corrupted its self before that call is made.

Mark,

This problem is weirder then I first thought.

I ran this script below:

script fastList
	property originalItems : {}
end script

on build(fastList)
	
end build

repeat 8126 times
	set fastList's originalItems to fastList's originalItems & "something"
end repeat

build(fastList)

Note: This script uses a different (newer) assignment statement inside the repeat loop.

The first time the script ran with no problems. I ran the same script again and it failed with stack overflow. I hit the space bar and recompiled and it ran. I ran it again and it failed with stack overflow after which I forced a recompile and it worked. I did the same thing again and got the same results. Even weirder on the runs that did work it ran around 40 and completed without error. The runs which got an error all ran over 1 minute and 30 seconds and then errored. This pattern was consistent every time I tried it.

This version of the script uses a different assignment statement inside the repeat then the original assignment statement. I used the new number because all numbers over 8126 always failed while 8126 works once, then fails and works again after a recompile. That is not what I expected at all.

As soon as I changed the assignment statement in the repeat block the old 8112 work and 8113 fails pattern didn’t apply anymore. Changing the type of assignment changes how many interactions it takes to cause the error.

If you can reproduce the work once, fail once and then work after recompile I would think that would be a really big hint.

I did try one other test:

script fastList
	property originalItems : {}
end script

on build(fastList)
	
end build

repeat 8112 times
	copy "something" to the end of fastList's originalItems
	delay 0.2
end repeat

build(fastList)

I was trying to see if altering the timing of the script made any difference. It made no difference. With this second script and using the old assignment statement it works with 8112 and smaller number of iterations. Anything bigger then 8112 iterations gets stack overflow errors.

When I get weird stuff like this I keep trying things that help me figure out the general patterns of the script. I’ll just keep trying different things until I see a helpful pattern or you fix the problem.

Bill

By the way… I’ve noticed on the latest version of Script Debugger (6.0.4) this will now actually crash the application, even if it isn’t running in debug mode.

(Crashing on exception: Error -2706 received from OSAID instance)

I have another customer with a massive script that is experiencing a similar crash. I suspect something has changed in AppleScript so that it is reporting errOSAInternalTableOverflow errors in places where it did not in the past. Please try this pre-release build and let me know how it goes . If the crash persists, please submit a crash report.

OS X’s Gatekeeper is asking me to move the app to the Trash. Is this to be expected?

“Script Debugger Lite” is damaged and can’t be opened.

Sorry, my error. Please use this download link.

I would like to ask a general question regarding stack overflow.
I encounter Stack Overflow message and hangs in SD occasionally when I am in debug mode for long scripts (>1000 lines of codings excluding spaces, comments and definition of properties and variables) that involves manipulating the properties (including the plain text content of the records) and arrays of >1000 records of an application.
Is there any way to tweak the system ( e.g. clear the system memory) to prevent the Stack Overflow from happening?

Thanks

Absolutely – for a larger stack, just choose the -l JavaScript switch on the osascript command line.

If we destructively test the stack size running JXA for Automation in VSC or Atom, we get:

[31721, Maximum call stack size exceeded.]

from the following code:

(() => {
    'use strict';

    const go = i => {
        try {
            return go(1 + i)
        } catch (e) {
            return [i.toString(), e.message];
        }
    }

    return go(0)
})();

Script Editor turns out to allow for a much smaller stack space (about one order of magnitude).

["3032", "Maximum call stack size exceeded."]

for the same JS code.

But even that is drastically shrunk by using -l AppleScript rather than -l JavaScript

We get only:

"Recursion limit encountered at 502"

from:

-- recursionDepth :: () -> IO String
on recursionDepth()
    script go
        on |λ|(i)
            try
                |λ|(1 + i)
            on error
                "Recursion limit encountered at " & i
            end try
        end |λ|
    end script
    
    go's |λ|(0)
end recursionDepth

on run
    recursionDepth()
end run

(Script Debugger and Script Editor give the same result for this code)

AppleScript has many charms, but a large stack is not, alas, among them.

In short, no, there isn’t.

Thank you @ComplexPoint. But I’m afraid I don’t understand a quarter of what you explained? I worked on MS VBA 28 years ago (Excel and Access mostly), and that’s why I am comfortable to write a long script with lots of array’s manipulation. But I have only picked up programming again during the last 12 months and only on AppleScript while starting to read Shane’s book on ASObjC more recently.

I am guessing what you and Shane are saying is that AppleScript has allocated much smaller memory space for some sort of table of pointers to keep track of what’s happening to variables in the program than the other language such as Javascript?

Thank you to both Shane and you.