Path: utzoo!utgpu!jarvis.csri.toronto.edu!clyde.concordia.ca!mcgill-vision!bloom-beacon!snorkelwacker!usc!wuarchive!decwrl!sun-barr!ccut!titcca!kddlab!atrpost!atr-la!alain From: alain@atr-la.atr.co.jp (Alain de Cheveigne) Newsgroups: comp.sys.mac.programmer Subject: ROM doesn't unlock Handles (was: ROM unlocks Handles). LONG Message-ID: <3875@atr-la.atr.co.jp> Date: 23 Dec 89 12:58:09 GMT Organization: ATR International,Japan Lines: 167 Some time ago I posted an article claiming that some ROM calls leave handles unlocked (causing a bug if code expects a locked handle to stay locked after the call). Larry Rosenstein asked me to produce an example. I set traps in my code to catch the bug, and after 3 weeks it still hasn't turned up. I guess I misinterpreted some other bug. Here are my apologies for maligning the ROM. Luckily, a posting by shebanow@Apple.COM (Andrew Shebanow) offers me an honorable way out: >As far as it goes, there is (should be) very little code left in >the system which goes around unlocking things on you. Almost >everything has been fixed to use HGetState/HSetState. Which implies that it hasn't always been so. So maybe it wasn't my imagination after all... My original posting had another aim: find the wisest way to deal with locking and unlocking handles, especially in code that contains many nested subroutines. Here's a synthesis of the follow-ups and e-mail I received. First, why lock a Handle? Because the Memory Manager might move the block it contains. The MM will update the master pointer, so the Handle is still valid, but it but won't update the pointers that you (or the compiler) might be using to access the data. This happens in seemingliy harmless code. Tim Maroney gives an example (marsup is an unlocked handle): >(*marsup)->wombat = NewHandle(WOMBAT_SIZE); > >The compiler is free to evaluate the address of the left-hand side >before it performs the right-hand side. If NewHandle moves memory, >then its return value will be assigned into outer space. There's a tech note that says that code like that needs to be preceded by MoveHHi(marsup); HLock(marsup), and followed by HUnlock(marsup). The trouble with this is that you use four operations instead of one. Your code gets longer, wastes time, and becomes pretty unreadable. So it's natural to try to cut down on the locking. When not to lock? Ordinary code that does't a) call the ROM or b) call routines in another segment is safe. Furthermore, IM-IV and V contain lists of Routines that May Move or Purge Memory, implying that all other ROM routines are safe. However, as pete@hub.UUCP (Pete Gontier) writes: >DTS' official position now is that there are so many patched traps running >around calling the Memory Manager that you need to lock and unlock handles >around ANY trap call. The list, in other words, is no longer valid. The follow-up on this boiled down to: From: shebanow@Apple.COM (Andrew Shebanow): >Anyhow, this statement of DTS's position is a bit of an oversimplification. > >It is true that the lists of traps that may move or purge memory are out >of date, and that traps which currently are considered "safe" may become >"unsafe" sometime in the future. Our recommendation is that developers >err on the side of caution: if you are passing a dereferenced field >of a handle by address, either use a temporary or lock the handle. I >do feel that there are some calls which are "safe" to use dereferenced >handles with, like BlockMove, some string manipulation calls, etc. Use >your better judgment (or "Toolbox Karma", if you will). Which is pretty lame. Either a call is safe or it isn't. If DTS can't guarantee it, who am I to judge, and how? Trial and error? Other (non-ROM) subroutines are supposedly safe if they don't call the ROM. But to be sure of this, you have to know what's inside a routine. This defeats the principle of information hiding, so the code tends to be unreliable (even Apple can't seem to keep track of such side effects). In addition, a call to a "harmless" subroutine can move memory if the subroutine is in an unloaded segment. Bottom line: don't trust *any* routine, either Apple's or your own. Another way to avoid locking is to use a temp variable, as recommends tim@hoptoad.uucp (Tim Maroney): >Handle h = NewHandle(WOMBAT_SIZE); >(*marsup)->wombat = h; > >Slightly more stack overhead, but you avoid trap overhead for locking >and unlocking, and also avoid all the state-restoration hassles that >have been mentioned lately here. This scheme was recommended by many, and emerged as the best solution for short fields. But it isn't obvious what to do if the field is large: a string, or an array, or data for a BitMap or PixMap. Temp variables for big data fields require space (stack? heap?) and copying takes time. In the case of a string, copying with strcpy() might move memory if strcpy() happened to be in an unloaded segment. BlockMove() is said to be safe (but then who knows...). It seems better to lock a handle to access big fields. This brings us to the question of state restoring. Suppose you have a long piece of code that needs a pointer to data in a handle. You don't want to keep locking and unlocking, so you call Hlock() once at the beginning, and HUnlock() at the end. Now, if your code calls a routine that also locks and unlocks the handle, then that handle is left *unlocked* in the following code. This situation is difficult to identify. Avoiding it, if you only have HLock() and HUnlock(), requires some coordination between sub-routine implementation and calling code (for example the routine relies on the calling code to lock). This runs counter to the principle of information hiding, and makes for fragile code. That's where HGetState() and HSetState() come in handy. When locking can't be avoided, precede HLock() with a call to HGetState(), and replace HUnlock() with HSetState(). Your code is guaranteed to leave the state as it found it. Some last advice: From: earleh@eleazar.dartmouth.edu (Earle R. Horton): > One thing to remember when using HGetState and HSetState is that these >are not available on early (64k ROM) machines, so you have to test for >machine type or trap availability before using them. For an application >program which probably owns all the Handles it deals with, HLock and HUnlock >are appropriate nearly all the time. For code resources which manipulate >data owned by applications or the system, H[GS]etState is a more application- >friendly technique, if available. For 64k ROM machines you could save the high-order byte of the handle. Code that does either this or calls HGetState() according to a flag should fit ok in a macro. From: lippin@ronzoni.berkeley.edu (The Apathist) (Tom Lippincott0): >Finally, a note on MoveHHi -- this call is easy to overuse. It's only >an advantage if a block is going to remain locked for a respectable >length of time; in particular, only when there's a chance of >significant memory allocation while the block is locked. If used when >not necessary, it wastes time with frivolous heap shuffling. Since >I avoid keeping handles locked for such periods, I use MoveHHi very >sparingly. From: tim@hoptoad.uucp (Tim Maroney): >>HLock and HUnlock are probably the best solution in this case, or the >>HGetState and HSetState calls. > >Nope, there's more overhead involved in the trap calls than in the >copying of eight bytes. And there's also less "cognitive overhead" >if you avoid locking handles, since you never have to keep track >of when things are supposed to be locked. Reducing "cognitive overhead" is *the* idea. Alain de Cheveigne alain@atr-la.atr.co.jp