Rework error handling

Previously, various error handling functions were defined, but not all
of them were even used. The new error handling functions handle the most
common cases:

- Assertions. These can be handled with `ASSERT()`, which gives the
  error location for debugging.

- Errors we don't know how to handle, like GetNewWindow returning NULL.
  These can be handled with `EXIT_INTERNAL()`, which gives the error
  location for debugging.

- Out of memory conditions. These can be handled with `ShowMemError()`.

- System errors in response to user operations. These can be handled
  with `ShowError()`.
This commit is contained in:
Dietrich Epp 2023-05-06 19:41:40 -04:00
parent 56a3983129
commit b1bcae531b
7 changed files with 163 additions and 94 deletions

View File

@ -47,7 +47,8 @@ static void SelectCurrentDirectory(struct ChooseDirReply *reply)
ci.dirInfo.ioDrDirID = LMGetCurDirStore(); ci.dirInfo.ioDrDirID = LMGetCurDirStore();
err = PBGetCatInfoSync(&ci); err = PBGetCatInfoSync(&ci);
if (err != 0) { if (err != 0) {
ExitErrorOS(kErrUnknown, err); ShowError(kErrNone, kErrNone, err, NULL);
return;
} }
directory = reply->directory; directory = reply->directory;
directory->vRefNum = ci.dirInfo.ioVRefNum; directory->vRefNum = ci.dirInfo.ioVRefNum;
@ -99,10 +100,10 @@ Boolean ChooseDirectory(FSSpec *directory)
switch (cdreply.status) { switch (cdreply.status) {
case kCDChild: case kCDChild:
BlockMoveData(&sfreply.sfFile, directory, sizeof(FSSpec)); BlockMoveData(&sfreply.sfFile, directory, sizeof(FSSpec));
return 1; return true;
case kCDCurrent: case kCDCurrent:
return 1; return true;
default: default:
return 0; return false;
} }
} }

View File

@ -3,73 +3,120 @@
// Mozilla Public License, version 2.0. See LICENSE.txt for details. // Mozilla Public License, version 2.0. See LICENSE.txt for details.
#include "macos/error.h" #include "macos/error.h"
#include "lib/defs.h"
#include "macos/main.h" #include "macos/main.h"
#include "macos/pstrbuilder.h"
#include "macos/resources.h" #include "macos/resources.h"
#include "macos/strutil.h"
#include <Dialogs.h> #include <Dialogs.h>
#include <TextUtils.h> #include <TextUtils.h>
void ExitError(ErrorCode errCode) enum {
// OK
kStrOK = 1,
// Quit
kStrQuit,
// An error of type ^2 occurred.
kStrErrorCode,
// Error at: ^1:^2
kStrErrorAt,
// Assertion: ^1
kStrAssertion,
// An internal error occurred.
kStrInternal,
// An unknown error occurred.
kStrUnknown,
// (Base value for error messages.)
kStrMessageBase
};
static void AppendErrorArray(struct PStrBuilder *str, int sep, int strNum,
int paramCount, const unsigned char *const *params)
{ {
Str255 message; Str255 message;
GetIndString(message, rSTRS_Errors, errCode); if (str->data[0] > 0 && sep != 0) {
if (message[0] == 0) { PStrAppendChar(str, sep);
GetIndString(message, rSTRS_Errors, kErrUnknown); }
GetIndString(message, rSTRS_Errors, strNum);
if (message[0] == 0) {
PStrAppendChar(str, '?');
} else {
PStrAppendSubstitute(str, message, paramCount, params);
} }
ParamText(message, NULL, NULL, NULL);
Alert(rAlrtError, NULL);
QuitApp();
} }
void ExitErrorOS(ErrorCode errCode, short osErr) static void AppendError0(struct PStrBuilder *str, int sep, int strNum)
{ {
Str255 message; AppendErrorArray(str, sep, strNum, 0, NULL);
GetIndString(message, rSTRS_Errors, errCode);
if (message[0] == 0) {
GetIndString(message, rSTRS_Errors, kErrUnknown);
}
StrAppendFormat(message, "\p\rError code: %d", osErr);
ParamText(message, NULL, NULL, NULL);
Alert(rAlrtError, NULL);
QuitApp();
} }
void ExitMemError(void) static void AppendError1(struct PStrBuilder *str, int sep, int strNum,
const unsigned char *strParam)
{ {
ExitErrorOS(kErrOutOfMemory, MemError()); AppendErrorArray(str, sep, strNum, 1, &strParam);
}
static void AppendError2(struct PStrBuilder *str, int sep, int strNum,
const unsigned char *strParam, int intParam)
{
unsigned char num[16];
const unsigned char *params[2];
params[0] = strParam;
NumToString(intParam, num);
params[1] = num;
AppendErrorArray(str, sep, strNum, 2, params);
}
static void ShowErrorAlert(const unsigned char *message, int button)
{
Str255 buttonText;
GetIndString(buttonText, rSTRS_Errors, button);
ParamText(message, buttonText, NULL, NULL);
Alert(rAlrtError, NULL);
} }
void ExitAssert(const unsigned char *file, int line, void ExitAssert(const unsigned char *file, int line,
const unsigned char *message) const unsigned char *assertion)
{ {
Str255 dmessage; struct PStrBuilder str;
GetIndString(dmessage, rSTRS_Errors, kErrInternal); PStrInit(&str);
StrAppendFormat(dmessage, "\p\rError at: %S:%d", file, line); AppendError0(&str, 0, kStrInternal);
if (message != NULL) { if (file != NULL) {
StrAppendFormat(dmessage, "\p: %S", message); AppendError2(&str, '\r', kStrErrorAt, file, line);
} }
ParamText(dmessage, NULL, NULL, NULL); if (assertion != NULL) {
Alert(rAlrtError, NULL); AppendError1(&str, '\r', kStrAssertion, assertion);
}
ShowErrorAlert(str.data, kStrQuit);
QuitApp(); QuitApp();
} }
void ShowError(const struct ErrorParams *p) void ShowError(ErrorCode err1, ErrorCode err2, short osErr,
const unsigned char *strParam)
{ {
Str255 message; struct PStrBuilder str;
GetIndString(message, rSTRS_Errors, p->err); PStrInit(&str);
if (message[0] == 0) { if (err1 != kErrNone) {
GetIndString(message, rSTRS_Errors, kErrUnknown); AppendError1(&str, ' ', kStrMessageBase - 1 + err1, strParam);
} else if (p->strParam != NULL) {
StrSubstitute(message, p->strParam);
} }
if (p->osErr != NULL) { if (err2 != kErrNone) {
StrAppendFormat(message, "\p\rError code: %d", p->osErr); AppendError1(&str, ' ', kStrMessageBase - 1 + err2, strParam);
} }
ParamText(message, NULL, NULL, NULL); if (osErr != 0) {
Alert(rAlrtError, NULL); AppendError2(&str, ' ', kStrErrorCode, NULL, osErr);
}
if (str.data[0] == 0) {
AppendError0(&str, ' ', kStrUnknown);
}
ShowErrorAlert(str.data, kStrOK);
}
void ShowMemError(void)
{
ShowError(kErrOutOfMemory, kErrNone, MemError(), NULL);
} }

View File

@ -6,32 +6,34 @@
// Error codes, corresponding to messages in a STR# resource. This should be // Error codes, corresponding to messages in a STR# resource. This should be
// kept in sync with STR# rSTRS_Errors in resources.r. // kept in sync with STR# rSTRS_Errors in resources.r.
typedef enum { typedef enum ErrorCode {
// An unknown error occurred. // (no error)
kErrUnknown = 1, kErrNone,
// An internal error occurred.
kErrInternal,
// Out of memory. // Out of memory.
kErrOutOfMemory, kErrOutOfMemory,
// Could not save project "^1". // Could not save project "^1".
kErrCouldNotSaveProject, kErrCouldNotSaveProject,
// Could not read project "^1".
kErrCouldNotReadProject,
// The project file is damaged.
kErrProjectDamaged,
// The project file is from an unknown version of SyncFiles.
kErrProjectUnknownVersion,
// Could not query volume parameters.
kErrVolumeQuery,
// Could not create alias.
kErrAlias,
// Could not get directory path.
kErrDirPath
} ErrorCode; } ErrorCode;
// ExitError shows an error dialog with the given error message, then quits the // ExitAssert shows an assertion error and quits the program. Either the file or
// program. // the assertion may be NULL.
void ExitError(ErrorCode errCode);
// ExitErrorOS shows an error dialog with the given error message and an OS
// error code, then quits the program.
void ExitErrorOS(ErrorCode errCode, short osErr);
// ExitMemError shows an out of memory error and quits the program.
void ExitMemError(void);
// ExitAssert shows an assertion error and quits the program. The message may be
// NULL.
void ExitAssert(const unsigned char *file, int line, void ExitAssert(const unsigned char *file, int line,
const unsigned char *message); const unsigned char *assertion);
// EXIT_INTERNAL shows an internal error message and quits the program.
#define EXIT_INTERNAL() ExitAssert("\p" __FILE__, __LINE__, NULL)
// EXIT_ASSERT shows an assertion error and quits the program. The message may // EXIT_ASSERT shows an assertion error and quits the program. The message may
// be NULL. // be NULL.
@ -45,24 +47,13 @@ void ExitAssert(const unsigned char *file, int line,
ExitAssert("\p" __FILE__, __LINE__, "\p" #p); \ ExitAssert("\p" __FILE__, __LINE__, "\p" #p); \
} while (0) } while (0)
// An ErrorParams contains the parameters for displaying en error alert window. // ShowMemError shows an out of memory error to the user.
// This structure should be zeroed before use, in case additional fields are void ShowMemError(void);
// added.
struct ErrorParams {
// The application error code. If this is zero, it will be treated as
// kErrInternal.
ErrorCode err;
// The OS error code. This is displayed at the end of the error message, // ShowError shows an error alert window to the user. The error codes are
// unless it is zero. // displayed if they are not 0. If osErr is not 0, then it is displayed as well.
short osErr; // Any ^1 parameters in the error messages are replaced with strParam.
void ShowError(ErrorCode err1, ErrorCode err2, short osErr,
// If the error messages contain any string substitutions like "^1", those const unsigned char *strParam);
// substitutions are replaced with this string.
const unsigned char *strParam;
};
// ShowError shows an error alert window to the user.
void ShowError(const struct ErrorParams *p);
#endif #endif

View File

@ -130,11 +130,11 @@ void ProjectNew(void)
project = (ProjectHandle)NewHandle(sizeof(struct Project)); project = (ProjectHandle)NewHandle(sizeof(struct Project));
if (project == NULL) { if (project == NULL) {
EXIT_ASSERT(NULL); EXIT_INTERNAL();
} }
window = GetNewCWindow(rWIND_Project, NULL, (WindowPtr)-1); window = GetNewCWindow(rWIND_Project, NULL, (WindowPtr)-1);
if (window == NULL) { if (window == NULL) {
EXIT_ASSERT(NULL); EXIT_INTERNAL();
} }
windowWidth = window->portRect.right - window->portRect.left; windowWidth = window->portRect.right - window->portRect.left;
SetWRefCon(window, (long)project); SetWRefCon(window, (long)project);
@ -145,7 +145,7 @@ void ProjectNew(void)
for (i = 0; i < 2; i++) { for (i = 0; i < 2; i++) {
control = GetNewControl(rCNTL_ChooseFolder, window); control = GetNewControl(rCNTL_ChooseFolder, window);
if (control == NULL) { if (control == NULL) {
EXIT_ASSERT(NULL); EXIT_INTERNAL();
} }
controlWidth = controlWidth =
(*control)->contrlRect.right - (*control)->contrlRect.left; (*control)->contrlRect.right - (*control)->contrlRect.left;
@ -453,7 +453,6 @@ static void ProjectSave(WindowRef window, ProjectHandle project,
Str255 name; Str255 name;
StandardFileReply reply; StandardFileReply reply;
OSErr err; OSErr err;
struct ErrorParams errp;
FSSpec spec; FSSpec spec;
ScriptCode script; ScriptCode script;
@ -483,11 +482,7 @@ static void ProjectSave(WindowRef window, ProjectHandle project,
return; return;
error: error:
memset(&errp, 0, sizeof(errp)); ShowError(kErrCouldNotSaveProject, kErrNone, err, reply.sfFile.name);
errp.err = kErrCouldNotSaveProject;
errp.osErr = err;
errp.strParam = reply.sfFile.name;
ShowError(&errp);
} }
void ProjectCommand(WindowRef window, ProjectHandle project, int menuID, void ProjectCommand(WindowRef window, ProjectHandle project, int menuID,
@ -569,7 +564,8 @@ static Boolean VolumeDoesSupportIDs(short vRefNum)
hb.ioParam.ioReqCount = sizeof(parms); hb.ioParam.ioReqCount = sizeof(parms);
err = PBHGetVolParms(&hb, false); err = PBHGetVolParms(&hb, false);
if (err != noErr) { if (err != noErr) {
ExitErrorOS(kErrUnknown, err); ShowError(kErrVolumeQuery, kErrNone, err, NULL);
return false;
} }
return (parms.vMAttrib & (1ul << bHasFileIDs)) != 0; return (parms.vMAttrib & (1ul << bHasFileIDs)) != 0;
} }
@ -591,15 +587,21 @@ static void ProjectChooseDir(WindowRef window, ProjectHandle project,
if (VolumeDoesSupportIDs(directory.vRefNum)) { if (VolumeDoesSupportIDs(directory.vRefNum)) {
err = NewAlias(NULL, &directory, &alias); err = NewAlias(NULL, &directory, &alias);
if (err != noErr) { if (err != noErr) {
ExitErrorOS(kErrUnknown, err); ShowError(kErrAlias, kErrNone, err, NULL);
return;
} }
} else { } else {
alias = NULL; alias = NULL;
} }
err = GetDirPath(&directory, &pathLength, &path); err = GetDirPath(&directory, &pathLength, &path);
if (err != noErr) { if (err != noErr) {
ExitErrorOS(kErrUnknown, err); ShowError(kErrDirPath, kErrNone, err, NULL);
if (alias != NULL) {
DisposeHandle((Handle)alias);
}
return;
} }
projectp = *project; projectp = *project;
dirp = &projectp->dirs[whichDir]; dirp = &projectp->dirs[whichDir];
if (dirp->path != NULL) { if (dirp->path != NULL) {

View File

@ -5,6 +5,12 @@
#include <string.h> #include <string.h>
void PStrInit(struct PStrBuilder *buf)
{
buf->data[0] = 0;
buf->truncated = 0;
}
static void PStrAppendMem(struct PStrBuilder *buf, const unsigned char *src, static void PStrAppendMem(struct PStrBuilder *buf, const unsigned char *src,
int slen) int slen)
{ {
@ -35,6 +41,11 @@ static void PStrAppendMem(struct PStrBuilder *buf, const unsigned char *src,
buf->data[0] = dlen + slen; buf->data[0] = dlen + slen;
} }
void PStrAppendChar(struct PStrBuilder *buf, unsigned char c)
{
PStrAppendMem(buf, &c, 1);
}
void PStrAppend(struct PStrBuilder *buf, const unsigned char *src) void PStrAppend(struct PStrBuilder *buf, const unsigned char *src)
{ {
PStrAppendMem(buf, src + 1, src[0]); PStrAppendMem(buf, src + 1, src[0]);

View File

@ -29,6 +29,12 @@ struct PStrBuilder {
Boolean truncated; Boolean truncated;
}; };
// PStrInit initializes a string buffer.
void PStrInit(struct PStrBuilder *buf);
// PStrAppendChar appends a single character to the buffer.
void PStrAppendChar(struct PStrBuilder *buf, unsigned char c);
// PStrAppend appends a string to the buffer. // PStrAppend appends a string to the buffer.
void PStrAppend(struct PStrBuilder *buf, const unsigned char *src); void PStrAppend(struct PStrBuilder *buf, const unsigned char *src);

View File

@ -119,10 +119,21 @@ resource 'BNDL' (128, purgeable) {
}; };
resource 'STR#' (rSTRS_Errors) {{ resource 'STR#' (rSTRS_Errors) {{
"An unknown error occurred.", "OK",
"Quit",
"An error of type ^2 occurred.",
"Error at: ^1:^2",
"Assertion: ^1",
"An internal error occurred.", "An internal error occurred.",
"An unknown error occurred.",
"Out of memory.", "Out of memory.",
"Could not save project “^1”.", "Could not save project “^1”.",
"Could not read project “^1”.",
"The project file is damaged.",
"The project file is from an unknown version of SyncFiles.",
"Could not query volume parameters.",
"Could not create alias.",
"Could not get directory path.",
}}; }};
resource 'WIND' (rWIND_Project, preload, purgeable) { resource 'WIND' (rWIND_Project, preload, purgeable) {