
In this article, we will discuss the verification of another well-known open source project - the vector graphics editor Inkscape 0.92. The project has been developing for more than 12 years and provides many opportunities to work with various formats of vector illustrations. During this time, its codebase has grown to 600 thousand lines, and the time has come to check it with the help of the PVS-Studio static analyzer.
Introduction
Inkscape is a free, cross-platform vector graphics editor. It is widely used by amateurs and professionals around the world to create illustrations, icons, logos, diagrams, maps, as well as web graphics. Inkscape has become one of the most popular editors in its field. The project was created in 2003 as a fork of the Sodipodi project, and still continues to evolve. More information about Inkscape can be found on the
official website .
The latest version of Inkscape - 0.92, the code of which is available in the
repository on GitHub and the static analyzer PVS-Studio 6.07, which can be downloaded at the
link, was used for verification. True, at the time of writing the article, only PVS-Studio for Windows is available for download. But the situation will change soon. And you can already sign up for volunteers in advance to test the PVS-Studio beta version for Linux. Details can be found in the article: "
PVS-Studio confesses his love for Linux ".
')

But back to the mistakes. I want to note that in the article the most interesting messages of the analyzer were selected and described. For a more thorough check, the project authors will be able to get a temporary key for PVS-Studio and a report from us. Since there is no public PVS-Studio yet, they can use the PVS-Studio Standalone tool running under Windows to view the report. Yes, it is not convenient. But I ask everyone to suffer, it remains not so long until the happy release of PVS-Studio for Linux.
Test results
Check pointer to null after new
PVS-Studio warning : V668, it’s not a problem. The exception will be generated in the case of memory allocation error. gzipstream.cpp 180
bool GzipInputStream::load() { .... outputBuf = new unsigned char [OUT_SIZE]; if ( !outputBuf ) {
According to the modern C ++ standard, if it is impossible to allocate memory, the
new operator throws an exception
std :: bad_alloc () , and does not return
nullptr . If the system fails to allocate memory, an exception will be thrown and the function will stop executing, therefore, the program will never enter the block after the condition.
In this case, it may lead to a memory leak. The most obvious solution to the problem is to use the
try {....} catch (const std :: bad_alloc &) {....} block , but it is much better to use smart pointers instead of explicitly freeing memory.
Similar pointer tests:
- V668 has been allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. gzipstream.cpp 397
- V668 allocated r new allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated The exception will be generated in the case of memory allocation error. gzipstream.cpp 175
- V668 has been allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. sp-lpe-item.cpp 719
Comparing this with zero
PVS-Studio warning : V704 '! This' expression can be compiled, because it can never be NULL. sp-lpe-item.cpp 213
bool SPLPEItem::performPathEffect(....) { if (!this) { return false; } .... }
According to the modern standard C ++, the
this pointer can never be null. Often, using
this with a null comparison can lead to unexpected errors. Read more about this in the diagnostic description of the
V704 .
Another check for
this being equal to
nullptr :
- V704 'this' expression should be avoided - this expression is always true on newer compilers, because it can be never NULL. sp-paint-server.cpp 42
Dangerous parameter override
PVS-Studio warning : V581 The conditional expressions of the 'if' are agreed alongside each other are identical. Check lines: 1046, 1051. sp-mesh-array.cpp 1051
void SPMeshNodeArray::create( ...., Geom::OptRect bbox )
As conceived by the author, in the case where the
bbox parameter is
nullptr , a new object of type
Geom :: OptRect should be created for it, and if the object could not be created, then the method will exit with an error message.
However, the code does not work as expected by the author. When the
bbox parameter is
nullptr , inside the first
if block, a completely new
bbox object is
created , which is immediately destroyed when it leaves the block. The result is that the second condition is always executed when the first condition is fulfilled, so every time the
bbox parameter is
nullptr , the method exits with an error message.
This code should be written like this:
void SPMeshNodeArray::create( ...., Geom::OptRect bbox ) { .... if( !bbox ) { std::cout << "SPMeshNodeArray::create(): bbox empty" << std::endl; bbox = item->geometricBounds(); if( !bbox ) { std::cout << "ERROR: No bounding box!" << std::endl; return; } } .... }
Incorrectly commented out line
PVS-Studio Warning: V628 It’s possible that you’ve been commented out, thus altering the program’s operation logics. FontFactory.cpp 705
font_instance *font_factory::Face(....) { .... if( features[0] != 0 )
Here the author forgot to comment out the line with the condition used for debugging. In this case, lucky and it did not lead to negative consequences. It turned out that the condition in
if simply duplicated the condition of the for loop execution at the first iteration, but this is undoubtedly a mistake and can lead to problems in the future.
"One-time cycle"
PVS-Studio warning : V612 An unconditional 'break' within a loop. text_reassemble.c 417
int TR_kern_gap(....) { .... while(ptsp && tsp){ .... if(!text32){ .... if(!text32)break; } .... if(!ptxt32){ .... if(!ptxt32)break; } .... break;
In any case, this cycle will end after the first pass, since there is no condition before the
break statement. Just say what the author meant is difficult. If there is no error, it is still better to rewrite the code and replace the
while with
if .
Very strange method
PVS-Studio warning : V571 Recurring check. The 'back == false' condition has been verified in line 388. Path.cpp 389
void Path::SetBackData (bool nVal) { if (back == false) { if (nVal == true && back == false) { back = true; ResetPoints(); } else if (nVal == false && back == true) { back = false; ResetPoints(); } } else { if (nVal == true && back == false) { back = true; ResetPoints(); } else if (nVal == false && back == true) { back = false; ResetPoints(); } } }
It is difficult to say why this method was written in such a strange way. The
if and
else blocks are the same; many unnecessary checks are performed. Even if there is no logical error here, then this method should definitely be rewritten as follows:
void Path::SetBackData (bool nVal) { back = nVal; ResetPoints(); }
Lost comma
PVS-Studio warning : V737 It is possible that ',' the comma is missing. drawing-text.cpp 272
void DrawingText::decorateStyle(....) { .... int dashes[16]={ 8, 7, 6, 5, 4, 3, 2, 1, -8, -7, -6, -5
A comma was omitted, which causes the
dashes array
to be initialized at all in ways other than those expected by the author.
Expected:
{ 8, 7, 6, 5, 4, 3, 2, 1, -8, -7, -6, -5, -4, -3, -2, -1 }
In fact, the array will be filled like this:
{ 8, 7, 6, 5, 4, 3, 2, 1, -8, -7, -6, -9, -3, -2, -1, 0 }
In the place of the 12th element of the array will be written the number
-5 - 4 == -9 . And the last element (for which there were not enough elements in the array initialization list) will be, according to the C ++ standard, initialized to zero.
Invalid length in strncmp
PVS-Studio warning : V666 Consider the third argument of the function 'strncmp'. This is not the case. blend.cpp 85
static Inkscape::Filters::FilterBlendMode sp_feBlend_readmode(....) { .... switch (value[0]) { case 'n': if (strncmp(value, "normal", 6) == 0) return Inkscape::Filters::BLEND_NORMAL; break; case 'm': .... case 's': if (strncmp(value, "screen", 6) == 0) return Inkscape::Filters::BLEND_SCREEN; if (strncmp(value, "saturation", 6) == 0)
An incorrect length of
“saturation” and
“hard-light” strings is passed to the
strncmp function
, so not all characters will be compared, but only the first 6 and 7 characters, respectively. Most likely, the so-called
Copy-Paste programming . This error will lead to false positives when adding new elements to the
switch-case . It would be worth fixing the code:
if (strncmp(value, "saturation", 10) == 0) .... if (strncmp(value, "hard-light", 10) == 0)
Potential division by zero
PVS-Studio warning : V609 Divide by zero. Denominator range [0..999]. lpe-fillet-chamfer.cpp 607
Geom::PathVector LPEFilletChamfer::doEffect_path(....) { .... if(....){ .... } else if (type >= 3000 && type < 4000) { unsigned int chamferSubs = type-3000; .... double chamfer_stepsTime = 1.0/chamferSubs; .... } ... }
In the case when the
type variable is equal to
3000 , the value of the variable
chamferSubs will be 0. Accordingly, the value of
chamfer_stepsTime will be
1.0 / 0 == inf , and this is clearly not what the author expects. To avoid this situation, it is worth changing the condition in the
if block:
... else if (type > 3000 && type < 4000) ...
Or you can separately handle the situation when
chamferSubs == 0 .
Similar situation:
- V609 Divide by zero. Denominator range [0..999]. lpe-fillet-chamfer.cpp 623
Missed else?
PVS-Studio warning : V646 Consider inspecting the application's logic. It's possible that 'else' keyword is missing. sp-item.cpp 204
void SPItem::resetEvaluated() { if ( StatusCalculated == _evaluated_status ) { .... } if ( StatusSet == _evaluated_status ) {
Judging by the formatting of the code (the
if statement is located on the same line as the closing bracket from the previous
if ) and the logic of the work, the
else keyword was omitted here:
.... if ( StatusCalculated == _evaluated_status ) { .... } else if ( StatusSet == _evaluated_status ) { .... } } ....
Work with a null pointer
PVS-Studio warning : V595 The 'priv' pointer was used before it was verified against nullptr. Check lines: 154, 160. document.cpp 154
SPDocument::~SPDocument() { priv->destroySignal.emit();
In the lower
if block, the
priv is checked for
NULL , since The author admits the equality of this pointer to zero, however, the above pointer is already used without any checks. To correct this error, you should check the pointer value before using it.
Similar warnings:
- V595 Check lines: 624, 641. sp-offset.cpp 624
- V595 The '_effects_list' pointer was used before it was verified against nullptr. Check lines: 103, 113. effect.cpp 103
- V595 The 'num' pointer was used before it was verified against nullptr. Check lines: 1312, 1315. cr-tknzr.c 1312
- V595 The 'selector' pointer was used before it was verified against nullptr. Check lines: 3463, 3481. cr-parser.c 3463
- V595 The 'a_this' pointer was used before it was verified against nullptr. Check lines: 1552, 1562. cr-sel-eng.c 1552
- V595 The 'FillData' pointer was used before it was verified against nullptr. Check lines: 5898, 5901. upmf.c 5898
- V595 The 'event_context' pointer was used before it was verified against nullptr. Check lines: 1014, 1023. tool-base.cpp 1014
- V595 The 'event_context' pointer was used before it was verified against nullptr. Check lines: 959, 970. tool-base.cpp 959
- V595 The 'this-> repr' pointer was used before it was verified against nullptr. Check lines: 662, 665. eraser-tool.cpp 662
- V595 The 'this-> repr' pointer was used before it was verified against nullptr. Check lines: 662, 665. eraser-tool.cpp 662
- V595 The 'modified_connection' pointer was used before it was verified against nullptr. Check lines: 1114, 1122. gradient-vector.cpp 1114
- V595 The 'c' pointer was used before it was verified against nullptr. Check lines: 762, 770. freehand-base.cpp 762
- V595 The 'release_connection' pointer was used before it was verified against nullptr. Check lines: 505, 511. gradient-toolbar.cpp 505
- V595 The 'modified_connection' pointer was used before it was verified against nullptr. Check lines: 506, 514. gradient-toolbar.cpp 506
Missed semicolon
PVS-Studio warning :
V504 It is highly probable that the semicolon ';' is missing after 'return' keyword. svg-fonts-dialog.cpp 167
void GlyphComboBox::update(SPFont* spfont) { if (!spfont) return

After the
return, a semicolon (";") is omitted, which is the cause of the problem described in the author's comments. Because, if you comment out the line:
this->append("");
then we get the construction of the form:
if (!spfont) return this->remove_all();
Accordingly, the combobox will only be cleared when
spfont == NULL .
Unused parameter
PVS-Studio warning : V763 Parameter 'new_value' is always rewritten in function body before being used. sp-xmlview-tree.cpp 259
void element_attr_changed(.... const gchar * new_value, ....) { NodeData *data = static_cast<NodeData *>(ptr); gchar *label; if (data->tree->blocked) return; if (0 != strcmp (key, "id") && 0 != strcmp (key, "inkscape:label")) return; new_value = repr->attribute("id");
In this function, the value of the
new_value parameter always changes before it is used. It may be worth removing
new_value from the parameter list, since At the moment, the presence of this parameter is absolutely not justified.
Similar situation:
- V763 Parameter 'widget' is always rewritten in function body before being used. ruler.cpp 923
Pointer to a non-existent array
PVS-Studio warning : V507 Pointer to local array. Such a pointer will become invalid. inkscape.cpp 582
void Application::crash_handler (int ) { .... if (doc->isModifiedSinceSave()) { const gchar *docname; .... if (docname) { .... if (*d=='.' && d>docname && dots==2) { char n[64]; size_t len = MIN (d - docname, 63); memcpy (n, docname, len); n[len] = '\0'; docname = n; } } if (!docname || !*docname) docname = "emergency"; .... }
The array
n has a lifetime less than the lifetime of the
docname pointer to it. This leads to working with an invalid
docname pointer. One solution to the problem is to define the array
n next to the
docname pointer:
.... if (doc->isModifiedSinceSave()) { const gchar *docname; char n[64]; ....
Similar pointers:
- V507 Pointer to local array "in_buffer" is stored outside the scope of this array. Such a pointer will become invalid. inkjar.cpp 371
- V507 Pointer to local array 'out_buffer' is stored outside the scope of this array. Such a pointer will become invalid. inkjar.cpp 375
Invalid object name in condition
PVS-Studio warning : V517 The use of if (A) {...} else if (A) {...} 'pattern was detected. There is a possibility of logical error presence. Check lines: 640, 643. font-variants.cpp 640
void FontVariants::fill_css( SPCSSAttr *css ) { .... if( _caps_normal.get_active() ) { css_string = "normal"; caps_new = SP_CSS_FONT_VARIANT_CAPS_NORMAL; } else if( _caps_small.get_active() ) { .... } else if( _caps_all_small.get_active() ) { .... } else if( _caps_all_petite.get_active() ) {
In the condition preceding
_caps_all_petite.get_active () , the object name must be
_caps_petite , not
_caps_all_petite . The error most likely occurred as a result of Copy-Paste.
Careless use of numeric constants
PVS-Studio warning : V624 The constant 0.707107 is being utilized. The resulting value could be inaccurate. Consider using the M_SQRT1_2 constant from <math.h>. PathOutline.cpp 1198
void Path::OutlineJoin (....) { .... if (fabs(c2) > 0.707107) { .... } .... }
Such a record is not entirely correct and may lead to a decrease in the accuracy of the calculations. It is better to use the mathematical constant
M_SQRT1_2 (the inverse of the square root of 2) declared in the file
<math.h> . I think in practice everything works well here, but I wanted to draw attention to such an example of ugly code.
Similar warnings:
- V624 The constant 1.414213562 is being utilized. The resulting value could be inaccurate. Consider using the M_SQRT2 constant from <math.h>. verbs.cpp 1848
- V624 The constant 3.14159 is being utilized. The resulting value could be inaccurate. Consider using the M_PI constant from <math.h>. odf.cpp 1568
- V624 The constant 1.414213562 is being utilized. The resulting value could be inaccurate. Consider using the M_SQRT2 constant from <math.h>. inkscape-preferences.cpp 1334
Identical expressions
PVS-Studio Warning: Ar.maxExtent () <tol 'of the operator. path-intersection.cpp 313
void mono_intersect(....) { if(depth > 12 || (Ar.maxExtent() < tol && Ar.maxExtent() < tol)) { .... } .... }
The verification of the condition
Ar.maxExtent () <tol is performed twice. Most likely it turned out as a result of making some corrections to the code. You should correct the expression or simply remove the duplicate check.
Similar check:
- V501 There are identical sub-expressions 'Ar.maxExtent () <0.1' to the left. path-intersection.cpp 364
Same actions in if and else blocks
PVS-Studio warning: The 'then' statement is equivalent to the 'else' statement. ShapeRaster.cpp 1825
void Shape::AvanceEdge(....) { .... if ( swrData[no].sens ) { if ( swrData[no].curX < swrData[no].lastX ) { line->AddBord(swrData[no].curX, swrData[no].lastX, false); } else if ( swrData[no].curX > swrData[no].lastX ) { line->AddBord(swrData[no].lastX, swrData[no].curX, false); } } else { if ( swrData[no].curX < swrData[no].lastX ) { line->AddBord(swrData[no].curX, swrData[no].lastX, false); } else if ( swrData[no].curX > swrData[no].lastX ) { line->AddBord(swrData[no].lastX, swrData[no].curX, false); } } }
The code in the
if and
else blocks is the same, so you should look at this place and either fix the logic of the work or delete the duplicate branch.
Similar places:
- V523 The 'then' statement is equivalent to the 'else' statement. ShapeRaster.cpp 1795
- V523 The 'then' statement is equivalent to the 'else' statement. PathCutting.cpp 1323
- V523 The 'then' statement is equivalent to the 'else' statement. ShapeSweep.cpp 2340
Conclusion
During the test, a lot of mistakes were made due to inattention. The PVS-Studio static analyzer can effectively detect such errors and thus save time and nerves of the programmer. The main thing is to perform code analysis regularly in order to immediately identify typos and other defects. One-time checks such as this one, although they advertise PVS-Studio well, are ineffective. Consider messages from the static analyzer as extended warnings from the compiler. And with the compiler messages you need to work all the time, not one-time before release. I hope this analogy is close and understandable to the soul of any programmer who is worried about the quality of the code.
I suggest
downloading and trying PVS-Studio on my own project.
PS

Our company decided to experiment a bit with Instagram. We do not know whether something will come out of this, but I invite everyone to follow "
pvsstudio ".
If you want to share this article with an English-speaking audience, then please use the link to the translation: Egor Bredikhin.
Waiting for the Linux version: Checking the Code of the Inkscape Graphics Editor .