Elusive Bugs: No known cause for missing resources, but solved regardless

Sometimes I get bug reports and, since no one else has ever reported them and they seem weird (“they” referring to the crash, not the user!), I write them off as a fluke, a one-time random occurrence, or what you might call “bit rot”. However, when you start to get many crash logs with the same error messages over and over again, it’s obviously none of those. That doesn’t mean anybody knows how to reproduce the error though!

For months, I used to receive e-mails with the following crash log:

Exception details:
System.Resources.MissingManifestResourceException: Could not find any resources appropriate for the specified culture (or the neutral culture) on disk.
baseName: PaintDotNet.Strings locationInfo: <null> fileName: PaintDotNet.Strings.resources

I could tell that the PaintDotNet.Strings.resources file was missing. I could even reproduce this crash by simply deleting the file. Fixing was usually an easy matter as well – the user just had to reinstall Paint.NET, or click “Repair” in the Add/Remove Programs control panel. However, I had no idea why or how the file was missing. Was it just being randomly deleted? Was it a race condition in the installer? Amazingly enough, I actually had this randomly happen to myself once or twice while doing regular installation and upgrading of some old builds of Paint.NET v3.0. So I knew for 100% sure that it was a real issue that had to be solved. And I wanted my inbox back! This was a top crash report for Paint.NET v2.6 through v2.72.

To this day, I still have no idea what causes this to happen. However, I did solve the problem for users! I decided that Paint.NET would auto-repair itself if it discovered any important files were missing. Thus, PdnRepair.exe was born!

All it really does it execute a “repair” operation on the Paint.NET installation, functionality that is built-in to Windows Installer via MsiReinstallProduct() and the REINSTALLMODE_FILEMISSING flag. This required a small change to the installer for Paint.NET, whereby it simply had to write Paint.NET’s product code GUID out to the registry so that PdnRepair would know which MSI it needed to repair (it ain’t psychic!).

You can see this in action pretty easily. Just go to your installation directory for Paint.NET and delete any *.EXE, *.DLL, or *.resources files except for PaintDotNet.exe and PaintDotNet.SystemLayer.dll. Then, launch Paint.NET as normal.

Once you click Repair, a console window will show up with some diagnostic text, followed by some standard Windows Installer dialogs and progress bars. When it’s finished, Paint.NET will automatically relaunch itself. Pretty easy, eh? The only problem with this dialog is that the text had to be hard coded and is English-only. This is because the file that’s always missing in the crash logs I’ve received is the file with the string resources in it!

And now I only get about 1 or 2 of these crash logs per month and it’s always from folks who are still running version 2.72. Even though I was never able to get a solid repro on the bug, it was still solved and one of my top support issues was eradicated! It also served as a generic repair utility for any number of other small issues that may crop up.

Lastly, the source code for this utility is very straightforward. If you want to see how it’s done for your project that uses MSI’s for installation, just go to the Paint.NET download page and grab the source code! It’s in the src/PdnRepair directory.


Elusive Bugs: Crash when clicking zoom buttons after a file failed to open

I actually found this by accident the other day. I was toying around with the latest build of Paint.NET, fresh out of the compiler’s oven, and I noticed some weird behavior. I had pre-ordered BioShock on Steam about 10 seconds after I read it was available on there (Tuesday can’t come fast enough!), and was pawing through the files that had been downloaded on my system. There was a bitmap and a PNG, and so of course I had to check them out!

After right clicking the image and choosing Edit, Paint.NET started up and gave an error saying it couldn’t open the image. After a few seconds of wondering why, I realized it was simply because the files were still in encrypted form. Steam is very careful about their release dates! (and rightfully so, of course).

But, what I noticed was that the tool windows and the toolbar were still interactive, even though there were no images open. They’re supposed to be disabled! This is a behavior glitch, of course, but if you can’t crash the app or get it into a bad state then it’s a bug I’m willing to punt until the next release (or just “won’t-fix” forever). So I started clicking on everything in the toolbars and tool windows, half expecting it to crash or worse. Everything was working great though, which I was happily surprised by.

Oops, except that when I clicked on either the Zoom In or Zoom Out buttons, it did crash 😦

Exception details:
System.NullReferenceException: Object reference not set to an instance of an object.
at PaintDotNet.AppWorkspace.ViewConfigStrip_ZoomIn(Object sender, EventArgs e)
at PaintDotNet.ViewConfigStrip.OnZoomIn()
at PaintDotNet.ViewConfigStrip.OnItemClicked(ToolStripItemClickedEventArgs e)

Darn it! Well, the fix was easy enough, all I had to do was check to make sure that ActiveDocumentWorkspace (an internal property of Paint.NET’s AppWorkspace class) property was non-null before doing anything with it.

But wait, there’s more! I’ve actually been receiving e-mails with this specific crash for months! I never knew how to reproduce the crash, and therefore had no way to fix it.

This bug is reproducible in Paint.NET v3.0 through v3.10 Beta 2. It is fixed for the finalized v3.10 release.

Elusive Bugs: “ScratchSurface was already borrowed…”

As the author of something like Paint.NET, I often get bugs reported that I simply don’t understand. Ever since before Paint.NET 3.0 went into beta, I’ve been getting crash reports like the following:

System.InvalidOperationException: ScratchSurface already borrowed: ‘PaintDotNet.DocumentWorkspace.DoSaveAs() handing off scratch surface to DoSaveAsDialog()’ (trying to borrow for: ‘PaintBucketTool: Tool.Activate()’)

This tells me that the Paint Bucket tool was trying to activate, but it couldn’t get the scratch surface (this is an internal Paint.NET object) because the Save As dialog already had it. Unfortunately, the stack trace didn’t help. All my powers of analysis were for naught, as I could not discern a way that the scratch surface was in use twice. One part of the crash log was pointing a finger at the Save As dialog, but that code was nowhere to be seen in the stack trace!

The number of reports of this particular crash were not alarming, nor were they causing any bad press or user sentiment. So, for months I just accepted the bug and hoped that a future version would work around it accidentally. As a software developer, you quickly learn that software has bugs. And you will ship with some. It’s just the way the world works outside of ivory towers.

See, the problem was that I was trying to use logic to figure out a repro for the bug (“a repro” is just the term for having a set of instructions to reproduce the issue). I looked at the callstack and did not see any of the Save As dialog’s functions, so I concluded that the Save As dialog was not active at the time of the crash. I thought that, somehow, the Save As dialog had closed but for some reason it had not returned the “Scratch Surface”. I probably have spent several hours over the last few months just trying to repro this one specific crash. I looked everywhere except that Save As dialog itself

Well, I was wrong. Yesterday the light bulb went on over my head and I decided I would try to do the following:

  1. Start Paint.NET
  2. Go to File -> Save As
  3. Click on a tool in the Tools window (this should be impossible! The “Save As” dialog is modal! The other windows should just go “beep” if you try to click on them!)

Guess what? Not only was (3) possible, it crashed the program! I finally had a repro! You can try it for yourself right now in the latest 3.08 release. For some reason, the Save As dialog is modal with respect to the main Paint.NET window, but it isn’t preventing clicks from being processed in the four “floating windows” (Tools, History, Layers, Colors). My knowledge of WinForms and Win32 doesn’t give me a way for this to be possible, but a workaround that produces the effect I need should be straight forward.

So, there you go. This will be fixed in the next release of Paint.NET of course.