[comp.sys.mac.programmer] Toolbox Gotchas

jln@accuvax.nwu.edu (John Norstad) (04/13/89)

While devoping Disinfectant I ran into a number of "gotchas" that
caused me great grief.  I thought it would be nice to tell the
rest of you about these problems, in the hope that you'll be able
to avoid them in your own programs.  I've told DTS about most of this stuff.

Gotcha #1. Watch out for PBGetCatInfo calls with TOPS.

The file manager routine PBGetCatInfo uses a parameter block of type
CInfoPBRec.  Make certain that you pass a pointer to the full parameter 
block when using MPW C, even if you know in advance that the object is a 
directory.  Don't just allocate and pass a pointer to the DirInfo variant.  
The DirInfo variant is four bytes shorter than the full union type, and with
TOPS the PBGetCatInfo call sets those four bytes at the end.  If your 
parameter block is not big enough you'll trash the stack.  

Gotcha #2. Make certain you're in the proper heap zone before calling
ReleaseResource.

At the bottom of IM II-26, in the Memory Mangler chapter, is the warning 
"Be sure, when calling routines that access blocks, that the zone in which 
the block is located is the current zone."  Heed this warning, especially
when releasing resources.  Bob Hablutzel and I discovered (after hours in 
Macsbug) that on the 128K ROMs, if you try to release an empty (unloaded) 
resource in the system heap, and if you neglect to set the current zone to 
the system zone, then the system will trash the free master pointer list.  
This is not good, and will almost undoubtably lead to subsequent bizarre 
behaviour.

Here's the code I use to release a resource:

   curZone = GetZone();
   SetZone(HandleZone(theRez));
   ReleaseResource(theRez);
   SetZone(curZone);
	
Gotcha #3. Don't believe Inside Macintosh.

On page IM II-34 we read the following warning in the description of the
HandleZone routine: "If handle h is empty (points to a NIL master pointer),
HandleZone returns a pointer to the current heap zone."  This is false -
HandleZone properly returns a pointer to the heap zone that contains the
master pointer.  See Gotcha #2 above.

Gotcha #4. Don't expect OpenResFile to do sanity checking.

Neither OpenResFile nor OpenRFPerm does any sanity checking of any sort
when opening a resource file.  If the file is damaged or contains trash it
is very possible for the Resource Mangler to bomb or hang inside the
OpenResFile or OpenRFPerm call.  Often what happens is that it makes a
Memory Mangler request for some ridiculously huge block of memory.  If you
have a GrowZone proc this can cause problems.

To prevent this problem you must write a sanity checker of your own that
opens the resource file as a binary file and checks at least the most
important structural characteristics of the file.  If your sanity check
fails you must avoid calling OpenResFile or OpenRFPerm on the file.  In
Disinfectant I check that the resource map and resource data are 
within the logical EOF of the file and don't overlap, I check that the
resource type list immediately follows the resource map, and I check that
the resource name list starts within the logical eof.

DTS tells me that the only way to be completely safe is to do a complete
sanity check of the entire resource fork - e.g., rewrite the RezDet MPW
tool.

Damaged and trashed resource forks are much more common than you might 
think.

Gotcha #5. Don't believe Inside Macintosh.

In the description of the OpenResFile routine, IM I-115 states "If the
resource file is already open, it doesn't make it the current resource
file; it simply returns the reference number."  This is false.  If the
resource file is already open, OpenResFile in fact DOES make it the 
current resource file.  OpenRFPerm also has the same behaviour, in those
cases where OpenRFPerm returns the reference number of the previously
opened copy of the file, rather than opening a new access path (see
IM IV-17 and TN 185).

Gotcha #6. Watch out for Standard File if you unmount volumes.

The standard file package keeps track of the last volume it used in the low
core global SFSaveDisk, which contains the negative of the vol ref num of
the last volume used.  If your program unmounts this volume and then later
calls the standard file package again, it will post an alert saying that
"A system error has occurred.  Please try again."  A simple fix for this
problem is to check the vRefNum stored in SFSaveDisk immediately before 
any calls to standard file.  Call PBGetVInfo to see if the volume still
exists.  If it doesn't, make an indexed call to PBGetVInfo to get the 
vRefNum of the first volume in the VCB queue, and set SFSaveDisk to the
negative of this vRefNum.  Also set CurDirStore to fsRtDirID.

Gotcha #7. Don't believe Inside Macintosh.

IM I-116 states that "When calling the CurResFile and HomeResFile routines,
described below, be aware that for the system resource file the actual
reference number is returned."  This is false.  CurResFile does indeed
return the actual reference number of the system file (2), but 
HomeResFile in fact returns 0 for system file resources.

Gotcha #8. Don't believe Inside Macintosh.

IM I-126 states "Like the attributes of individual resources, resource file
attributes are specified by bits in the low-order byte of a word."  This
is false.  In fact, the resource file attributes are stored in the
high-order byte of the word.

Gotcha #9. Directory IDs are longs, not shorts, stupid.

Directory IDs, unlike volume reference numbers and working directory ids,
are longs, not shorts.  Watch out for this one.  It's really easy to 
declare a dirID to be a short by mistake, and unless you're using Modula-2
you probably won't catch the bug even with extensive beta testing.  Don't
feel too stupid if you do this - I have it on good authority that ResEdit
once had this bug!

Gotcha #10. Always set the ioNamePtr field in file manager param blocks.

See TN 179.  Read it.  Believe it.  Always set ioNamePtr.  Set it to nil
if you don't care about the name.  I made this mistake three times while
developing Disinfectant, and all three times it took FOREVER to find the
bug.  The problem is those silly little arrows in the file manager 
chapter of IM IV.  They all point to the left for ioNamePtr, which usually
means that you don't have to set the field before calling the routine.

I hope my experiences help somebody.

John Norstad
Academic Computing and Network Services
Northwestern University

Bitnet:      jln@nuacc
Internet:    jln@acns.nwu.edu
AppleLink:   a0173
CompuServe:  76666,573

bob@accuvax.nwu.edu (Bob Hablutzel) (04/13/89)

As a quick note to John's list of gotchas, let me just note that the gotcha
#2, refering to releasing resources, also applies to disposing handles. If the 
handle is disposed in the wrong zone, the heap will be corrupted.

Bob Hablutzel	Wildwood Software	BOB@NUACC.ACNS.NWU.EDU
Disclaimer:	Deep in the heart of ROM, it's every man for himself...

ra_robert@gsbacd.uchicago.edu (04/13/89)

Um...is there any chance that we might see Inside Mac, 2nd edition, from Apple
one of these days?  Shouldn't be too hard between one of the printings to go
back and correct some of the misstatements.

Just a suggestion...


Robert
------
ra_robert@gsbacd.uchicago.edu
------
generic disclaimer: all my opinions are mine

ech@pegasus.ATT.COM (Edward C Horvath) (04/14/89)

From article <10050096@accuvax.nwu.edu>, by jln@accuvax.nwu.edu (John Norstad):

> While devoping Disinfectant I ran into a number of "gotchas" that
> caused me great grief.  I thought it would be nice to tell the
> rest of you about these problems, in the hope that you'll be able
> to avoid them in your own programs.  I've told DTS about most of this stuff.
...
> Gotcha #10. Always set the ioNamePtr field in file manager param blocks.

Allow me to add my favorite foulup:

Gotcha #11: Always set the ioMisc field in calls to PBOpen.

Easiest thing here is to clear it.  Failure to do so uses random locations in
memory as the IO buffer for the file.  This one is especially fun because
the bomb is (a) guaranteed to hit sometime, and (b) always takes awhile,
so you go nuts trying to find a stray pointer which your code never even
referenced.

Any more true confessions out there?  This could be fun...

=Ned Horvath=

alexis@ccnysci.UUCP (Alexis Rosen) (04/16/89)

John discusses the complete lack of sanity checking when you call OpenResFile.
The Apple Answer is, rewrite the appropriate parts of RezDet and put them in
your code.

This is pretty terrible. As John says, damaged resource forks are not so rare.
Crashing when you open a bad resource file is very very unfriendly. But
having to write all that code to protect yourself is an awful lot of work.
I can't think of many people outside of Apple, except maybe Steve Brecher, who
could do this without extensive sessions with Inside Mac. This already makes
that solution problematic.

Of course the real problem is much worse. Apple specifically warns us, many
times, that the format of the Resource Map will change in the future. It is
my guess that such a change will come with System 7.0, and certainly no later
than System 8.0.

Guaranteeing that your application will crash on a future system just to
protect yourself now is a miserable tradeoff. I suggest that Apple provide
us with the tools to do this properly.

There are two decent ways to do this that I can think of. First, the simplest
way is to provide a trap that checks the file. The second, slightly more
involved way is a little micer because it's more automatic. When you call
OpenResFile, the first thing it does is write a dirty bit in the header
somewhere. It then reads in the resource map. If everything is OK, the last
thing is does before it returns is clear the dirty bit. When it opens a res
file, before it writes the bit it reads it, and if it's set it knows that the
last time it opened this resource fork it crashed. So it's a good bet that
the res fork is damaged, and needs to be checked carefully. It would then
call the sanity checker, and if it failed it would return an error.

Anyone have any idea what the new resource manager is going to be like?

---
Alexis Rosen
alexis@ccnysci.{uucp,bitnet}
alexis@rascal.ics.utexas.edu  (last resort)

darin@Apple.COM (Darin Adler) (04/20/89)

In article <10050096@accuvax.nwu.edu> jln@accuvax.nwu.edu (John Norstad) writes:
> Gotcha #3. Don't believe Inside Macintosh.
> 
> On page IM II-34 we read the following warning in the description of the
> HandleZone routine: "If handle h is empty (points to a NIL master pointer),
> HandleZone returns a pointer to the current heap zone."  This is false -
> HandleZone properly returns a pointer to the heap zone that contains the
> master pointer.  See Gotcha #2 above.

This is incorrect. Since the publication of Inside Macintosh Volume II,
HandleZone has been *slightly* enhanced (along with the rest of the
Memory Manager) to handle the system heap as a special case. The sentence
could be correctly reworded: "If handle h is empty (points to a NIL master
pointer), HandleZone returns a pointer to the current heap zone, unless
the handle points somewhere within the system heap zone and the current
heap zone is the application heap zone, in which case it returns a pointer
to the system heap zone."

This could return suprising results, since you can create a heap zone
in the middle of the system heap (in a nonrelocatable or locked relocatable
block), and HandleZone will return misleading values. This hack in the ROM
(and in system patches) lets you be somewhat haphazard about what the current
zone is as long as you stick mostly with the system heap and the application
heap.
-- 
Darin Adler, System Software Scapegoat, Apple Computer
	Internet: darin@Apple.com
	UUCP: {nsc,sun}!apple!darin

lsr@Apple.COM (Larry Rosenstein) (05/10/89)

Here are some others off the top of my head:

* Be sure to set ioVersNum to 0 in the approrpiate paramter blocks, even 
if you call PBHOpen, etc.  The description of PBHOpen doesn't mention this 
as a parameter, but a call to PBHOpen will be treated as a call to PBOpen 
on an MFS disk, and then the field must be 0.

* Be aware of the Poor Man's Search Path.  For example, when saving a 
file, don't call PBOpen or PBDelete to get rid of the existing file first. 
 If the filename is the same as one in the system folder, you will get 
that file instead.  You can disable the effect of the PMSP by setting an 
explicit dirID in the call.  

Also, note that Create doesn't use the PMSP,  OpenResFile doesn't give you 
the option to set the dirID, and CreateResFile calls OpenResFile first.  
If you are creating a resource file, it is best to call Create first, to 
create the file, and then CreateResFile (see Tech Note 101; or use the MPW 
glue routine described in Tech Note 214.)

* When creating a picture, always set the clip region to the minimum 
bounding box.  When a picture is replayed and offset from its original 
position.  All the coordinates in the picture are offset.  If the clip 
region is the largest possible rectangle, it will be offset as well, and 
the coordinates will overflow resulting in an empty clipping.




Larry Rosenstein, Apple Computer, Inc.
Object Specialist

Internet: lsr@Apple.com   UUCP: {nsc, sun}!apple!lsr
AppleLink: Rosenstein1