Skip to content

Commit eea155d

Browse files
committedNov 1, 2017
Fix crash when attempting to render multipolygon with missing exterior ring
This commit fixes a possible crash when the vector layer renderer attempts to render a multipolygon containing a polygon without an exterior ring. The underlying cause of the creation of this invalid geometry is deeper, but this commit hardens the renderer and makes it more robust for handling bad geometries. Fixes #17365
1 parent 6418a83 commit eea155d

File tree

4 files changed

+90
-6
lines changed

4 files changed

+90
-6
lines changed
 

‎src/core/qgsvectorlayerrenderer.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -271,7 +271,7 @@ void QgsVectorLayerRenderer::drawRenderer( QgsFeatureIterator &fit )
271271
break;
272272
}
273273

274-
if ( !fet.hasGeometry() )
274+
if ( !fet.hasGeometry() || fet.geometry().isEmpty() )
275275
continue; // skip features without geometry
276276

277277
mContext.expressionContext().setFeature( fet );

‎src/core/symbology/qgssymbol.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,9 @@ void QgsSymbol::renderFeature( const QgsFeature &feature, QgsRenderContext &cont
914914

915915
context.setGeometry( geomCollection.geometryN( i ) );
916916
const QgsPolygon &polygon = dynamic_cast<const QgsPolygon &>( *geomCollection.geometryN( i ) );
917+
if ( !polygon.exteriorRing() )
918+
break;
919+
917920
_getPolygon( pts, holes, context, polygon, !tileMapRendering && clipFeaturesToExtent() );
918921
static_cast<QgsFillSymbol *>( this )->renderPolygon( pts, ( !holes.isEmpty() ? &holes : nullptr ), &feature, context, layer, selected );
919922

‎tests/src/core/testqgsrenderers.cpp

Lines changed: 86 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@
2222
#include <QDesktopServices>
2323

2424
//qgis includes...
25-
#include <qgsmaplayer.h>
26-
#include <qgsvectorlayer.h>
27-
#include <qgsapplication.h>
28-
#include <qgsproviderregistry.h>
29-
#include <qgsproject.h>
25+
#include "qgsmaplayer.h"
26+
#include "qgsvectorlayer.h"
27+
#include "qgsapplication.h"
28+
#include "qgsproviderregistry.h"
29+
#include "qgsproject.h"
30+
#include "qgsmultipolygon.h"
31+
#include "qgspolygon.h"
32+
#include "qgslinestring.h"
33+
#include "qgsmultilinestring.h"
34+
#include "qgsmultipoint.h"
3035
//qgis test includes
3136
#include "qgsmultirenderchecker.h"
3237

@@ -52,13 +57,15 @@ class TestQgsRenderers : public QObject
5257
void cleanup() {} // will be called after every testfunction.
5358

5459
void singleSymbol();
60+
void emptyGeometry();
5561
// void uniqueValue();
5662
// void graduatedSymbol();
5763
// void continuousSymbol();
5864
private:
5965
bool mTestHasError = false ;
6066
bool setQml( const QString &type ); //uniquevalue / continuous / single /
6167
bool imageCheck( const QString &type ); //as above
68+
bool checkEmptyRender( const QString &name, QgsVectorLayer *layer );
6269
QgsMapSettings *mMapSettings = nullptr;
6370
QgsMapLayer *mpPointsLayer = nullptr;
6471
QgsMapLayer *mpLinesLayer = nullptr;
@@ -147,6 +154,80 @@ void TestQgsRenderers::singleSymbol()
147154
QVERIFY( imageCheck( "single" ) );
148155
}
149156

157+
void TestQgsRenderers::emptyGeometry()
158+
{
159+
// test rendering an empty geometry
160+
// note - this test is of limited use, because the features with empty geometries should not be rendered
161+
// by the feature iterator given that renderer uses a filter rect on the request. It's placed here
162+
// for manual testing by commenting out the part of the renderer which places the filterrect on the
163+
// layer's feature request. The purpose of this test is to ensure that we do not crash for empty geometries,
164+
// as it's possible that malformed providers OR bugs in underlying libraries will still return empty geometries
165+
// even when a filter rect request was made, and we shouldn't crash for these.
166+
QgsVectorLayer *vl = new QgsVectorLayer( QStringLiteral( "MultiPolygon?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
167+
QVERIFY( vl->isValid() );
168+
QgsProject::instance()->addMapLayer( vl );
169+
170+
QgsFeature f;
171+
std::unique_ptr< QgsMultiPolygon > mp = qgis::make_unique< QgsMultiPolygon >();
172+
mp->addGeometry( new QgsPolygon() );
173+
f.setGeometry( QgsGeometry( std::move( mp ) ) );
174+
QVERIFY( vl->dataProvider()->addFeature( f ) );
175+
QVERIFY( checkEmptyRender( "Multipolygon", vl ) );
176+
177+
// polygon
178+
vl = new QgsVectorLayer( QStringLiteral( "Polygon?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
179+
QVERIFY( vl->isValid() );
180+
QgsProject::instance()->addMapLayer( vl );
181+
f.setGeometry( QgsGeometry( new QgsPolygon() ) );
182+
QVERIFY( vl->dataProvider()->addFeature( f ) );
183+
QVERIFY( checkEmptyRender( "Polygon", vl ) );
184+
185+
// linestring
186+
vl = new QgsVectorLayer( QStringLiteral( "LineString?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
187+
QVERIFY( vl->isValid() );
188+
QgsProject::instance()->addMapLayer( vl );
189+
f.setGeometry( QgsGeometry( new QgsLineString() ) );
190+
QVERIFY( vl->dataProvider()->addFeature( f ) );
191+
QVERIFY( checkEmptyRender( "LineString", vl ) );
192+
193+
// multilinestring
194+
vl = new QgsVectorLayer( QStringLiteral( "MultiLineString?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
195+
QVERIFY( vl->isValid() );
196+
QgsProject::instance()->addMapLayer( vl );
197+
std::unique_ptr< QgsMultiLineString > mls = qgis::make_unique< QgsMultiLineString >();
198+
mls->addGeometry( new QgsLineString() );
199+
f.setGeometry( QgsGeometry( std::move( mls ) ) );
200+
QVERIFY( vl->dataProvider()->addFeature( f ) );
201+
QVERIFY( checkEmptyRender( "MultiLineString", vl ) );
202+
203+
// multipoint
204+
vl = new QgsVectorLayer( QStringLiteral( "MultiPoint?crs=epsg:4326&field=pk:int&field=col1:string" ), QStringLiteral( "vl" ), QStringLiteral( "memory" ) );
205+
QVERIFY( vl->isValid() );
206+
QgsProject::instance()->addMapLayer( vl );
207+
std::unique_ptr< QgsMultiPoint > mlp = qgis::make_unique< QgsMultiPoint >();
208+
f.setGeometry( QgsGeometry( std::move( mlp ) ) );
209+
QVERIFY( vl->dataProvider()->addFeature( f ) );
210+
QVERIFY( checkEmptyRender( "MultiPoint", vl ) );
211+
}
212+
213+
bool TestQgsRenderers::checkEmptyRender( const QString &testName, QgsVectorLayer *layer )
214+
{
215+
QgsMapSettings ms;
216+
QgsRectangle extent( -180, -90, 180, 90 );
217+
ms.setExtent( extent );
218+
ms.setFlag( QgsMapSettings::ForceVectorOutput );
219+
ms.setOutputDpi( 96 );
220+
ms.setLayers( QList< QgsMapLayer * >() << layer );
221+
QgsMultiRenderChecker myChecker;
222+
myChecker.setControlName( "expected_emptygeometry" );
223+
myChecker.setMapSettings( ms );
224+
myChecker.setControlPathPrefix( "map_renderer" );
225+
myChecker.setColorTolerance( 15 );
226+
bool myResultFlag = myChecker.runTest( testName, 200 );
227+
mReport += myChecker.report();
228+
return myResultFlag;
229+
}
230+
150231
//
151232
// Private helper functions not called directly by CTest
152233
//

0 commit comments

Comments
 (0)
Please sign in to comment.