Changeset - r14077:8447889ed7f9
[Not reviewed]
master
0 1 0
rubidium - 14 years ago 2009-12-25 23:14:12
rubidium@openttd.org
(svn r18634) -Revert (r16808): the fix doesn't work in all cases
-Fix [FS#3421] (r16838): crash when invalid pointers are left due to saveload failing at e.g. decompressing the savegame
1 file changed with 50 insertions and 29 deletions:
0 comments (0 inline, 0 general)
src/saveload/saveload.cpp
Show inline comments
 
@@ -61,12 +61,13 @@ typedef size_t ReaderProc();
 

	
 
/** What are we currently doing? */
 
enum SaveLoadAction {
 
	SLA_LOAD, ///< loading
 
	SLA_SAVE, ///< saving
 
	SLA_PTRS, ///< fixing pointers
 
	SLA_NULL, ///< null all pointers (on loading error)
 
};
 

	
 
enum NeedLength {
 
	NL_NONE = 0,       ///< not working in NeedLength mode
 
	NL_WANTLENGTH = 1, ///< writing length and data
 
	NL_CALCLENGTH = 2, ///< need to calculate the length
 
@@ -103,20 +104,51 @@ struct SaveLoadParams {
 
	StringID error_str;                  ///< the translateable error message to show
 
	char *extra_msg;                     ///< the error message
 
};
 

	
 
static SaveLoadParams _sl;
 

	
 
/** Null all pointers (convert index -> NULL) */
 
static void SlNullPointers()
 
{
 
	const ChunkHandler *ch;
 
	const ChunkHandler * const *chsc;
 

	
 
	_sl.action = SLA_NULL;
 

	
 
	DEBUG(sl, 1, "Nulling pointers");
 

	
 
	for (chsc = _sl.chs; (ch = *chsc++) != NULL;) {
 
		while (true) {
 
			if (ch->ptrs_proc != NULL) {
 
				DEBUG(sl, 2, "Nulling pointers for %c%c%c%c", ch->id >> 24, ch->id >> 16, ch->id >> 8, ch->id);
 
				ch->ptrs_proc();
 
			}
 
			if (ch->flags & CH_LAST) break;
 
			ch++;
 
		}
 
	}
 

	
 
	DEBUG(sl, 1, "All pointers nulled");
 

	
 
	assert(_sl.action == SLA_NULL);
 
}
 

	
 
/** Error handler, calls longjmp to simulate an exception.
 
 * @todo this was used to have a central place to handle errors, but it is
 
 * pretty ugly, and seriously interferes with any multithreaded approaches */
 
static void NORETURN SlError(StringID string, const char *extra_msg = NULL)
 
{
 
	_sl.error_str = string;
 
	free(_sl.extra_msg);
 
	_sl.extra_msg = (extra_msg == NULL) ? NULL : strdup(extra_msg);
 
	/* We have to NULL all pointers here; we might be in a state where
 
	 * the pointers are actually filled with indices, which means that
 
	 * when we access them during cleaning the pool dereferences of
 
	 * those indices will be made with segmentation faults as result. */
 
	SlNullPointers();
 
	throw std::exception();
 
}
 

	
 
typedef void (*AsyncSaveFinishProc)();
 
static AsyncSaveFinishProc _async_save_finish = NULL;
 
static ThreadObject *_save_thread;
 
@@ -567,12 +599,13 @@ static void SlSaveLoadConv(void *ptr, Va
 

	
 
			/* Write The value to the struct. These ARE endian safe. */
 
			WriteValue(ptr, conv, x);
 
			break;
 
		}
 
		case SLA_PTRS: break;
 
		case SLA_NULL: break;
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/** Calculate the net length of a string. This is in almost all cases
 
 * just strlen(), but if the string is not properly terminated, we'll
 
@@ -675,12 +708,13 @@ static void SlString(void *ptr, size_t l
 

	
 
			((char *)ptr)[len] = '\0'; // properly terminate the string
 
			str_validate((char *)ptr, (char *)ptr + len);
 
			break;
 
		}
 
		case SLA_PTRS: break;
 
		case SLA_NULL: break;
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 
/**
 
 * Return the size in bytes of a certain type of atomic array
 
@@ -697,13 +731,13 @@ static inline size_t SlCalcArrayLen(size
 
 * @param array The array being manipulated
 
 * @param length The length of the array in elements
 
 * @param conv VarType type of the atomic array (int, byte, uint64, etc.)
 
 */
 
void SlArray(void *array, size_t length, VarType conv)
 
{
 
	if (_sl.action == SLA_PTRS) return;
 
	if (_sl.action == SLA_PTRS || _sl.action == SLA_NULL) return;
 

	
 
	/* Automatically calculate the length? */
 
	if (_sl.need_length != NL_NONE) {
 
		SlSetLength(SlCalcArrayLen(length, conv));
 
		/* Determine length only? */
 
		if (_sl.need_length == NL_CALCLENGTH) return;
 
@@ -808,12 +842,15 @@ static void SlList(void *list, SLRefType
 
			for (iter = temp.begin(); iter != temp.end(); ++iter) {
 
				void *ptr = IntToReference((size_t)*iter, conv);
 
				l->push_back(ptr);
 
			}
 
			break;
 
		}
 
		case SLA_NULL:
 
			l->clear();
 
			break;
 
		default: NOT_REACHED();
 
	}
 
}
 

	
 

	
 
/** Are we going to save this object or not? */
 
@@ -909,12 +946,15 @@ bool SlObjectMember(void *ptr, const Sav
 
						case SLA_LOAD:
 
							*(size_t *)ptr = CheckSavegameVersion(69) ? SlReadUint16() : SlReadUint32();
 
							break;
 
						case SLA_PTRS:
 
							*(void **)ptr = IntToReference(*(size_t *)ptr, (SLRefType)conv);
 
							break;
 
						case SLA_NULL:
 
							*(void **)ptr = NULL;
 
							break;
 
						default: NOT_REACHED();
 
					}
 
					break;
 
				case SL_ARR: SlArray(ptr, sld->length, conv); break;
 
				case SL_STR: SlString(ptr, sld->length, conv); break;
 
				case SL_LST: SlList(ptr, (SLRefType)conv); break;
 
@@ -929,12 +969,13 @@ bool SlObjectMember(void *ptr, const Sav
 
		 * file_value: the value of the variable in the savegame is abused by sld->version_to */
 
		case SL_WRITEBYTE:
 
			switch (_sl.action) {
 
				case SLA_SAVE: SlWriteByte(sld->version_to); break;
 
				case SLA_LOAD: *(byte *)ptr = sld->version_from; break;
 
				case SLA_PTRS: break;
 
				case SLA_NULL: break;
 
				default: NOT_REACHED();
 
			}
 
			break;
 

	
 
		/* SL_VEH_INCLUDE loads common code for vehicles */
 
		case SL_VEH_INCLUDE:
 
@@ -1143,22 +1184,19 @@ static void SlLoadChunks()
 
		ch = SlFindChunkHandler(id);
 
		if (ch == NULL) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Unknown chunk type");
 
		SlLoadChunk(ch);
 
	}
 
}
 

	
 
static const char *_sl_ptrs_error; ///< error message if there was an error during fixing pointers, NULL otherwise
 

	
 
/** Fix all pointers (convert index -> pointer) */
 
static void SlFixPointers()
 
{
 
	const ChunkHandler *ch;
 
	const ChunkHandler * const *chsc;
 

	
 
	_sl.action = SLA_PTRS;
 
	_sl_ptrs_error = NULL;
 

	
 
	DEBUG(sl, 1, "Fixing pointers");
 

	
 
	for (chsc = _sl.chs; (ch = *chsc++) != NULL;) {
 
		while (true) {
 
			if (ch->ptrs_proc != NULL) {
 
@@ -1168,15 +1206,12 @@ static void SlFixPointers()
 
			if (ch->flags & CH_LAST)
 
				break;
 
			ch++;
 
		}
 
	}
 

	
 
	/* We need to fix all possible pointers even if there were invalid ones. This way pool cleaning will work fine. */
 
	if (_sl_ptrs_error != NULL) SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, _sl_ptrs_error);
 

	
 
	DEBUG(sl, 1, "All pointers fixed");
 

	
 
	assert(_sl.action == SLA_PTRS);
 
}
 

	
 
/*******************************************
 
@@ -1534,61 +1569,47 @@ static void *IntToReference(size_t index
 
	 * invalid vehicle was 0xFFFF, now we use 0x0000 for everything invalid. */
 
	if (rt != REF_VEHICLE_OLD) index--;
 

	
 
	switch (rt) {
 
		case REF_ORDERLIST:
 
			if (OrderList::IsValidID(index)) return OrderList::Get(index);
 
			_sl_ptrs_error = "Referencing invalid OrderList";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid OrderList");
 

	
 
		case REF_ORDER:
 
			if (Order::IsValidID(index)) return Order::Get(index);
 
			/* in old versions, invalid order was used to mark end of order list */
 
			if (CheckSavegameVersionOldStyle(5, 2)) return NULL;
 
			_sl_ptrs_error = "Referencing invalid Order";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid Order");
 

	
 
		case REF_VEHICLE_OLD:
 
		case REF_VEHICLE:
 
			if (Vehicle::IsValidID(index)) return Vehicle::Get(index);
 
			_sl_ptrs_error = "Referencing invalid Vehicle";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid Vehicle");
 

	
 
		case REF_STATION:
 
			if (Station::IsValidID(index)) return Station::Get(index);
 
			_sl_ptrs_error = "Referencing invalid Station";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid Station");
 

	
 
		case REF_TOWN:
 
			if (Town::IsValidID(index)) return Town::Get(index);
 
			_sl_ptrs_error = "Referencing invalid Town";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid Town");
 

	
 
		case REF_ROADSTOPS:
 
			if (RoadStop::IsValidID(index)) return RoadStop::Get(index);
 
			_sl_ptrs_error = "Referencing invalid RoadStop";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid RoadStop");
 

	
 
		case REF_ENGINE_RENEWS:
 
			if (EngineRenew::IsValidID(index)) return EngineRenew::Get(index);
 
			_sl_ptrs_error = "Referencing invalid EngineRenew";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid EngineRenew");
 

	
 
		case REF_CARGO_PACKET:
 
			if (CargoPacket::IsValidID(index)) return CargoPacket::Get(index);
 
			_sl_ptrs_error = "Referencing invalid CargoPacket";
 
			break;
 
			SlError(STR_GAME_SAVELOAD_ERROR_BROKEN_SAVEGAME, "Referencing invalid CargoPacket");
 

	
 
		default: NOT_REACHED();
 
	}
 

	
 
	/* Print a debug message about each invalid reference */
 
	DEBUG(sl, 1, "%s (index = " PRINTF_SIZE ")", _sl_ptrs_error, index);
 

	
 
	/* Return NULL for broken savegames */
 
	return NULL;
 
}
 

	
 
/** The format for a reader/writer type of a savegame */
 
struct SaveLoadFormat {
 
	const char *name;           ///< name of the compressor/decompressor (debug-only)
 
	uint32 tag;                 ///< the 4-letter tag by which it is identified in the savegame
0 comments (0 inline, 0 general)