Programs for working with music have a small amount of code and, at first, I doubted that it was possible to find a sufficient number of errors for articles. I still wanted to touch on the subject of music software, so I was ready to combine several projects in the article. And now I am writing the third article, trying to somehow fit the interesting errors into one article. The third project for the analysis selected MIDI-sequencer and music editor - Rosegarden. Attention! Reading the article triggers Facepalm!
Introduction
Rosegarden is a free MIDI sequencer, a note editor for Linux using ALSA and JACK, a program for creating and editing music like Apple Logic Pro, Cakewalk Sonar and Steinberg Cubase.
The article includes only the most interesting errors that I found with PVS-Studio. To view the full report, authors can verify the project themselves by requesting a temporary key in support.
PVS-Studio is a tool for detecting errors in the source code of programs written in C, C ++ and C # languages. Works in the environment of Windows and Linux.
')
Error detection example, where Data-flow analysis helps
False warnings always form part of a professional static code analyzer report. It's a shame when people just don’t want to write code better and simply write off warnings as false. Sometimes the code is written in such a confusing manner that it is impossible for another programmer to figure it out without debugging. One way or another, we try to take into account such situations so that the analyzer does not issue false warnings. To this end, Data-flow analysis is being actively developed, which, besides reducing the number of false warnings, allows us to find interesting errors.
V560 A part of the conditional expression is always false: singleStaff. NotationScene.cpp 1707
void NotationScene::layout(....) { .... bool full = (singleStaff == 0 && startTime == endTime); m_hlayout->setViewSegmentCount(m_staffs.size()); if (full) { Profiler profiler("....", true); m_hlayout->reset(); m_vlayout->reset(); bool first = true; for (unsigned int i = 0; i < m_segments.size(); ++i) { if (singleStaff &&
Because of a logical error in the
for loop, the
continue statement is never executed, which probably leads to unnecessary iterations of the loop. The reason for this is to check the
singleStaff pointer in the condition with the '&&' operator. The value of the
singleStff pointer
is always zero. All this code is under the condition "if (full)", when calculating which, the analyzer saw the dependence on the
singleStaff variable:
bool full = (singleStaff == 0 && startTime == endTime);
The value of the
full variable will be
true only if the pointer
singleStaff is zero.
Tale of unreachable code
In this section, I have collected various examples of errors that somehow lead to code failure. All of this applies to
CWE-571: Expression is Always True ,
CWE-570: Expression is Always False ,
CWE-561: Dead Code and their variations.
V547 Expression '! BeamedSomething' is always true. SegmentNotationHelper.cpp 1405
void SegmentNotationHelper::makeBeamedGroupAux(....) { int groupId = segment().getNextId(); bool beamedSomething = false;
This example is very similar to the code in the previous section, but a bit simpler. The variable
beamedSomething is initialized to
false and no longer changes. As a result, the
continue statement is always executed in the
for loop, which is why a large code fragment is never executed.
V547 Expression 'i> 5' is always false. SegmentParameterBox.cpp 323
void SegmentParameterBox::initBox() { .... for (int i = 0; i < 6; i++) { timeT time = 0; if (i > 0 && i < 6) { time = Note(Note::Hemidemisemiquaver).get.... << (i - 1); } else if (i > 5) { time = Note(Note::Crotchet).getDuration() * (i - 4); } .... }
The loop counter takes values in the range from 0 to 5. The first conditional expression is executed for all counter values, except for zero, while the second conditional expression is never executed, because expects the variable
i to have a value of 6 or more.
V547 Expression 'adjustedOctave <8' is always false. NotePixmapFactory.cpp 1920
QGraphicsPixmapItem* NotePixmapFactory::makeClef(....) { .... int oct = clef.getOctaveOffset(); if (oct == 0) return plain.makeItem(); int adjustedOctave = (8 * (oct < 0 ? -oct : oct)); if (adjustedOctave > 8) adjustedOctave--; else if (adjustedOctave < 8) adjustedOctave++; .... }
Let's start to sort out the example in order. The
oct variable
is first initialized with a certain value of the signed type, and then a zero value is excluded from this range. Next, the module of the
oct variable is
computed and multiplied by 8. The resulting value in the
adjustedOctave will have a range [8..N), which makes the check (adjustedOctave <8) meaningless.
V547 Expression '"" is always true. LilyPondOptionsDialog.cpp 64
LilyPondOptionsDialog::LilyPondOptionsDialog(....) { setModal(true); setWindowTitle((windowCaption = "" ? tr("LilyPond Export/Preview") : windowCaption)); .... }
An interesting error with the formation of the header of the modal window. Apparently they wanted to set a new window title if the current value is absent, but they made a mistake in the statement.
For those who did not immediately notice a typo, I will. The '==' operator was to be used, not the '=' operator.
The same code is used when showing another window:
- V547 Expression '"" is always true. MusicXMLOptionsDialog.cpp 60
Note. If the author of the code wanted to set a new title in one line and erase the old one in this way, then no - it’s not cool to write.
V517 The use of if (A) {...} else if (A) {...} 'pattern was detected. There is a possibility of logical error presence. Check lines: 223, 239. IntervalDialog.cpp 223
QString IntervalDialog::getIntervalName(....) { .... if (deviation == -1) textIntervalDeviated += tr("a minor"); else if (deviation == 0)
One of the conditions is superfluous or written with an error. The value 0 is already processed at the very beginning.
No comments
In this section, I will provide some interesting code snippets for working with files. It seems the programmer was inspired by programming languages such as C # and Java. Otherwise, it is not clear why not to create an instance of the
ifstream type simply as a variable on the stack. Dynamic memory allocation here is clearly redundant and, in addition, was the reason for the error.
V773 The function of the testFile 'pointer. A memory leak is possible. RIFFAudioFile.cpp 561
AudioFileType RIFFAudioFile::identifySubType(const QString &filename) { std::ifstream *testFile = new std::ifstream(filename.toLocal8Bit(), std::ios::in | std::ios::binary); if (!(*testFile)) return UNKNOWN; .... testFile->close(); delete testFile; delete [] bytes; return type; }
If there are problems with the file, the
testFile pointer
is not released when the function exits. This is a common pattern that leads to a memory leak.
V773 The 'midiFile' pointer. A memory leak is possible. MidiFile.cpp 1531
bool MidiFile::write(const QString &filename) { std::ofstream *midiFile = new std::ofstream(filename.toLocal8Bit(), std::ios::out | std::ios::binary); if (!(*midiFile)) { RG_WARNING << "write() - can't write file"; m_format = MIDI_FILE_NOT_LOADED; return false; } .... midiFile->close(); return true; }
You might think that this code snippet is the same as the previous one, but this is not quite the case. Unlike the first example, there is no memory release at all in this function. There is always a memory leak.
V668 allocated 'allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated The exception will be generated in the case of memory allocation error. SF2PatchExtractor.cpp 94
SF2PatchExtractor::Device SF2PatchExtractor::read(string fileName) { Device device; ifstream *file = new ifstream(fileName.c_str(), ios::in |....); if (!file) throw FileNotFoundException(); .... }
Here is a list of problems with this code snippet:
- The code is overly complicated;
- Checking the pointer does not make sense here (the new operator will raise an exception if it cannot allocate memory for the object);
- The situation with the absence of the file is not processed;
- Memory leak the pointer is not released anywhere else.
And this place is not one:
- V668 has been allocated using the 'new' operator. The exception will be generated in the case of memory allocation error. RosegardenMainWindow.cpp 4672
- V668 allocated 'allocated allocated allocated allocated allocated allocated allocated allocated allocated allocated The exception will be generated in the case of memory allocation error. SF2PatchExtractor.cpp 67
Errors of incorrect work with data types
V601 The
charger . MidiEvent.cpp 181
QDebug & operator<<(QDebug &dbg, const MidiEvent &midiEvent) { timeT tempo; int tonality; std::string sharpflat; .... tonality = (int)midiEvent.m_metaMessage[0]; if (tonality < 0) { sharpflat = -tonality + " flat";
Suppose the value of the
tonality variable was '42', then in the places indicated in the code they wanted to get the following lines: "42 flat" or "42 sharp". But this doesn’t work the way the programmer expects. Converting a number to a string does not occur; instead, a shifted pointer is stored in the string, forming garbage in the buffer. Or happen
access violation . Or anything will happen at all, since accessing the array abroad results in undefined program behavior.
You can fix the error as follows:
if (tonality < 0) { sharpflat = to_string(-tonality) + " flat"; } else { sharpflat = to_string(tonality); sharpflat += " sharp"; }
V674 The '0.1' literal of the 'double' type is compared to a value of the 'int' type. Consider inspecting the 'm_connectingLineLength> 0.1' expression. StaffLayout.cpp 1028
class StaffLayout { .... protected: int m_connectingLineLength; .... } int m_connectingLineLength; void StaffLayout::resizeStaffLineRow(int row, double x, double length) { .... if (m_pageMode != LinearMode && m_connectingLineLength > 0.1) { .... }
Comparing a variable of type
int with a value of 0.1 is meaningless. Perhaps something else was meant here. The authors of the project should carefully study this code.
V601 The string literal is implicitly cast to the bool type. FileSource.cpp 902
bool FileSource::createCacheFile() { { QMutexLocker locker(&m_mapMutex); #ifdef DEBUG_FILE_SOURCE std::cerr << "...." << m_refCountMap[m_url] << std::endl; #endif if (m_refCountMap[m_url] > 0) { m_refCountMap[m_url]++; m_localFilename = m_remoteLocalMap[m_url]; #ifdef DEBUG_FILE_SOURCE std::cerr << "...." << m_refCountMap[m_url] << std::endl; #endif m_refCounted = true; return true; } } QDir dir; try { dir = TempDirectory::getInstance()->....; } catch (DirectoryCreationFailed f) { #ifdef DEBUG_FILE_SOURCE std::cerr << "...." << f.what() << std::endl; #endif return ""; // <= } .... }
In one place, instead of
true / false values, the function returns an empty string, which is always interpreted as
true .
Errors with iterators
Using iterators in this project looks no less strange than working with files.
V783 Dereferencing of the invalid iterator 'i' might take place. IconStackedWidget.cpp 126
void IconStackedWidget::slotPageSelect() { iconbuttons::iterator i = m_iconButtons.begin(); int index = 0; while (((*i)->isChecked() == false) && (i != m_iconButtons.end())) { ++i; index++; } m_pagePanel->setCurrentIndex(index); }
In the
while loop, the order of checking the iterator
i is confused. There is nothing unusual in this code, a classic mistake.
V783 Dereferencing of the invalid iterator 'beatTimeTs.end ()' might take place. CreateTempoMapFromSegmentCommand.cpp 119
void CreateTempoMapFromSegmentCommand::initialise(Segment *s) { .... std::vector<timeT> beatTimeTs; .... for (int i = m_composition->...At(*beatTimeTs.begin() - 1) + 1; i <= m_composition->...At(*beatTimeTs.end() - 1); ++i){ .... }
The analyzer detected another call to the
end () iterator. Perhaps you wanted to get something like this code:
...At(*(beatTimeTs.end() - 1))
but forgot brackets.
Similar code is in another file:
- V783 Dereferencing of the invalid iterator 'm_segments.end ()' might take place. StaffHeader.cpp 250
Pointer errors
V1004 The 'track' pointer was unsafely after it was verified against nullptr. Check lines: 319, 329. MatrixView.cpp 329
void MatrixView::slotUpdateWindowTitle(bool m) { .... Track *track = m_segments[0]->getComposition()->getTrackById(trackId); int trackPosition = -1; if (track) trackPosition = track->getPosition();
With arrows, I marked two places where the
track pointer is dereferenced. First place is safe, because the pointer is exactly nonzero. Second place may lead to undefined program behavior. In the above code snippet there are no indirect checks. The code runs sequentially and contains a potential error.
Other dangerous pointer dereferencing:
- V1004 The 'track' pointer was unsafely after it was verified against nullptr. Check lines: 2528, 2546. RosegardenDocument.cpp 2546
- V1004 The 'inst' pointer was used unsafely after it was verified against nullptr. Check lines: 392, 417. ManageMetronomeDialog.cpp 417
- V1004 The 'controller' pointer was unsafely after it was verified against nullptr. Check lines: 75, 84. ControllerEventsRuler.cpp 84
V595 The 'm_scene' pointer was used before it was verified against nullptr. Check lines: 1001, 1002. NotationWidget.cpp 1001
void NotationWidget::slotEnsureTimeVisible(timeT t) { m_inMove = true; QPointF pos = m_view->mapToScene(0,m_view->height()/2); pos.setX(m_scene->getRulerScale()->getXForTime(t));
The
V595 diagnostic finds a similar type of error. Here, the
s_scene pointer
is dereferenced on one line, and immediately on the next one it is checked whether it is valid.
V595 The 'm_hideSignatureButton' pointer was used against it. Check lines: 248, 258. TimeSignatureDialog.cpp 248
TimeSignature TimeSignatureDialog::getTimeSignature() const { QSettings settings; settings.beginGroup( GeneralOptionsConfigGroup ); settings.setValue("timesigdialogmakehidden", m_hideSignatureButton->isChecked());
An error similar to the previous example, but I decided to bring this code fragment anyway. Three dereferencing of potentially null pointers is performed here.
All other similar places will give a list:
- V595 The 'm_timeT' pointer was used before it was verified against nullptr. Check lines: 690, 696. TimeWidget.cpp 690
- V595 The 'm_scene' pointer was used before it was verified against nullptr. Check lines: 526, 538. NoteRestInserter.cpp 526
- V595 The 'item' pointer has been verified against nullptr. Check lines: 318, 320. TempoView.cpp 318
- V595 The 'm_scene' pointer was used before it was verified against nullptr. Check lines: 902, 903. MatrixWidget.cpp 902
- V595 The 'm_seqManager' pointer was used before it was verified against nullptr. Check lines: 1029, 1058. RosegardenMainWindow.cpp 1029
- V595 The 'm_seqManager' pointer was used before it was verified against nullptr. Check lines: 5690, 5692. RosegardenMainWindow.cpp 5690
- V595 The 'm_seqManager' pointer was used before it was verified against nullptr. Check lines: 5701, 5704. RosegardenMainWindow.cpp 5701
- V595 The m_controller 'pointer was used before it was verified against nullptr. Check lines: 553, 563. ControllerEventsRuler.cpp 553
- V595 '' pointer pointer pointer pointer pointer Check lines: 418, 420. MarkerRuler.cpp 418
- V595 The 'm_doc' pointer was used before it was verified against nullptr. Check lines: 490, 511. SequenceManager.cpp 490
- V595 The m_groupLocalEventBuffers' pointer was used against nullptr. Check lines: 329, 335. DSSIPluginInstance.cpp 329
- V595 The 'm_instrumentMixer' pointer was used before it was verified against nullptr. Check lines: 699, 709. AudioProcess.cpp 699
Rare mistake
An error with the order of initialization of class members is very rare. In our database of errors there are only twelve
references to this error.
V670 The uninitialized class member 'm_intervals' is used to initialize the 'm_size' member. Remember that members are initialized in the order of declarations inside a class. Tuning.cpp 394
class Tuning { .... int m_size;
Class fields are initialized in the order in which they are defined in the class. In the given code example, the
m_size field
is initialized first and will have an incorrect value.
miscellanea
V557 Array overrun is possible. The value of the submaster index could reach 64. SequencerDataBlock.cpp 325
#define SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS 64 class SequencerDataBlock { .... protected: int m_submasterLevelUpdateIndices[64]; .... } bool SequencerDataBlock::getSubmasterLevel(int submaster, ....) const { ....int lastUpdateIndex[SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS]; if (submaster < 0 || submaster > SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS) { info.level = info.levelRight = 0; return false; } int currentUpdateIndex=m_submasterLevelUpdateIndices[submaster]; info = m_submasterLevels[submaster]; if (lastUpdateIndex[submaster] != currentUpdateIndex) { lastUpdateIndex[submaster] = currentUpdateIndex; return true; } else { return false; // no change } }
This mistake has already become a classic. When comparing the array index with the maximum value, for some reason the operator '>' and '> =' are always confused. Such an error results in a conversion outside the array. In this case, even to two arrays.
The correct check should look like this:
if (submaster < 0 || submaster >= SEQUENCER_DATABLOCK_MAX_NB_SUBMASTERS) {
This code is copied into two more functions:
- V557 Array overrun is possible. The value of the submaster index could reach 64. SequencerDataBlock.cpp 343
- V557 Array overrun is possible. The value of the submaster index could reach 64. SequencerDataBlock.cpp 344
V612 An unconditional 'break' within a loop. Fingering.cpp 83
Fingering::Barre Fingering::getBarre() const { int lastStringStatus = m_strings[getNbStrings() - 1]; Barre res; res.fret = lastStringStatus; for(unsigned int i = 0; i < 3; ++i) { if (m_strings[i] > OPEN && m_strings[i] == lastStringStatus) res.start = i; break; } res.end = 5; return res; }
I already gave examples of code whose styles were similar to C # or Java. Here is an obvious similarity with the Python language. Unfortunately (for the author of the code), this is not how it works in C ++. The
break statement is not in a condition, but always runs on the first iteration of the loop.
V746 Object slicing. By reference rather than by value. MupExporter.cpp 197
timeT MupExporter::writeBar(....) { .... try {
Intercepting an exception by value can lead to several types of errors. In the project code I found this class:
class BadSoundFileException : public Exception
When catching an exception by value, a new object of the
Exception class will be created, and information about the inherited class
BadSoundFileException will be lost.
There are about fifty such places in the project.
V523 The 'then' statement is equivalent to the 'else' statement. HydrogenXMLHandler.cpp 476
bool HydrogenXMLHandler::characters(const QString& chars) { bool rc=false; if (m_version=="") { rc=characters_093(chars); } else { rc=characters_093(chars); } return rc; }
Suspicious place. The presence of different comments suggests different code, but here it is not.
Two similar warnings:
- V523 The 'then' statement is equivalent to the 'else' statement. HydrogenXMLHandler.cpp 182
- V523 The 'then' statement is equivalent to the 'else' statement. HydrogenXMLHandler.cpp 358
Conclusion
While this is a project with the lowest quality code of all in the review. We will continue to study further.
Other reviews:
If you know an interesting software for working with music and want to see it in the review, then send the names to me by
mail .
It is very easy to try the PVS-Studio analyzer on your project, just go to the
download page.
If you want to share this article with an English-speaking audience, then please use the link to the translation: Svyatoslav Razmyslov.
Review of Music Software Code Defects Part 3. Rosegarden