Hello,

This is a follow-up on the previous discussion about ASSERT_RETURN
that I proposed. Since then, I have been thinking, and come to some
tentative conclusions:
* Giel is right that it should be ASSERT_OR_RETURN (not
ASSERT_AND_RETURN as I mistakenly suggested)
* Both of these are too long to type and lead to unnecessary line
wrapping that are hard to read
* Adding a return value inside a macro is far too ugly. It makes the
code hard to read and make return values bug-prone to change
* assert(!expr) where expr is a const char does not have the desired
effect of displaying the contents of expr as previously suggested

I tried making a ASSERT_CONDITIONAL, which is basically an assert
inside an if expression, which meant you could write if
(!ASSERT_CONDITIONAL(something, "blah")) return 0;, but this turned
out to be exceptionally difficult, mostly because assert returns void
and cannot be placed inside a conditional expression, and it also
cannot be wrapped inside a function without losing the correct code
location number in the assert() message.

So what I came up with is the attached ASSERT_ORZ() macro. The name is
horrible, I know, but the alternative was to call it by its full
proper name ASSERT_OR_RETURN_ZERO, which nobody would bother typing
and would wrap lines like there was no tomorrow. It asserts the given
condition, and if it fails it will throw an assert and then return
zero. Zero works for the three cases where the function returns one of
zero, NULL or false in case of error, which may sound like little but
actually covers almost every case. It (and also fixed ordinary ASSERT
to work the same way) only evaluates the condition once in case of
success (for performance, not safety), and twice in case of failure in
order to give assert() the correct expression.

I made some example changes to game.c in the patch that shows how it
works, and how it saves vital typing time and code lines.

Comments are as always welcome :-)

 - Per
Index: src/game.c
===================================================================
--- src/game.c	(revision 6987)
+++ src/game.c	(working copy)
@@ -3471,11 +3471,7 @@
 	UDWORD			fileExtension;
 	DROID			*psDroid, *psNext;
 
-	ASSERT(aFileName && strlen(aFileName) > 4, "Bad savegame filename");
-	if (!aFileName || strlen(aFileName) < 4)
-	{
-		return false;
-	}
+	ASSERT_ORZ(aFileName && strlen(aFileName) > 4, "Bad savegame filename");
 
 	debug(LOG_WZ, "saveGame: %s", aFileName);
 
@@ -4824,14 +4820,8 @@
 	saveGame.GameType = saveType;
 
 	//save the current level so we can load up the STARTING point of the mission
-	if (strlen(aLevelName) > MAX_LEVEL_SIZE)
-	{
-		ASSERT( false,
-			"writeGameFile:Unable to save level name - too long (max20) - %s",
-			aLevelName );
-
-		return false;
-	}
+	ASSERT_ORZ(strlen(aLevelName) < MAX_LEVEL_SIZE, "Unable to save level name - too long (max %d) - %s",
+	           (int)MAX_LEVEL_SIZE, aLevelName);
 	sstrcpy(saveGame.levelName, aLevelName);
 
 	//save out the players power
@@ -5233,11 +5223,7 @@
 	for (i=0; i < psSaveDroid->numWeaps; i++)
 	{
 		int weapon = getCompFromName(COMP_WEAPON, psSaveDroid->asWeaps[i].name);
-		if( weapon < 0)
-		{
-			ASSERT(false, "This component does not exist : %s", psSaveDroid->asWeaps[i].name );
-			return NULL;
-		}
+		ASSERT_ORZ(weapon >= 0, "This component does not exist : %s", psSaveDroid->asWeaps[i].name);
 		psTemplate->asWeaps[i] = weapon;
 	}
 
@@ -5333,12 +5319,7 @@
 		}
 		psTemplate->asParts[i] = (UDWORD)compInc;
 	}
-	if (!found)
-	{
-		//ignore this record
-		ASSERT( found,"buildUnitFromSavedUnit; failed to find weapon" );
-		return NULL;
-	}
+	ASSERT_ORZ(found, "Failed to find weapon");
 	psTemplate->numWeaps = psSaveDroid->numWeaps;
 
 	if (psSaveDroid->numWeaps > 0)
@@ -5346,11 +5327,7 @@
 		for(i = 0;i < psTemplate->numWeaps;i++)
 		{
 			int weapon = getCompFromName(COMP_WEAPON, psSaveDroid->asWeaps[i].name);
-			if( weapon < 0)
-			{
-				ASSERT(false, "This component does not exist : %s", psSaveDroid->asWeaps[i].name );
-				return NULL;
-			}
+			ASSERT_ORZ(weapon >= 0, "This component does not exist : %s", psSaveDroid->asWeaps[i].name);
 			psTemplate->asWeaps[i] = weapon;
 		}
 	}
@@ -5382,13 +5359,8 @@
 			psSaveDroid->player, false);
 	}
 
-	if(psDroid == NULL)
-	{
-		ASSERT( false,"buildUnitFromSavedUnit; failed to build unit" );
-		return NULL;
-	}
+	ASSERT_ORZ(psDroid, "Failed to build unit");
 
-
 	//copy the droid's weapon stats
 	for (i=0; i < psDroid->numWeaps; i++)
 	{
@@ -5744,23 +5716,14 @@
 		}
 		psTemplate->asParts[i] = (UDWORD)compInc;
 	}
-	if (!found)
-	{
-		//ignore this record
-		ASSERT( found,"buildUnitFromSavedUnit; failed to find weapon" );
-		return NULL;
-	}
+	ASSERT_ORZ(found, "Failed to find weapon");
 	psTemplate->numWeaps = psSaveDroid->numWeaps;
 	if (psSaveDroid->numWeaps > 0)
 	{
 		for(i = 0;i < psTemplate->numWeaps;i++)
 		{
 			int weapon = getCompFromName(COMP_WEAPON, psSaveDroid->asWeaps[i].name);
-			if( weapon < 0)
-			{
-				ASSERT(false, "This component does not exist : %s", psSaveDroid->asWeaps[i].name );
-				return NULL;
-			}
+			ASSERT_ORZ(weapon >= 0, "This component does not exist : %s", psSaveDroid->asWeaps[i].name);
 			psTemplate->asWeaps[i] = weapon;
 		}
 	}
@@ -5794,11 +5757,7 @@
 			psSaveDroid->player, false);
 	}
 
-	if(psDroid == NULL)
-	{
-		ASSERT( false,"buildUnitFromSavedUnit; failed to build unit" );
-		return NULL;
-	}
+	ASSERT_ORZ(psDroid, "Failed to build unit");
 
 	turnOffMultiMsg(false);
 
@@ -6997,11 +6956,7 @@
 
         psStructure = buildStructure(psStats, psSaveStructure->x, psSaveStructure->y,
 			psSaveStructure->player,true);
-		if (!psStructure)
-		{
-			ASSERT( false, "loadSaveStructure:Unable to create structure" );
-			return false;
-		}
+	ASSERT_ORZ(psStructure, "Unable to create structure");
 
         /*The original code here didn't work and so the scriptwriters worked
         round it by using the module ID - so making it work now will screw up
@@ -7284,13 +7239,8 @@
             continue;
         }
 
-		psStructure = buildStructure(psStats, psSaveStructure->x, psSaveStructure->y,
-			psSaveStructure->player,true);
-		if (!psStructure)
-		{
-			ASSERT( false, "loadSaveStructure:Unable to create structure" );
-			return false;
-		}
+	psStructure = buildStructure(psStats, psSaveStructure->x, psSaveStructure->y, psSaveStructure->player,true);
+	ASSERT_ORZ(psStructure, "Unable to create structure");
 
         /*The original code here didn't work and so the scriptwriters worked
         round it by using the module ID - so making it work now will screw up
@@ -7724,13 +7674,8 @@
             continue;
         }
 
-		psStructure = buildStructure(psStats, psSaveStructure->x, psSaveStructure->y,
-			psSaveStructure->player,true);
-		if (!psStructure)
-		{
-			ASSERT( false, "loadSaveStructure:Unable to create structure" );
-			return false;
-		}
+	psStructure = buildStructure(psStats, psSaveStructure->x, psSaveStructure->y, psSaveStructure->player, true);
+	ASSERT_ORZ(psStructure, "Unable to create structure");
 
         /*The original code here didn't work and so the scriptwriters worked
         round it by using the module ID - so making it work now will screw up
@@ -8541,11 +8486,7 @@
 		pFeature = buildFeature(psStats, psSaveFeature->x, psSaveFeature->y,true);
 		//will be added to the top of the linked list
 		//pFeature = apsFeatureLists[0];
-		if (!pFeature)
-		{
-			ASSERT( false, "loadSaveFeature:Unable to create feature" );
-			return false;
-		}
+		ASSERT_ORZ(pFeature, "Unable to create feature");
 		//restore values
 		pFeature->id = psSaveFeature->id;
 		pFeature->direction = psSaveFeature->direction;
@@ -8645,11 +8586,7 @@
 		pFeature = buildFeature(psStats, psSaveFeature->x, psSaveFeature->y,true);
 		//will be added to the top of the linked list
 		//pFeature = apsFeatureLists[0];
-		if (!pFeature)
-		{
-			ASSERT( false, "loadSaveFeature:Unable to create feature" );
-			return false;
-		}
+		ASSERT_ORZ(pFeature, "Unable to create feature");
 		//restore values
 		pFeature->id = psSaveFeature->id;
 		pFeature->direction = psSaveFeature->direction;
Index: lib/framework/debug.h
===================================================================
--- lib/framework/debug.h	(revision 6987)
+++ lib/framework/debug.h	(working copy)
@@ -54,9 +54,19 @@
 /** Whether asserts are currently enabled. */
 extern bool assertEnabled;
 
+/** Deals with failure in an assert. Expression is (re-)evaluated for output in the assert() call. */
+#define ASSERT_FAILURE(expr, expr_string, location_description, function, ...) \
+	( \
+		(void)_debug(LOG_ERROR, function, __VA_ARGS__), \
+		(void)_debug(LOG_ERROR, function, "Assert in Warzone: %s (%s), last script event: '%s'", \
+	                                  location_description, expr_string, last_called_script_event), \
+		( assertEnabled ? assert(expr) : (void)0 )\
+	)
+
 /**
- * ASSERT helper macro to allow some debug functions to use an alternate
- * calling location.
+ * Internal assert helper macro to allow some debug functions to use an alternate calling location.
+ * Expression is only evaluated once if true, if false it is evaluated another time to provide decent 
+ * feedback on OSes that have good GUI facilities for asserts and lousy backtrace facilities. 
  *
  * \param expr                 Expression to assert on.
  * \param location_description A string describing the calling location, e.g.:
@@ -71,17 +81,10 @@
  */
 #define ASSERT_HELPER(expr, location_description, function, ...) \
 ( \
-	( \
-		(expr) ? /* if (expr) */ \
-			(void)0 \
-		: /* else */\
-		( \
-			(void)_debug(LOG_ERROR, function, __VA_ARGS__), \
-			(void)_debug(LOG_ERROR, function, "Assert in Warzone: %s (%s), last script event: '%s'", \
-		                                  location_description, (#expr), last_called_script_event) \
-		) \
-	), \
-	assertEnabled ? assert(expr) : (void)0 \
+	(expr) ? /* if (expr) */ \
+		(void)0 \
+	: /* else */\
+	ASSERT_FAILURE(expr, #expr, location_description, function, __VA_ARGS__) \
 )
 
 /**
@@ -94,7 +97,15 @@
 #define ASSERT(expr, ...) \
 	ASSERT_HELPER(expr, AT_MACRO, __FUNCTION__, __VA_ARGS__)
 
+/**
+ *
+ * Assert-or-return-zero, macro that returns zero (can also be interpreted as false or NULL) on failure,
+ * and also provides asserts and debug output for debugging.
+ */
+#define ASSERT_ORZ(expr, ...) \
+	do { bool _wzeval = (expr); if (!_wzeval) { ASSERT_FAILURE(expr, #expr, AT_MACRO, __FUNCTION__, __VA_ARGS__); return 0; } } while (0)
 
+
 /**
  * Compile time assert
  * Not to be used in global context!
_______________________________________________
Warzone-dev mailing list
Warzone-dev@gna.org
https://mail.gna.org/listinfo/warzone-dev

Reply via email to