NSAppleScript memory leak

Platform: macOS 12.3.1 Xcode: 13.3

I had Xcode ARC turned ON. AppleScript should be declared like below. NSAppleScript *script = [[NSAppleScript alloc] initWithSource:strScript];

When call AppleScript, open "Monitor.app" app will observe some memory leak. I try to wrap AppleScript by autoReleasePool. Memery leak still exists @autoreleasepool { NSAppleScript *script = [[NSAppleScript alloc] initWithSource:strScript]; //script to do something } Is there other suggestion to do gabage collection to release memory?

How are you checking for leaks? I ran a test here — I created a new app that constructs an NSAppleScript with a trivial script — and it doesn’t seem to leak when I run it under the Leaks instrument.

When call AppleScript, open "Monitor.app" app will observe some memory leak.

I’m unfamiliar with an app called “Monitor”.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I do memory inspection by Activity monitor.app

Are you referring to the Memory tab in the main Activity Monitor window you get by choosing Window > Activity Monitor? If so, how do you get it to show leaks?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I test NSAppleScript to move desktop windows, src as below. And memory increase faster when moving windows. When stop moving window, memory usage increase as well. It seems memory not be released somehow.

NSString *command = [NSString stringWithFormat:

    //@"#!/bin/bash\nosascript <<EOF\n"

    @"try\n"

    @"tell application "System Events"\n"

        @"tell process "%@"\n"

            @"tell window %d\n"

                @"set {position, size} to {{%d,%d},{%d,%d}}\n"

            @"end tell\n"

        @"end tell\n"

    @"end tell\n"

    @"on error errMsg number errorNumber\n"

        @"return "Error with number: " & errorNumber as text\n"

    @"end try", ownerName, winIdx, (int)rect.origin.x, (int)rect.origin.y, (int)rect.size.width, (int)rect.size.height];

    NSDictionary* errorDict;

    NSAppleEventDescriptor* returnDescriptor = NULL;

    NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:command];

    returnDescriptor = [scriptObject executeAndReturnError: &errorDict];

OK, that’s likely to be memory growth rather than a memory leak.

A memory leak is where the code allocates some memory and then completely forgets about it. There are no references to the memory and so the code can’t possibly release it. The Leaks instrument is the standard way of debugging those. I recommend that you start by running it to confirm that you don’t have any leaks.

Memory growth may or may not be a problem depending on the exact circumstances. One potential issue here is called abandoned memory. This is where the code does have a reference to the memory but never releases it anyway. A classic example of abandoned memory is a buggy cache, where the cache grows without bounds.

However, there are other cases where memory growth is simply expected behaviour. For example, the AppleScript subsystem is unlikely to be used in your app otherwise, so the first time you fire up NSAppleScript that’ll allocate a whole bunch of memory for that subsystem.

The best way to tease these apart is to run generational analysis with the Allocations instrument. See Instruments Help > Profile your app’s memory usage > Find abandoned memory.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

 The first time you fire up NSAppleScript that’ll allocate a whole bunch of memory for that subsystem. -> Is there a way to avoid allocating a bunch of memory? How to release memory when end of NSAppleScript command?

Is there a way to avoid allocating a bunch of memory?

Probably not. AppleScript is a big check o’ infrastructure so it’s not surprising that it needs memory to run.

How to release memory when end of NSAppleScript command?

Not that I’m aware of.

Why is this such a concern? While AppleScript does use a bunch of memory, it’s not particularly significant given the capabilities of modern Macs. Moreover, if you don’t use it again, macOS will eventually page it out [1] when the system comes under memory pressure.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] The read-only stuff will just get dropped while the read/write stuff will be written to swap.

When use NSApleScript very often, memory will growth frequently. Can application trigger macOS page out or limit target application only use defined memory?

Can application trigger macOS page out

On macOS the VM system will automatically write pages to swap when the system is short on memory. What else are you looking for here?

limit target application only use defined memory?

Your application, the one running the NSAppleScript? Or the application being targeted by the script?

With regards the first, I’m not sure whether that’s possible but, even if it were, that’s not something you want to do. If your app hits this limit your process will crash soon after. That’s because malloc will start returning NULL and there’s a lot of code running in your process that assumes that that won’t happen.

With regards the second, the answer is “No.”

In both cases the most likely path to a solution is to have the process terminate. That definitely gets rid of the cruft. On the send side, you might move this code to an XPC service. On the target side, you can quit and relaunch the app using AppleScript.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I try not to do next if scriptObject nil (src as below) " NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:command];   if( scriptObject == nil )     return false; " But "scriptObject == nil" not happened. I inspect memory leak by Xcode Instrument and find "NSAppleEventDescriptor coerceToDescriptorType" allocate lots of memory. Could you help us how to free it?

I need more info to be able to help you here. Consider this sequence:

  1. With Xcode 13.4.1 on macOS 12.4, I created a new test project from one of the built-in templates.

  2. I added a Test button and wired it up to this:

    - (IBAction)testAction:(id)sender {
        #pragma unused(sender)
        NSString * command = @"beep";
        NSAppleScript * scriptObject = [[NSAppleScript alloc] initWithSource:command];
        if (scriptObject == nil) {
            NSLog(@"error");
        } else {
            NSLog(@"success");
            // `scriptObject` is released by ARC
        }
    }
    
  3. I ran the app and clicked the Test button. It logged:

    2022-07-15 10:43:13.698473+0100 Test704662[9796:14916487] success
    

    confirming that my code is working.

  4. I chose Product > Profile.

  5. In Instruments, I selected the Leaks instrument.

  6. I ran the app from Instruments and clicked my Test button a bunch of times.

  7. After a few seconds Instruments ran its leaks check, which reported no leaks.

What are you doing differently?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

This is how to activate app using AppleScript. In Instrument, leak happend at "NSAppleEventDescriptor listDescriptor = [returnDescriptor coerceToDescriptorType:typeAEList];" ---- src below ---- NSString command = [NSString stringWithFormat:   @"try\n"   @"tell application "%@"\n"     @"activate\n"     @"delay 0.5\n"     @"tell application "System Events" to tell process "%@"\n"       @"tell (first window whose subrole is "AXStandardWindow")\n"         @"set fullScreen to value of attribute "AXFullScreen"\n"         @"if fullScreen = true then\n"           @"set value of attribute "AXFullScreen" to false\n"         @"end if\n"       @"end tell\n"     @"end tell\n"   @"end tell\n"   @"tell application "System Events"\n"     @"tell process "%@"\n"       @"tell window %d\n"         @"set {position, size} to {{%d,%d},{%d,%d}}\n"       @"end tell\n"     @"end tell\n"   @"end tell\n"   @"on error errMsg number errorNumber\n"     @"return "Error with number: " & errorNumber as text\n"   @"end try", ownerName, ownerName, ownerName, idx, (int)rect.origin.x, (int)rect.origin.y, (int)rect.size.width, (int)rect.size.height];   NSDictionary errorDict;   NSAppleEventDescriptor returnDescriptor = NULL;   NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:command];   if( scriptObject == nil )     return false;   returnDescriptor = [scriptObject executeAndReturnError: &errorDict];   NSAppleEventDescriptor *listDescriptor = [returnDescriptor coerceToDescriptorType:typeAEList];   NSString *result = @"";   for (NSInteger i=1; i<=[listDescriptor numberOfItems]; ++i) {     NSAppleEventDescriptor *stringDescriptor = [listDescriptor descriptorAtIndex:i];     if ( stringDescriptor.stringValue == nil ) continue;     result = [NSString stringWithFormat:@"%@%@", result, stringDescriptor.stringValue];   }

BRs, YM

This is how to activate app using AppleScript.

I’m sorry but I can’t read your post. Please put the code in a code block. For this and other tips, see Quinn’s Top Ten DevForums Tips.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

This is how to activate app using AppleScript.

  NSString *command = [NSString stringWithFormat:
  @"try\n"
  @"tell application \"%@\"\n"
    @"activate\n"
    @"delay 0.5\n"
    @"tell application \"System Events\" to tell process \"%@\"\n"
      @"tell (first window whose subrole is \"AXStandardWindow\")\n"
        @"set fullScreen to value of attribute \"AXFullScreen\"\n"
        @"if fullScreen = true then\n"
          @"set value of attribute \"AXFullScreen\" to false\n"
        @"end if\n"
      @"end tell\n"
    @"end tell\n"
  @"end tell\n"
  @"tell application \"System Events\"\n"
    @"tell process \"%@\"\n"
      @"tell window %d\n"
        @"set {position, size} to {{%d,%d},{%d,%d}}\n"
      @"end tell\n"
    @"end tell\n"
  @"end tell\n"
  @"on error errMsg number errorNumber\n"
    @"return \"Error with number: \" & errorNumber as text\n"
  @"end try", ownerName, ownerName, ownerName, idx, (int)rect.origin.x, (int)rect.origin.y, (int)rect.size.width, (int)rect.size.height];
  NSDictionary* errorDict;
  NSAppleEventDescriptor* returnDescriptor = NULL;
  NSAppleScript* scriptObject = [[NSAppleScript alloc] initWithSource:command];
  if( scriptObject == nil )
    return false;
  returnDescriptor = [scriptObject executeAndReturnError: &errorDict];
  NSAppleEventDescriptor *listDescriptor = [returnDescriptor coerceToDescriptorType:typeAEList];
  NSString *result = @"";
  for (NSInteger i=1; i<=[listDescriptor numberOfItems]; ++i) {
    NSAppleEventDescriptor *stringDescriptor = [listDescriptor descriptorAtIndex:i];
    if ( stringDescriptor.stringValue == nil ) continue;
    result = [NSString stringWithFormat:@"%@%@", result, stringDescriptor.stringValue];
  }
  return result;
}

This is how to activate app using AppleScript.

OK. Does that leak if you test it in isolation? That is, substituting it for step 2 in the steps 1 through 7 of my earlier post.

ps Using string substitution to build an AppleScript is extremely risky. For example, if the application name contains a weird character, like a " or a \, bad things will happen. I recommend that you pass these parameters to your script using an Apple event. You do this with the -executeAppleEvent:error: method. For an example of this, see the appendix in BSD Privilege Escalation on macOS (it’s in Swift but it’s not too hard to translate it to Objective-C).

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

NSAppleScript memory leak
 
 
Q