mirror of https://github.com/OpenTTD/OpenTTD
Fix #7513: recursive array/class/table release caused stack overflow
parent
47a99bb676
commit
44d1b964bf
|
@ -17,9 +17,9 @@ public:
|
||||||
return newarray;
|
return newarray;
|
||||||
}
|
}
|
||||||
#ifndef NO_GARBAGE_COLLECTOR
|
#ifndef NO_GARBAGE_COLLECTOR
|
||||||
void EnqueueMarkObjectForChildren(SQGCMarkerQueue &queue);
|
void EnqueueMarkObjectForChildren(SQGCMarkerQueue &queue) override;
|
||||||
#endif
|
#endif
|
||||||
void Finalize(){
|
void Finalize() override {
|
||||||
_values.resize(0);
|
_values.resize(0);
|
||||||
}
|
}
|
||||||
bool Get(const SQInteger nidx,SQObjectPtr &val)
|
bool Get(const SQInteger nidx,SQObjectPtr &val)
|
||||||
|
@ -78,9 +78,13 @@ public:
|
||||||
ShrinkIfNeeded();
|
ShrinkIfNeeded();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
void Release()
|
void Release() override
|
||||||
{
|
{
|
||||||
sq_delete(this,SQArray);
|
this->_sharedstate->DelayFinalFree(this);
|
||||||
|
}
|
||||||
|
void FinalFree() override
|
||||||
|
{
|
||||||
|
sq_delete(this, SQArray);
|
||||||
}
|
}
|
||||||
SQObjectPtrVec _values;
|
SQObjectPtrVec _values;
|
||||||
};
|
};
|
||||||
|
|
|
@ -126,31 +126,33 @@ public:
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
void Release() {
|
void Release() override {
|
||||||
_uiRef++;
|
_uiRef++;
|
||||||
try {
|
try {
|
||||||
if (_hook) { _hook(_userpointer,0);}
|
if (_hook) { _hook(_userpointer,0);}
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
_uiRef--;
|
_uiRef--;
|
||||||
if (_uiRef == 0) {
|
if (_uiRef == 0) {
|
||||||
SQInteger size = _memsize;
|
this->_sharedstate->DelayFinalFree(this);
|
||||||
this->~SQInstance();
|
|
||||||
SQ_FREE(this, size);
|
|
||||||
}
|
}
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
_uiRef--;
|
_uiRef--;
|
||||||
if(_uiRef > 0) return;
|
if(_uiRef > 0) return;
|
||||||
|
this->_sharedstate->DelayFinalFree(this);
|
||||||
|
}
|
||||||
|
void FinalFree() override
|
||||||
|
{
|
||||||
SQInteger size = _memsize;
|
SQInteger size = _memsize;
|
||||||
this->~SQInstance();
|
this->~SQInstance();
|
||||||
SQ_FREE(this, size);
|
SQ_FREE(this, size);
|
||||||
}
|
}
|
||||||
void Finalize();
|
void Finalize() override;
|
||||||
#ifndef NO_GARBAGE_COLLECTOR
|
#ifndef NO_GARBAGE_COLLECTOR
|
||||||
void EnqueueMarkObjectForChildren(SQGCMarkerQueue &queue);
|
void EnqueueMarkObjectForChildren(SQGCMarkerQueue &queue) override;
|
||||||
#endif
|
#endif
|
||||||
bool InstanceOf(SQClass *trg);
|
bool InstanceOf(SQClass *trg);
|
||||||
bool GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res);
|
bool GetMetaMethod(SQVM *v,SQMetaMethod mm,SQObjectPtr &res) override;
|
||||||
|
|
||||||
SQClass *_class;
|
SQClass *_class;
|
||||||
SQUserPointer _userpointer;
|
SQUserPointer _userpointer;
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
#ifndef _SQOBJECT_H_
|
#ifndef _SQOBJECT_H_
|
||||||
#define _SQOBJECT_H_
|
#define _SQOBJECT_H_
|
||||||
|
|
||||||
#include <forward_list>
|
#include <vector>
|
||||||
#include "squtils.h"
|
#include "squtils.h"
|
||||||
|
|
||||||
#define SQ_CLOSURESTREAM_HEAD (('S'<<24)|('Q'<<16)|('I'<<8)|('R'))
|
#define SQ_CLOSURESTREAM_HEAD (('S'<<24)|('Q'<<16)|('I'<<8)|('R'))
|
||||||
|
@ -350,6 +350,14 @@ struct SQCollectable : public SQRefCounted {
|
||||||
virtual void Finalize()=0;
|
virtual void Finalize()=0;
|
||||||
static void AddToChain(SQCollectable **chain,SQCollectable *c);
|
static void AddToChain(SQCollectable **chain,SQCollectable *c);
|
||||||
static void RemoveFromChain(SQCollectable **chain,SQCollectable *c);
|
static void RemoveFromChain(SQCollectable **chain,SQCollectable *c);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to perform the final memory freeing of this instance. Since the destructor might
|
||||||
|
* release more objects, this can cause a very deep recursion. As such, the calls to this
|
||||||
|
* are to be done via _sharedstate->DelayFinalFree which ensures the calls to this method
|
||||||
|
* are done in an iterative instead of recursive approach.
|
||||||
|
*/
|
||||||
|
virtual void FinalFree() {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -357,19 +365,19 @@ struct SQCollectable : public SQRefCounted {
|
||||||
* The iterative approach provides effectively a depth first search approach.
|
* The iterative approach provides effectively a depth first search approach.
|
||||||
*/
|
*/
|
||||||
class SQGCMarkerQueue {
|
class SQGCMarkerQueue {
|
||||||
std::forward_list<SQCollectable*> queue; ///< The queue of elements to still process.
|
std::vector<SQCollectable*> stack; ///< The elements to still process, with the most recent elements at the back.
|
||||||
public:
|
public:
|
||||||
/** Whether there are any elements left to process. */
|
/** Whether there are any elements left to process. */
|
||||||
bool IsEmpty() { return this->queue.empty(); }
|
bool IsEmpty() { return this->stack.empty(); }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the first element from the queue.
|
* Remove the most recently added element from the queue.
|
||||||
* Removal when the queue is empty results in undefined behaviour.
|
* Removal when the queue is empty results in undefined behaviour.
|
||||||
*/
|
*/
|
||||||
SQCollectable *Pop()
|
SQCollectable *Pop()
|
||||||
{
|
{
|
||||||
SQCollectable *collectable = this->queue.front();
|
SQCollectable *collectable = this->stack.back();
|
||||||
this->queue.pop_front();
|
this->stack.pop_back();
|
||||||
return collectable;
|
return collectable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -382,7 +390,7 @@ public:
|
||||||
{
|
{
|
||||||
if ((collectable->_uiRef & MARK_FLAG) == 0) {
|
if ((collectable->_uiRef & MARK_FLAG) == 0) {
|
||||||
collectable->_uiRef |= MARK_FLAG;
|
collectable->_uiRef |= MARK_FLAG;
|
||||||
this->queue.push_front(collectable);
|
this->stack.push_back(collectable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -99,6 +99,7 @@ SQSharedState::SQSharedState()
|
||||||
_notifyallexceptions = false;
|
_notifyallexceptions = false;
|
||||||
_scratchpad=NULL;
|
_scratchpad=NULL;
|
||||||
_scratchpadsize=0;
|
_scratchpadsize=0;
|
||||||
|
_collectable_free_processing = false;
|
||||||
#ifndef NO_GARBAGE_COLLECTOR
|
#ifndef NO_GARBAGE_COLLECTOR
|
||||||
_gc_chain=NULL;
|
_gc_chain=NULL;
|
||||||
#endif
|
#endif
|
||||||
|
@ -226,6 +227,34 @@ SQInteger SQSharedState::GetMetaMethodIdxByName(const SQObjectPtr &name)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper function that is to be used instead of calling FinalFree directly on the instance,
|
||||||
|
* so the frees can happen iteratively. This as in the FinalFree the references to any other
|
||||||
|
* objects are released, which can cause those object to be freed yielding a potentially
|
||||||
|
* very deep stack in case of for example a link list.
|
||||||
|
*
|
||||||
|
* This is done internally by a vector onto which the to be freed instances are pushed. When
|
||||||
|
* this is called when not already processing, this method will actually call the FinalFree
|
||||||
|
* function which might cause more elements to end up in the queue which this method then
|
||||||
|
* picks up continueing until it has processed all instances in that queue.
|
||||||
|
* @param collectable The collectable to (eventually) free.
|
||||||
|
*/
|
||||||
|
void SQSharedState::DelayFinalFree(SQCollectable *collectable)
|
||||||
|
{
|
||||||
|
this->_collectable_free_queue.push_back(collectable);
|
||||||
|
|
||||||
|
if (!this->_collectable_free_processing) {
|
||||||
|
this->_collectable_free_processing = true;
|
||||||
|
while (!this->_collectable_free_queue.empty()) {
|
||||||
|
SQCollectable *collectable = this->_collectable_free_queue.back();
|
||||||
|
this->_collectable_free_queue.pop_back();
|
||||||
|
collectable->FinalFree();
|
||||||
|
}
|
||||||
|
this->_collectable_free_processing = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#ifndef NO_GARBAGE_COLLECTOR
|
#ifndef NO_GARBAGE_COLLECTOR
|
||||||
|
|
||||||
void SQSharedState::EnqueueMarkObject(SQObjectPtr &o,SQGCMarkerQueue &queue)
|
void SQSharedState::EnqueueMarkObject(SQObjectPtr &o,SQGCMarkerQueue &queue)
|
||||||
|
|
|
@ -61,6 +61,7 @@ struct SQSharedState
|
||||||
public:
|
public:
|
||||||
SQChar* GetScratchPad(SQInteger size);
|
SQChar* GetScratchPad(SQInteger size);
|
||||||
SQInteger GetMetaMethodIdxByName(const SQObjectPtr &name);
|
SQInteger GetMetaMethodIdxByName(const SQObjectPtr &name);
|
||||||
|
void DelayFinalFree(SQCollectable *collectable);
|
||||||
#ifndef NO_GARBAGE_COLLECTOR
|
#ifndef NO_GARBAGE_COLLECTOR
|
||||||
SQInteger CollectGarbage(SQVM *vm);
|
SQInteger CollectGarbage(SQVM *vm);
|
||||||
static void EnqueueMarkObject(SQObjectPtr &o,SQGCMarkerQueue &queue);
|
static void EnqueueMarkObject(SQObjectPtr &o,SQGCMarkerQueue &queue);
|
||||||
|
@ -74,6 +75,10 @@ public:
|
||||||
SQObjectPtr _registry;
|
SQObjectPtr _registry;
|
||||||
SQObjectPtr _consts;
|
SQObjectPtr _consts;
|
||||||
SQObjectPtr _constructoridx;
|
SQObjectPtr _constructoridx;
|
||||||
|
/** Queue to make freeing of collectables iterative. */
|
||||||
|
std::vector<SQCollectable *> _collectable_free_queue;
|
||||||
|
/** Whether someone is already processing the _collectable_free_queue. */
|
||||||
|
bool _collectable_free_processing;
|
||||||
#ifndef NO_GARBAGE_COLLECTOR
|
#ifndef NO_GARBAGE_COLLECTOR
|
||||||
SQCollectable *_gc_chain;
|
SQCollectable *_gc_chain;
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -50,7 +50,7 @@ public:
|
||||||
newtable->_delegate = NULL;
|
newtable->_delegate = NULL;
|
||||||
return newtable;
|
return newtable;
|
||||||
}
|
}
|
||||||
void Finalize();
|
void Finalize() override;
|
||||||
SQTable *Clone();
|
SQTable *Clone();
|
||||||
~SQTable()
|
~SQTable()
|
||||||
{
|
{
|
||||||
|
@ -60,7 +60,7 @@ public:
|
||||||
SQ_FREE(_nodes, _numofnodes * sizeof(_HashNode));
|
SQ_FREE(_nodes, _numofnodes * sizeof(_HashNode));
|
||||||
}
|
}
|
||||||
#ifndef NO_GARBAGE_COLLECTOR
|
#ifndef NO_GARBAGE_COLLECTOR
|
||||||
void EnqueueMarkObjectForChildren(SQGCMarkerQueue &queue);
|
void EnqueueMarkObjectForChildren(SQGCMarkerQueue &queue) override;
|
||||||
#endif
|
#endif
|
||||||
inline _HashNode *_Get(const SQObjectPtr &key,SQHash hash)
|
inline _HashNode *_Get(const SQObjectPtr &key,SQHash hash)
|
||||||
{
|
{
|
||||||
|
@ -81,7 +81,11 @@ public:
|
||||||
|
|
||||||
SQInteger CountUsed(){ return _usednodes;}
|
SQInteger CountUsed(){ return _usednodes;}
|
||||||
void Clear();
|
void Clear();
|
||||||
void Release()
|
void Release() override
|
||||||
|
{
|
||||||
|
this->_sharedstate->DelayFinalFree(this);
|
||||||
|
}
|
||||||
|
void FinalFree() override
|
||||||
{
|
{
|
||||||
sq_delete(this, SQTable);
|
sq_delete(this, SQTable);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue