Skip to content

Commit bd9b373

Browse files
committedMar 26, 2019
Fix map layer memory leak with bad layer handling
Long story: if the store contains a layer with the same ID it silently fails to add it to the store. This is the cause of leak in case client code calls store->addLayers() within the (iterative) process of fixing bad layers by patching the XML and re-reading it. The proposed solution is to check in QgsProject::addLayer() - which creates the layer object - if the newly created layer was already in the store and delete it at the end of the layer reading process if it was. Deleting the layer from within the store->addLayers is extremely dangerous, because the caller could keep a reference to the layer and use it.
1 parent e75308e commit bd9b373

File tree

1 file changed

+22
-8
lines changed

1 file changed

+22
-8
lines changed
 

‎src/core/qgsproject.cpp

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -944,30 +944,30 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
944944
{
945945
QString type = layerElem.attribute( QStringLiteral( "type" ) );
946946
QgsDebugMsgLevel( "Layer type is " + type, 4 );
947-
QgsMapLayer *mapLayer = nullptr;
947+
std::unique_ptr<QgsMapLayer> mapLayer;
948948

949949
if ( type == QLatin1String( "vector" ) )
950950
{
951-
mapLayer = new QgsVectorLayer;
951+
mapLayer = qgis::make_unique<QgsVectorLayer>();
952952

953953
// apply specific settings to vector layer
954-
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer ) )
954+
if ( QgsVectorLayer *vl = qobject_cast<QgsVectorLayer *>( mapLayer.get() ) )
955955
{
956956
vl->setReadExtentFromXml( mTrustLayerMetadata );
957957
}
958958
}
959959
else if ( type == QLatin1String( "raster" ) )
960960
{
961-
mapLayer = new QgsRasterLayer;
961+
mapLayer = qgis::make_unique<QgsRasterLayer>();
962962
}
963963
else if ( type == QLatin1String( "mesh" ) )
964964
{
965-
mapLayer = new QgsMeshLayer;
965+
mapLayer = qgis::make_unique<QgsMeshLayer>();
966966
}
967967
else if ( type == QLatin1String( "plugin" ) )
968968
{
969969
QString typeName = layerElem.attribute( QStringLiteral( "name" ) );
970-
mapLayer = QgsApplication::pluginLayerRegistry()->createLayer( typeName );
970+
mapLayer.reset( QgsApplication::pluginLayerRegistry()->createLayer( typeName ) );
971971
}
972972

973973
if ( !mapLayer )
@@ -978,13 +978,19 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
978978

979979
Q_CHECK_PTR( mapLayer ); // NOLINT
980980

981+
// This is tricky: to avoid a leak we need to check if the layer was already in the store
982+
// because if it was, the newly created layer will not be added to the store and it would leak.
983+
const QString layerId { layerElem.namedItem( QStringLiteral( "id" ) ).toElement().text() };
984+
Q_ASSERT( ! layerId.isEmpty() );
985+
const bool layerWasStored { layerStore()->mapLayer( layerId ) };
986+
981987
// have the layer restore state that is stored in Dom node
982988
bool layerIsValid = mapLayer->readLayerXml( layerElem, context ) && mapLayer->isValid();
983989
QList<QgsMapLayer *> newLayers;
984-
newLayers << mapLayer;
990+
newLayers << mapLayer.get();
985991
if ( layerIsValid )
986992
{
987-
emit readMapLayer( mapLayer, layerElem );
993+
emit readMapLayer( mapLayer.get(), layerElem );
988994
addMapLayers( newLayers );
989995
}
990996
else
@@ -995,6 +1001,14 @@ bool QgsProject::addLayer( const QDomElement &layerElem, QList<QDomNode> &broken
9951001
QgsDebugMsg( "Unable to load " + type + " layer" );
9961002
brokenNodes.push_back( layerElem );
9971003
}
1004+
1005+
// It should be safe to delete the layer now if layer was stored, because all the store
1006+
// had to to was to reset the data source in case the validity changed.
1007+
if ( ! layerWasStored )
1008+
{
1009+
mapLayer.release();
1010+
}
1011+
9981012
return layerIsValid;
9991013
}
10001014

0 commit comments

Comments
 (0)
Please sign in to comment.