|
This Technical Note describes latest information about bugs or unexpected
"features" in the MPW C, Pascal, and Assembler products and the Toolbox and OS
Interface Libraries. We intend this Note to be a complete list of all known
bugs in these products, which will be updated as old bugs are fixed, or new
ones appear. If you have encountered a bug or unexpected feature which is not
described here, be sure to let us know. Specific code examples are useful.
[Aug 01 1988]
|
Introduction
The bugs described in the October 1 revision of this Note will be fixed in the
3.0 release of MPW scheduled for Fall 1988.
Back to top C Language
The following information applies to the C compiler and its associated
libraries shipped with the 2.0.2 version of MPW.
- A series of bugs involving floating point array elements and the
+= , *=, and = operators. A similar bug was reported as fixed in MPW 2.0.2, unfortunately the fix did not apply to array elements. This bug ONLY occurs when using SANE in combination with float or double variables, it does not occur if the -mc68881 compiler option is specified or if extended variables are used. The following fragment illustrates the bugs:
main()
{
double x[2],y; /* Also fails if x,y are declared float
but succeeds if declared extended */
x[0] = 0.5;
y = 5.0;
x[0] += 2.0*y;
printf("x[0] = %f\n", x[0]);
x[0] = 0.5;
y = 5.0;
x[0] = x[0] + 2.0*y;
printf("x[0] = %f\n", x[0]);
x[0] = 0.5;
y = 5.0;
x[0] *= 2.0*y;
printf("x[0] = %f\n", x[0]);
x[0] = 0.5;
y = 5.0;
x[0] = x[0]*(2.0*y); /* Succeeds if parenthesis are removed */
printf("x[0] = %f\n", x[0]);
exit(0);
|
This code fragment returns the erroneous values 0.5, 0.5, 0.5, 0.5 (the correct
values are 10.5,10.5, 5.0 and 5.0).
Workaround: If using SANE, use extended variables in these
situations.
- Taking the address of a floating point formal parameter (function argument) to a function fails. This bug occurs when using either SANE or the
-mc68881 compiler option in combination with float or double function arguments, it does not occur if the function arguments are declared extended . The following fragment illustrates two instances of this bug:
#include <Types.h>
#include <Math.h>
#include <stdio.h>
#define real float /* Fails with either float or double */
main()
{
Bug1(1.0, 2.0, 3.0);
Bug2(1.0, 2.0, 3.0);
}
Bug1 (x, y, z)
real x, y, z;
{
real *p, *q, *r;
/* Take address of arguments, assign directly */
p = &x; q = &y; r = &z;
fprintf(stderr, "Example 1: Before: %g, %g, %g\n", x, y, z);
*p = 11.0;
*q = 12.0;
*r = 13.0;
fprintf(stderr, "Example 1: After: %g, %g, %g\n", x, y, z);
}
Bug2(x, y, z)
real x, y, z;
{
fprintf(stderr, "Example 2: Bug2 Before: %g, %g, %g\n",
x, y, z);
/* Take address of arguments, assign indirectly */
foo(&x, &y, &z);
fprintf(stderr, "Example 2: Bug2 After: %g, %g, %g\n",
x, y, z);
}
foo(x, y, z)
real *x, *y, *z;
{
fprintf(stderr, "Example 2: foo Before: %g, %g, %g\n",
*x, *y, *z);
*x = 11.0;
*y = 12.0;
*z = 13.0;
fprintf(stderr, "Example 2: foo After: %g, %g, %g\n",
*x, *y, *z);
|
This is, in fact, a general problem with C compilers. The underlying reason
for this problem is related to the automatic widening and
narrowing of basic types performed by C compilers. For instance in C, as
defined by K&R (The C Programming Language, Kernighan & Ritchie,
1978, Appendix A Sec. 7.1, p. 186 and Sec. 10.1, P. 206), variables of type
char and short are widened to int before being
passed as function arguments and narrowed before use inside the function.
Similarly, the floating point type float is automatically widened to
double before being passed. K&R notes, however, that "C converts
all float actual parameters to double , so formal parameters
declared float have their declarations adjusted to read
double ." The value of such a formal parameter is not narrowed
to the declared type of the parameter, instead the declared type is
adjusted to match that of the widened value. So, in fact, the sample
code above will fail if real is defined as float , even on a
bug free K&R conforming compiler.
In MPW C, where float and double are widened to
extended , the sample code fails for either float or
double formal parameters. This can, of course, lead to additional
problems if you are porting code from an environment where double was
the widened floating point type (where taking the address of a double formal
parameter would work as expected).
Workaround: Taking the address of a function argument is not recommended;
you should make a local copy in a temporary variable and take the address of
the copy. If you must take the address of a floating point function argument,
make sure it is declared as type extended , and the pointer is of type
extended* .
- The shift operators
>> and << can sometimes produce unexpected results when applied to unsigned short and unsigned char operands. The anomaly lies in the fact that the 680x0 LSR.L and LSL.L instructions are used instead of the LSR.W and LSL.W or the LSR.B and LSL.B instructions. The following example illustrates this anomaly:
main()
{
unsigned short u, c;
short i, k;
u = 0xFFFF;
k = 8;
i = (u << k)/256;
printf("unsigned short: i = %d, %#x\n", i, i);
c = 256;
i = (u << k)/c;
printf("unsigned short: i = %d, %#x\n", i, i);
|
This code fragment returns the values -1, 0xFFFFFFFF and -1, 0xFFFFFFFF, which
are the correct values as defined by the C language, however, you might be
expecting 255, 0xFF and 255, 0xFF.
- The compiler optimization flags
-q (sacrifice code size for speed) and -q2 (memory locations won't change except by explicit stores) can produce incorrect code. We have several vague descriptions of problems, and are looking for more specific examples. The following example illustrates a specific bug that occurs when using enumerated types:
badfunc(bool, i)
Boolean *bool;
int i;
{
while (true) {
if(i < 3) {
*bool = true;
if (func(1)) return;
}
if (func(i)) return;
}
|
The enumerated type here is the type used to define the values true
and false in the header file Types.h . The optimizer is
apparently confused by the fact that true has the char value
1 , which it thinks is the same as the int value 1 to
be passed to the function func(). The object code produced for the two calls to
the function func() is:
...
MOVEQ #$01,D3 ; Move the constant 1 into a register
...
MOVE.B D3,(A2) ; Assign "true" to variable bool
MOVE.B D3,-(A7) ; Attempt to push integer 1 onto stack
; ERROR should be MOVE.L !!!!!
JSR *+$0002 ; JSR to func
...
MOVE.L D4,-(A7) ; Correctly push integer variable i onto stack
JSR *+$0002 ; JSR to func
|
In the first function call, the int constant 1 is passed as a
byte value! Since the stack is correctly adjusted after the first call this
error may go undetected (except that the called function may spot the resulting
nonsensical parameter). This problem is, of course, not limited to the
enumerated type defining true and false , but can occur as a
side effect of any of the many enumerated types defined in the Toolbox header
files.
Workaround:
The best solution, for now, is to avoid using the optimization
flags -q and -q2 altogether.
- The compiler flag
-mc68020 (generate MC68020 instructions) generates inconsistent code when passing structures by value. Specifically, structures larger than 4 bytes that are not a multiple of 4 bytes are padded out to the nearest 4 byte multiple before being pushed onto the stack by the calling routine. Unfortunately, the called routine does not take this padding into account when accessing its function arguments. The following example illustrates this bug:
#include <Types.h>
#include <strings.h>
typedef struct /* 6 byte long structure */
{
short Temp1;
short Temp2;
short Temp3;
} TestStruct;
main()
{
TestStruct reply;
foo(reply,"Hello world.\n");
}
foo(tst ,str)
TestStruct tst;
char *str;
{
Debugger(); /* So we can look, before stepping off the cliff */
printf("%s",str);
|
Since function arguments are pushed onto the stack in left to right order in C,
the pointer to the string constant "Hello world.\n" is pushed
onto the stack before the padded contents of the reply
structure. Thus when the called function, foo() , goes to fetch the
argument str , it looks at the location just beyond where the argument
tst is located. Unfortunately, since the called function does not know
the structure argument was padded, it will not find the correct value for the
second argument (or in the general case, any arguments following the
structure).
Workaround: When using the -mc68020 compiler option, either don't
pass structures larger than 4 bytes by value, or make sure all structures
larger than 4 bytes are padded out to a multiple of 4 bytes.
- Switch statements with non-integer controlling expressions can fail. The following example illustrates the problem:
#include <stdio.h>
#include <strings.h>
main()
{
unsigned short w = 1;
printf("The following test fails in MPW\n");
printf("Should switch to 1; actually got to ");
switch (w) {
case 1:
printf("1\n");
break;
case 5:
printf("5\n");
break;
case 32771:
printf("32771\n");
break;
case 32773:
printf("32773\n");
break;
default:
printf("default\n");
break;
}
|
In this example, instead of reaching case 1: in the switch statement,
the default: case is reached due to the fact that the compiler
generates comparison instructions based on word length values. The 1st Edition
of K&R (The C Programming Language, Kernighan & Ritchie, 1978,
Appendix A Sec. 9.7, p202) requires all case constants to have integer type and
the controlling expression to be coerced to integer type. The 2nd Edition
(ANSI) of K&R (The C Programming Language, Kernighan & Ritchie,
1988, Appendix A Sec. 9.4, p223) requires that the controlling expression and
all case constants undergo "integral promotion"--promotion to int (or
to unsigned int if necessary). MPW 2.0.2 C fails to promote either
the controlling expression or the case constants to type int .
Workaround: If the controlling expression is manually coerced to type
int this example functions correctly.
- Variable declarations inside the body of switch statements, when combined with the
-g compiler flag, can cause the compiler's code generator to abort with the message: "Code generator abort code 615." The following example illustrates the problem:
foo(i)
int i;
{
switch (i) {
int j; /* VARIABLE DECLARATION INSIDE BODY OF SWITCH */
case 0:
j = 22 +i;
printf("INSIDE: i=%d, j=%d\n", i, j);
break;
case 1:
j = 57 -i;
printf("INSIDE: i=%d, j=%d\n", i, j);
break;
default:
j = i;
printf("INSIDE: i=%d, j=%d\n", i, j);
break;
}
|
While such a declaration is perfectly legal C, it is a bad practice (The C
Programming Language, Kernighan & Ritchie, 1978, Appendix A Sec. 9.2,
p201). K&R go on to point out that if the declaration of the variable
contains an initializer, the initialization will NOT take place. This is also
the ANSI draft C standard's interpretation.
Workaround: Since the -g option is a very useful debugging
option, move the variable declaration outside the body of the switch statement.
- Compatibility Note: Local variable declarations of the form
char s[]; (as an array of unspecified length), may not behave as expected. Pre-ANSI compilers often expand such declarations into the equivalent of an extern declaration. MPW 2.0.2 C, and most modern compilers, treat this construct as the declaration of a new array, thus overriding any global declaration with the same name. In the following example:
/* From file foo.c */
char s[255];
main()
{
strcpy(s, "This is one heck of a mess");
otherfunc();
}
/* From file bar.c */
otherfunc()
{
char s[];
printf("%s\n", s);
|
garbage is printed. As a local variable declaration, this declaration is
incomplete since no length is specified or implied, so an ANSI C compiler will
of course fail. This is obviously not a recommended programming practice, but
if you are porting old C code you may encounter this usage.
Workaround: ALWAYS use the declaration extern char s[]; instead.
- Another instance where declarations of the form
type s[]; (as an array of unspecified length) may not behave as expected, is as a member of a struct or union . Pre ANSI compilers often expand such declarations into the equivalent of a pointer declaration. This construct is explicitly prohibited in ANSI C structures and unions. MPW 2.0.2 C on the other hand, issues no warning and treats this construct as the declaration of an array of length zero, which occupies no space in the struct . In the following example:
typedef struct ST1 {
int array1[]; /* Zero length array or Ptr to array? */
int array2[];
int array3[];
}ST1;
main()
{
ST1 s1;
int i1, i2, i3;
i1 = s1.array1[0];
i2 = s1.array2[0];
i3 = s1.array3[0];
|
the three fields of the struct ST1 are located at the same
memory location, and the assignments shown will actually copy garbage into the
integers, since no space was allocated for even the first element of the
arrays. Unfortunately, structures containing an array of unspecified or zero
length as the final member are sometimes used to indicate a structure
containing a variable length array. While this may be useful, it is not
tolerated by ANSI C and thus is not a recommended programming practice.
However, if you are porting old C code you may encounter this usage.
Workaround: ALWAYS use the declarations of the form type *s; ,
type (*s)[]; , or type s[1] (depending on the intended
meaning) in structures and unions instead.
- The routines
_SetUpA5 and _RestoreA5 described in the OS Utilities Chapter of Inside Macintosh, Volume II, are missing. Refer to M.OV.A5 for two new routines which solve this problem.
- Hint: Switch statements with large numbers of cases (over 100 or so) can trigger the appearance of the MPW Bulldozer cursor (signaling heap purging and compacting in progress), can cause "Out of memory" errors from the compiler, or at least take a very long time to compile.
Workaround: Large switch statements can be split up into smaller ones
to avoid these symptoms.
Back to top
Pascal Language
The following information applies to the Pascal compiler and its associated
libraries shipped with the 2.0.2 version of MPW.
- The Pascal compiler generates incorrect code when accessing the elements of
packed Boolean arrays with more than 32767 elements. The generated code contains an ASR.W instruction to compute the byte offset into the array, this of course fails for numbers larger than 32767 ($7FFF). The following example, which zeroes bits far beyond the end of the array itself, illustrates the error:
PROGRAM PackArrayBug;
VAR
MyBits: packed array[0..33000] of Boolean;
PROCEDURE BadCode;
VAR
i: longint;
BEGIN
for i := 0 to 33000 do
MyBits[i] := false;
END;
BEGIN
BadCode
|
Workaround:
Don't use packed Boolean arrays with more than
32767 elements.
- The Pascal compiler fails to detect the situation where a procedure name is passed as an argument to a routine expecting a function as an argument. The following example illustrates the error:
PROGRAM FuncArgBug;
PROCEDURE ExpectsFunc(x: integer; function Visit(y1: longint;
y2: char;): Boolean);
VAR
result: Boolean;
yc: char;
BEGIN
yc := 'A';
result := Visit(x, yc);
END;
PROCEDURE FormalProc(y1: longint; y2: char;);
BEGIN
writeln(y1: 1, ' ', y2);
END;
BEGIN
ExpectsFunc(5, FormalProc);
|
This type of problem typically leads to stack misalignment and spectacular
crashes.
Workaround: Make certain that Pascal routines expecting functions as
arguments are indeed passed functions.
- The
-mc68881 option causes the Pascal compiler to generate incorrect code when calling external C language routines with floating point extended arguments. Apparently the compiler miscalculates the size of the extended argument so that it incorrectly removes the arguments from the stack. The following example, which can corrupt the local variables of its caller, illustrates the error:
FUNCTION E2S(f: Str255; x: extended): Str255;
VAR
dummy: integer;
i: integer;
t: Str255;
FUNCTION sprintf(str: Ptr; fmt: Ptr; x: extended):
integer; C; EXTERNAL;
BEGIN
t[0] := chr(0);
f[ord(f[0]) +1] := chr(0);
dummy := sprintf(@t[1], @f[1], x);
i := 0;
repeat
i := i+1;
until ((t[i] = chr(0)) or (i > 254));
t[0] := chr(i);
E2S := t;
|
The relevant portions of the generated code are:
LINK A6,#$FDF0 ; LINK for local vars
MOVEM.L D6/D7,-(A7) ; Save Registers used here
...
...
LEA $FF00(A6),A0 ; Get address of extended var
MOVE.L -(A0),-(A7) ; Push extended onto stack
MOVE.L -(A0),-(A7)
MOVE.L -(A0),-(A7)
PEA $FF01(A6) ; Push address of format str
PEA $FDF1(A6) ; Push address of target str
JSR *+$0002 ; JSR to sprintf
LEA $0012(A7),A7 ; Pop args off stack
; (SHOULD pop off $14 bytes!)
MOVE.W D0,D6 ; Save function result
...
...
MOVEM.L (A7)+,D6/D7 ; Restore registers used here
; (Now they've been corrupted!)
UNLK A6 ; Correctly restores A7
MOVEA.L (A7)+,A0
ADDQ.W #$8,A7
|
Notice that this code would have succeeded if the routine had not used the
D6 and D7 registers for storage, and then restored them
(incorrectly) before returning.
Workaround: When calling such a routine with the -mc68881
option, isolate the call in a small subroutine or function that has no local
variables, so that registers will not need to be saved and restored.
- The
{$SC+} compiler flag for short circuiting AND and OR operators can sometimes produce incorrect code. The following example does not work if {$SC+} has been used:
USES
Memtypes,QuickDraw,OSIntf,ToolIntf,PackIntf;
VAR
b1,b2,b3 : BOOLEAN;
Begin
b1 := false;
b2 := true;
b3 := true;
if not (b1 and b2) and b3 then
SysBeep(40);
|
Workaround: Don't use the {$SC+} compiler flag.
- The Pascal compiler generates incorrect overflow checking code for
longint valued arguments to the Odd function. The generated code contains a signed divide (DIVS ) by 1 followed by a TRAPV , thus the overflow flag is set for values greater than $7FFF. The following example will fail with a CHK exception, unless the {$OV+} directive is removed:
{$OV+}
PROGRAM PascalOdd;
VAR
IsOdd: Boolean;
longval: LONGINT;
BEGIN
longval := 123456;
IsOdd := Odd(longval);
|
Workaround:
Don't use the {$OV+} compiler flag if you pass
longint values to the Odd function.
- The Pascal compiler generates incorrect code when functions with
RECORD type are used as the object of a WITH statement. This will only occur if the function is called at a level below the main program, and if the length of the RECORD type is 4 bytes or less. The generated code often contains an LEA (A7)+, an instruction, which is of course illegal. The following example demonstrates this unusual situation:
PROGRAM PasRecordValFuncBug;
TYPE
OurRecord =
RECORD
a: Integer;
END;
FUNCTION RecordValuedFunction: OurRecord;
BEGIN
END;
PROCEDURE ContainsBadCode;
BEGIN
WITH RecordValuedFunction DO BEGIN { This usage bad code. }
a:=a;
END;
END;
BEGIN { PasRecordValFuncBug }
ContainsBadCode;
WITH RecordValuedFunction DO BEGIN { This usage is okay. }
a:=a;
END;
|
Workaround: Don't use RECORD valued functions as the object of
WITH statements.
Back to top
Assembly Language
The following information applies to the Assembler and its associated libraries
shipped with the 2.0.2 version of MPW.
- There are no known outstanding bugs in the MPW Assembler.
Back to top
Interface Libraries
The following information applies to the Toolbox and OS interface libraries
shipped with the 2.0.2 version of MPW.
- The glue for the Device Manager call
GetDCtlValue [Not in ROM] described in Inside Macintosh, Volume III, is incorrect and will return an incorrect value for the handle to the driver's device control entry. The following is a corrected version of the erroneous glue found in the library file Interface.o :
;-----------------------------------------------------
;FUNCTION GetDCtlEntry(refNum: Integer) : DCtlHandle;
;-----------------------------------------------------
GetDCtlEntry PROC EXPORT
MOVEA.L (SP)+,A0 ;Get the return address
MOVE.W (SP)+,D0 ;Get the refNum
ADDQ.W #$1,D0 ;Change to a
NEG.W D0 ; Unit Number
;===> LSR.W #$2,D0 ;==Shift in wrong direction!
LSL.W #$2,D0 ;Times 4 bytes/entry
MOVEA.L UTableBase,A1 ;Get address of unit table
MOVE.L (A1,D0.W),(SP) ;Get the DCtlHandle
|
This error will affect C, Pascal, and assembly language users.
Workaround:
Use the corrected glue for GetDCtlValue .
- The glue for the register-based Vertical Retrace Manager calls
_SlotVInstall and _SlotVRemove described in Inside Macintosh, Volume V, is incorrect. The following are corrected versions of the erroneous glue for these routines found in the library file Interface.o :
;-----------------------------------------------------------------------
;FUNCTION SlotVInstall(vblTaskPtr: QElemPtr; theSlot: Integer): OSErr;
;-----------------------------------------------------------------------
SlotVInstall PROC EXPORT
MOVEA.L (A7)+,A1 ; save return address
MOVE.W (A7)+,D0 ; the slot number
MOVEA.L (A7)+,A0 ; the VBL task ptr
_SlotVInstall
MOVE.W D0,(A7) ; save result code on stack
JMP (A1) ; return to caller
ENDPROC
;----------------------------------------------------------------------
;FUNCTION SlotVRemove(vblTaskPtr: QElemPtr; theSlot: Integer): OSErr;
;----------------------------------------------------------------------
SlotVRemove PROC EXPORT
MOVEA.L (A7)+,A1 ; save return address
MOVE.W (A7)+,D0 ; the slot number
MOVEA.L (A7)+,A0 ; the VBL task ptr
_SlotVRemove
MOVE.W D0,(A7) ; save result code on stack
JMP (A1) ; return to caller
|
These errors will affect C, Pascal, and assembly language users.
Workaround: Use the corrected glue for _SlotVInstall and
_SlotVRemove .
- The glue for the register based Start Manager calls
_GetTimeout and _SetTimeout described in Inside Macintosh, Volume V, is incorrect . The following are corrected versions of the erroneous glue for these routines found in the library file Interface.o :
;-----------------------------------------------------------------------
;PROCEDURE GetTimeout(VAR count: INTEGER);
;-----------------------------------------------------------------------
GetTimeout PROC EXPORT
;===> CLR.W -(A7) ;===OOPS, selector in A0 not on stack
SUBA.L A0,A0 ;Put selector in A0, i.e. 0
_InternalWait
MOVEA.L (A7)+,A1 ;Pop return address into A1
MOVEA.L (A7)+,A0 ;Pop location for VAR count
MOVE.W D0,(A0) ;Stuff returned value into count
JMP (A1) ;And go home
;----------------------------------------------------------------------
; PROCEDURE SetTimeout(count: INTEGER);
;----------------------------------------------------------------------
SetTimeout PROC EXPORT
MOVEA.L (A7)+,A1 ;Pop return address into A1
MOVE.W (A7),D0 ;Move count parameter into D0
;===> MOVE.W #$0001,(A7) ;===OOPS, selector in A0 not on stack
MOVEA.W #$0001,A0 ;Put selector in A0
_InternalWait
|
These errors will affect C, Pascal, and assembly language users.
Workaround: Use the corrected glue for _GetTimeout and
_SetTimeout .
Back to top References
M.OV.A5
Back to top Downloadables
|
Acrobat version of this Note (K).
|
Download
|
|