2

I have written this trivial action method associated to a textfield.
Every time I enter text into a textfield a search in a PDF is performed and PDFView automatically scroll to selection:

- (IBAction) search:(id)id
{
    NSString *query = [self.searchView stringValue]; // get from textfield
    selection = [document findString: query fromSelection:NULL withOptions:NSCaseInsensitiveSearch]; 
    if (selection != nil)
    {
        [self.pdfView setCurrentSelection:selection];
        [self.pdfView scrollSelectionToVisible:self.searchView];
    } 
}

Problem is that after 3 or 4 searches I get EXC_BAD_ACCESS on row (i).
If I debug I see that query contains an NSCFString and not an NSString.
I think it is a memory management problem..but where?

enter image description here

I replicated the same issue inside a trivial testcase:

  @interface PDFRef_protoTests : SenTestCase {
   @private

      PDFDocument *document;

   }

  ........

 - (void)setUp
  {
     [super setUp];
     document = [[PDFDocument alloc] initWithURL: @"a local url ..."];
  }

- (void)test_exc_bad_access_in_pdfdocument
{
    for (int i =0 ;i<100; i++)
    {
        NSString *temp;
        if (i % 2 == 0) temp = @"home";
        else if (i % 3 ==0) temp = @"cocoa";
        else temp=@"apple";
        PDFSelection *selection = [document findString: temp 
                                   fromSelection:nil 
                                withOptions:NSCaseInsensitiveSearch]; 
        NSLog(@"Find=%@, iteration=%d", selection, i);
    }
}

Update:

1) It seems that it happens also if I use asynchronous search (method beginFindString: withOptions) every time I perform second search.

2) I found a similar problem to mine in MacRuby Issue Tracking: http://www.macruby.org/trac/ticket/1029

3) It seems that if I disable temporarily garbage collection it works but memory goes up. I wrote something like:

[[NSGarbageCollector defaultCollector] disable];
[[NSGarbageCollector defaultCollector] enable];

surrounding search code

Another Update

Very weird thing is that sometimes all works. Than I clean and Rebuild and problem arises again. From a certain point of view is is not 100% reproducible. I suspect a bug in PDFKit or some compiler setting I have to do

Update Again

Dears it seems very crazy. I'd concentrate on testcase which is very trivial and which replicates easily the problem. What's wrong with it? This testcase works only if I disable (by code or by project setting) GC

Another Update

Boys it seems a bug but I downloaded an example called PDFLinker from Apple website (http://developer.apple.com/library/mac/#samplecode/PDFKitLinker2/Introduction/Intro.html#//apple_ref/doc/uid/DTS10003594). This example implements a PDFViewer. Code of my app and this example are quite similars. For the same search action on same PDF, my memory rises at 300/400 MB while PDFLinker rises at 190MB. Clearly there is something wrong in my code. But I am comparing it bit by bit and I don't think I am inserting memory leaks (and Instrument doesn't give me any evidence). Maybe is there some project-wide setting ?

Update Yet Changing from 64 bit to 32 bit memory consumption lowered. Surely there is a problem with 64bit and PDFKit. BTW still EXC_BAD_ACCESS on second search

SOLUTION Crucial point is that PDFKit with Garbage collection is bugged. If I disable GC all works correctly. I had another issue that had complicated my analysis: I disabled GC on Project Setting but GC remained enabled on Target Settings. So Apple's example PDFLinked2 worked while mine not.

pierocampanelli
  • 960
  • 5
  • 19

5 Answers5

2

This sort of thing is usually evidence that you're hanging onto a pointer to an object that has been destroyed. Turn on zombie objects (with NSZombieEnabled) to see exactly where and when you're accessing a bad object.

Jonathan Grynspan
  • 43,286
  • 8
  • 74
  • 104
  • I'll try but how can be a zombie object in a trivial-asshole piece of 4 lines of code? – pierocampanelli Jun 11 '11 at 14:42
  • Well, first let's find out which object is bad. :) – Jonathan Grynspan Jun 11 '11 at 15:25
  • If I type fast in text field doing a series of searches (about 3 or 4) I continuously receive EXC_BAD_ACCESS. Sometime I receive "Thread 1: program received signal: "EXC_BAD_ACCESS". Other times I receive 2011-06-11 17:29:24.960 PDFRef_proto[18222:903] -[CPCharacterIndex count]: unrecognized selector sent to instance 0x2002e2200 2011-06-11 17:29:24.961 PDFRef_proto[18222:903] Exception detected while handling key input. 2011-06-11 17:29:24.961 PDFRef_proto[18222:903] -[CPCharacterIndex count]: unrecognized selector sent to instance 0x2002e2200 – pierocampanelli Jun 11 '11 at 15:28
  • Again, turn on zombies. You'll see what object is bad in the Xcode console. – Jonathan Grynspan Jun 11 '11 at 15:29
  • Zombies doesn't help. It doesn't print anything useful on console – pierocampanelli Jun 11 '11 at 15:32
  • Alright, so what's the backtrace look like in Xcode when you get the EXC_BAD_ACCESS? – Jonathan Grynspan Jun 11 '11 at 15:33
  • (BTW, from the errors you're receiving, I can tell it's a dangling pointer you're dealing with. Zombies almost always help with such problems.) – Jonathan Grynspan Jun 11 '11 at 15:34
  • Jonathan tnx for your time. I added a screenshoot. I am still crazy about this. I have only written 4 lines of code sigh..The only thing is that probably I am using PDFKit the wrong way. – pierocampanelli Jun 11 '11 at 15:41
2

I agree you have found a bug in PDFKit.

I got various forms of errors (segmentation fault, selector not understood, etc) running your test case. Wrapping the code in @try/@catch doesn't prevent all errors associated with this method.

I also got errors printing the log message.

To work around the bug(s), I suggest you disable GC during your invocation of -findString:fromSelection:, as you've already discovered.

Also, be sure to make copies of the values of interest from selection before re-enabling GC. Don't just copy selection either.

If you conduct searches from multiple places in your code I also suggest you extract a separate method to perform the search. Then you can invoke that one to conduct the searches for you without duplicating the GC disable/enable nesting.

Huperniketes
  • 940
  • 7
  • 17
  • yes but as you can see there is an apple demo that works (PDFLinker2). – pierocampanelli Jun 17 '11 at 17:19
  • yes, but as you can see PDFLinker2 was written for Tiger. When I set PDFLinker2 to build against either the 10.5 or 10.6 SDK with GC **required**, it causes unrecognized selector errors and segmentation faults too. Might there be ways to prevent PDFKit bugs (and -findString:fromSelection:withOptions: in particular) from manifesting under GC? Sure, but Googling "+PDFKit garbage collection" shows how common problems are with PDFKit in GC. Making your app stable for users is more important than making PDFKit and GC work together. Either don't use GC in your app, or disable it around PDFKit. – Huperniketes Jun 17 '11 at 23:53
1

Judging from your screen shot it doesn't seem like you have NSZombie turned on. Probably the reason why it doesn't help you. Here's how you turn it on:

How to enable NSZombie in Xcode?

The screenshot you provided was otherwise very useful, but you really need NSZombie to figure out this kind of errors. Well, unless it's obvious, which it isn't from the code you posted.


EDIT: I read the comment that you're using garbage collection. I'm an iOS developer, so I have very limited experience with garbage collection in Objective-C, but as far as I understand NSZombie doesn't work in a garbage collected environment.

I'm not sure it should be possible to get EXC_BAD_ACCESS in a garbage collected environment, unless you create your own pointer and try to call methods on it without having created an object and I don't see why you would do that.

I've heard that some frameworks doesn't work well with garbage collection, but I wouldn't think PDFKit was among them. Anyway, the solution might be to not use garbage collection. Perhaps you should file a bug report with Apple.

Community
  • 1
  • 1
Erik B
  • 40,889
  • 25
  • 119
  • 135
1

Did you try retaining the searchview stringvalue object before using it?

As you say it happens when you type fast and it happens for async calls, it is possible that the object stringValue is pointing to is being released between the time your query object is pointing to it, and the time you use it int the search.

You could try something like this to see if the problem persists:

- (IBAction) search:(id)id
{
    NSString *query = [[self.searchView stringValue] retain]; // get from textfield
    selection = [document findString: query fromSelection:NULL withOptions:NSCaseInsensitiveSearch]; 
    if (selection != nil)
    {
        [self.pdfView setCurrentSelection:selection];
        [self.pdfView scrollSelectionToVisible:self.searchView];
    } 

    [query release];

}

Of course there is also the possibility that document is relased. How do you declare it? is it a property with retain? Can it be released by the time you are searching?

EDIT:

I see that you posted the code with the second parameter as NULL, but in your screenshot, this value is nil.

The documentation says you should use NULL when you want to start the search from the beginning.

http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/QuartzFramework/Classes/PDFDocument_Class/Reference/Reference.html#//apple_ref/doc/uid/TP40003873-RH2-DontLinkElementID_1

And as the compiler interprets nil and NULL differently, this could be leading to some weird behavior internally.

Felipe Sabino
  • 17,825
  • 6
  • 78
  • 112
  • This is absolutely the solution; in fact I just spent several minutes typing up answer explaining why Piero should `copy` the string, before I realized that the reason this question was on the front page was that you had just added this post. – jscs Jun 15 '11 at 03:47
  • Uhm....I don't know if it fixes and I am going to try but alas your solution doesn't fix testcase. In that case you can see that NSString is allocated inside the method but still I got the exception – pierocampanelli Jun 15 '11 at 06:43
  • Sorry but it doesn't fix my problem. The only way to fix is to disable temporaneily GC – pierocampanelli Jun 15 '11 at 07:23
  • What about the document object? Is there any change it is being released between searches? It it properly retained by your controller? – Felipe Sabino Jun 15 '11 at 10:39
  • It surely is something that is being set as autorelease and the GC assumes is not being used anymore and deallocs it. With the info provided by the queston and the snapshots, the only possible variables that fit are the query and the document... – Felipe Sabino Jun 15 '11 at 10:41
  • Just realised that the argument above is also valid for the selection object. Also if you have this variables set as retain properties, you probably should you self.document or self.selection, so that you use the setter properly and then your variable would be retained. Otherwise you are just assigning it and later it might be pointing to a position in memory that is a released object, causing the EXC_BAD_ACCESS to occur – Felipe Sabino Jun 15 '11 at 10:49
  • Just to exclude any problem with document object I refactored code. Now I assign document to the view avoiding to maintains a PDFDocument instance variable.PDFDocument *docu = [[PDFDocument alloc] initWithURL: url] ; and than [self.pdfView setDocument: docu]; ... but still problem persists – pierocampanelli Jun 15 '11 at 11:53
  • I checked the doc and your code again, take a look at the edit to see if thats what is causing your problem... – Felipe Sabino Jun 16 '11 at 04:03
  • Sorry....but I have tried already to exchange NULL, nil in that piece of code.....same results – pierocampanelli Jun 16 '11 at 06:15
  • It's simple to find the definition for nil in Xcode: #define nil NULL Do you still think the compiler treats nil and NULL differently? -[PDFDocument findString:fromSelection:] is already compiled and won't see any difference either. – Huperniketes Jun 17 '11 at 05:26
1

keep PDFSelection *selection as a member variable and pass it in fromSelection: instead of nil.

It is possible that PDFDocument keeps the returned PDFSelection instance to improve the performance.

cocoafan
  • 4,884
  • 4
  • 37
  • 45
  • documentation states that fromSelection: can be nil. This because setting it to nil means to start search from beginning of PDF document. As I want to search always from beginning I can't change. – pierocampanelli Jun 15 '11 at 11:51
  • 1
    But the documentation does not mention how to reset the 'starting from beginning' flag after you pass once nil. Sure, your code should work regarding the documentation and programmer's expectation. However, in your question you suspect a bug in PDFKit and in my answer I try to point out what the bug could be, which is in my opinion worth a try. – cocoafan Jun 15 '11 at 11:58
  • It happens also with fromSelection: argument <> nil – pierocampanelli Jun 15 '11 at 12:24
  • Did you store the argument <> nil, which you are passing to, as ivar? This is important. – cocoafan Jun 15 '11 at 16:21