SlideShare a Scribd company logo
1 of 30
Download to read offline
Zero, one, two, Freddy's coming for you
Author: Andrey Karpov
Date: 14.02.2020
Tags: Cpp, Java, CSharp, Knowledge
This post continues the series of articles, which can well be called "horrors for developers". This time it will
also touch upon a typical pattern of typos related to the usage of numbers 0, 1, 2. The language you're
writing in doesn't really matter: it can be C, C++, C#, or Java. If you're using constants 0, 1, 2 or variables'
names contain these numbers, most likely, Freddy will come to visit you at night. Go on, read and don't say
we didn't warn you.
Introduction
I continue the series of articles on the patterns noticed of how people make mistakes. Previous posts:
1. Last Line Effect
2. The most dangerous function in the C/C++ world
3. The Evil within the Comparison Functions
This time it wasn't me who took note of the pattern, but my colleague Svyatoslav Razmyslov. He noticed
that in his articles he was constantly describing problems involving variables with numbers 1 and 2 in their
names. Svyatoslav invited me to explore this issue in more detail. Eventually, efforts made ended up very
fruitful. It emerged that in our error collection there are a lot of code fragments that are erroneous because
of the fact that people got confused in 0, 1, 2 indexes or variables names, containing such numbers. A new
interesting pattern has been revealed, which will be discussed below. I am grateful to Svyatoslav for a hint
to look into this topic and, therefore, I dedicate this article to him.
Svyatoslav Razmyslov, manager, attentive bug hunter and just a talented person.
What is the purpose of this article? To show how easy it is for all of us to make mistakes and make typos.
Forewarned developers - more attentive developers. Especially during code reviews when they focus on
these ill-fated 0, 1, 2. Developers will also be able to appreciate the contribution of static code analyzers
that help to spot such errors. It's not about advertising PVS-Studio (well, to some extent, it is :). Until now,
many developers consider static analysis superfluous, preferring to focus on their own accuracy and code
reviews. Unfortunately, attempts to write clean code are laudable but not enough. This article will once
again convincingly demonstrate this.
No one is immune to errors. Below you will see epic blunders in even such well-known projects as Qt, Clang,
Hive, LibreOffice, Linux Kernel, .NET Compiler Platform, XNU kernel, Mozilla Firefox. By the way, these are
not some exotic rare mistakes, but the most common ones. Still not convincing enough? Then let's get
going!
"Talk is cheap. Show me bugs!"
(c) remade quote by Linus Torvalds.
Typos in constants when indexing arrays
Usually in our articles we cite warnings that helped to find certain errors. This time I'll omit these warnings,
as even without them errors will still be obvious and clear. Even though these bugs leap out in a short code
fragment, they are great at hiding in projects' code.
Let's start with confusions with numerical literals, used for arrays indexing. Despite the banality of these
errors, they are many and they can be found in projects that are much greater than students' laboratory
researches.
XNU kernel project, C
uint32_t
gss_krb5_3des_unwrap_mbuf(....)
{
....
for (cflag = 1; cflag >= 0; cflag--) {
*minor = gss_krb5_3des_token_get(
ctx, &itoken, wrap, &hash, &offset, &length, reverse);
if (*minor == 0)
break;
wrap.Seal_Alg[0] = 0xff;
wrap.Seal_Alg[0] = 0xff;
}
....
}
The line was copied, but the index stayed the same. Most likely, the code here is supposed to be as follows:
wrap.Seal_Alg[0] = 0xff;
wrap.Seal_Alg[1] = 0xff;
LibreOffice project, C++
Sequence< OUString > FirebirdDriver::
getSupportedServiceNames_Static() throw (RuntimeException)
{
Sequence< OUString > aSNS( 2 );
aSNS[0] = "com.sun.star.sdbc.Driver";
aSNS[0] = "com.sun.star.sdbcx.Driver";
return aSNS;
}
As in the previous case, the authors copied the line, but forgot to change 0 for 1. Only fixed the string literal.
One might ask the philosophical question - how can you make such an error in a four-line function? You can
and that's it. That's what programming is like.
Quake-III-Arena project, C
int VL_FindAdjacentSurface(....)
{
....
if (fabs(dir[0]) > test->radius ||
fabs(dir[1]) > test->radius ||
fabs(dir[1]) > test->radius)
{
....
}
The developer forgot to change dir[1] for dir[2] in the copied line. As a result - the value on Z axis is out of
control.
OpenCOLLADA project, C++
struct short2
{
short values[2];
short2(short s1, short s2)
{
values[0] = s1;
values[2] = s2;
}
....
};
Yes, even in such a short constructor one can find a way to be out of array bounds during its initialization.
Godot Engine, C++
Array PhysicsDirectSpaceState::_cast_motion(....)
{
....
Array ret(true);
ret.resize(2);
ret[0]=closest_safe;
ret[0]=closest_unsafe;
return ret;
}
No comment is needed.
Asterisk, C
static void sip_threadinfo_destructor(void *obj)
{
struct sip_threadinfo *th = obj;
struct tcptls_packet *packet;
if (th->alert_pipe[1] > -1) { // <=
close(th->alert_pipe[0]);
}
if (th->alert_pipe[1] > -1) {
close(th->alert_pipe[1]);
}
th->alert_pipe[0] = th->alert_pipe[1] = -1;
....
}
When writing similar blocks, an error is usually in the last one. All above cases were like this, except for the
last one. Here the typo is in an unusual place, namely, in the first block. It's hard to say why it happened so.
I'll just leave the picture of a unicorn shrugging his shoulders:
Open CASCADE Technology, C++
inline void Prepend(const Standard_Integer theIndex)
{
if (myIndex[1] >= 0)
Standard_OutOfRange::Raise ("BRepMesh_PairOfIndex....");
myIndex[1] = myIndex[0];
myIndex[1] = theIndex;
}
Different values are copied twice in the same array slot. Obviously, it's an error. The project code is
unfamiliar to me, so it's not clear how to fix this bug. So I just looked at how the developers fixed the code
after our team pointed out this error to them. Here is the correct version:
myIndex[1] = myIndex[0];
myIndex[0] = theIndex;
Trans-Proteomic Pipeline, C++
void ASAPRatio_getProDataStrct(proDataStrct *data,
char **pepBofFiles)
{
....
if (data->indx == -1) {
data->ratio[0] = -2.;
data->ratio[0] = 0.; // <=
data->inv_ratio[0] = -2.;
data->inv_ratio[1] = 0.;
return;
}
....
}
I'm concerned that such errors take place in research packages. Trans-Proteomic Pipeline is designed to
handle the tasks in biology. One might make a real mess of things and screw up the entire research. We
found many intriguing things in this package: check in 2012, check in 2013. Perhaps, we should take
another look at this project.
ITK project, C++
Here is another project for medical research: Medicine Insight Segmentation and Registration Toolkit (ITK).
The project is different, and the bugs are the same.
template< typename TCoordRepType >
void
VoronoiDiagram2D< TCoordRepType >::SetOrigin(PointType vorsize)
{
m_VoronoiBoundaryOrigin[0] = vorsize[0];
m_VoronoiBoundaryOrigin[0] = vorsize[1];
}
ITK project, C++
int itkPointSetToSpatialObjectDemonsRegistrationTest(....)
{
....
// Set its position
EllipseType::TransformType::OffsetType offset;
offset[0]=50;
offset[1]=50;
offset[1]=50;
....
}
Copy-Paste at its finest.
ReactOS project, C++
HPALETTE CardWindow::CreateCardPalette()
{
....
//include button text colours
cols[0] = RGB(0, 0, 0);
cols[1] = RGB(255, 255, 255);
//include the base background colour
cols[1] = crBackgnd;
//include the standard button colours...
cols[3] = CardButton::GetHighlight(crBackgnd);
cols[4] = CardButton::GetShadow(crBackgnd);
cols[5] = CardButton::GetFace(crBackgnd);
....
}
Apparently, the crBackgnd constant had to be written in the cols[2] slot.
Coin3D project, C++
SoVRMLInline::GLRender(SoGLRenderAction * action)
{
....
if ((size[0] >= 0.0f && size[1] >= 0.0f && size[1] >= 0.0f) &&
((vis == ALWAYS) ||
(vis == UNTIL_LOADED && child == NULL))) {
....
}
The size[1] array element is checked twice, whereas the size[2] element isn't checked at all. That's how
strange artifacts appear in the images.
OpenCV project, C++
bool Jpeg2KDecoder::readHeader()
{
....
cmptlut[0] = ....
cmptlut[1] = ....
cmptlut[2] = ....
if( cmptlut[0] < 0 || cmptlut[1] < 0 || cmptlut[0] < 0 )
result = false;
....
}
My gut is telling me that the cmptlut[0] < 0 expression was copied twice, but 0 was changed just once.
Visualization Toolkit (VTK) project, C++
void vtkImageStencilRaster::PrepareForNewData(....)
{
....
if (allocateExtent &&
allocateExtent[1] >= allocateExtent[1])
....
}
In this case and later I won't comment many similar errors. Why comment? The main thing when looking
through such code fragments is to become aware of the fact that even if the error is simple it doesn't mean
a developer will definitely notice it.
Visualization Toolkit (VTK) project, C++
template <class iterT>
void vtkDataSetAttributesCopyValues(....)
{
....
inZPtr +=
(outExt[0] - outExt[0])*inIncs[0] * data_type_size +
(outExt[2] - outExt[2])*inIncs[1] * data_type_size +
(outExt[4] - outExt[4])*inIncs[2] * data_type_size;
....
}
Here the programmer was clearly in a hurry and wrote the code very quickly. It's hard to explain how he
made a mistake three times. The elements of the array are subtracted from themselves. The result is that
this code equals the following:
inZPtr +=
(0)*inIncs[0] * data_type_size +
(0)*inIncs[1] * data_type_size +
(0)*inIncs[2] * data_type_size;
However, this code can be shortened even more:
inZPtr += 0;
Just great. There's a long, serious-looking expression in the code that doesn't really do anything. I just love
such cases.
Visualization Toolkit (VTK) project, C++
A similar case of hasty coding.
void vtkPiecewiseControlPointsItem::SetControlPoint(
vtkIdType index, double* newPos)
{
double oldPos[4];
this->PiecewiseFunction->GetNodeValue(index, oldPos);
if (newPos[0] != oldPos[0] || newPos[1] != oldPos[1] ||
newPos[2] != oldPos[2] || newPos[2] != oldPos[2])
{
this->PiecewiseFunction->SetNodeValue(index, newPos);
}
}
The newPos[2] != oldPos[2] comparison repeats twice.
ADAPTIVE Communication Environment (ACE), C++
bool URL_Base::strip_scheme (ACE_CString& url_string)
{
....
ACE_CString::size_type pos = url_string.find (':');
if (pos > 0 &&
url_string[pos+1] == '/' &&
url_string[pos+1] == '/')
{
....
// skip '<protocol>://'
url_string = url_string.substr (pos+3);
}
....
}
The condition should check that there are two slashes after the colon. In other words, we look for the
substring "://". Due to a typo, the check gets blinded and considers any character as a second slash.
IPP Samples, C++
void MeBase::MakeVlcTableDecision()
{
....
Ipp32s BestMV =
IPP_MIN(IPP_MIN(m_cur.MvRate[0],m_cur.MvRate[1]),
IPP_MIN(m_cur.MvRate[2],m_cur.MvRate[3]));
Ipp32s BestAC =
IPP_MIN(IPP_MIN(m_cur.AcRate[0],m_cur.AcRate[1]),
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2]));
....
}
The typo lies here in the macro arguments:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2])
As a result, the minimum value is chosen from two equal ones. In fact, the following should be written:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[3])
By the way, this code might demonstrate the benefit of the standard library. If we write in the following
way:
Ipp32s BestMV = std::min_element(begin(m_cur.MvRate), end(m_cur.MvRate));
Ipp32s BestAC = std::min_element(begin(m_cur.AcRate), end(m_cur.AcRate));
The code will be shorter and less prone to errors. Actually, the less of the same-type code, the more likely it
is to be written correctly.
Audacity, C++
sampleCount VoiceKey::OnBackward (....) {
....
int atrend = sgn(buffer[samplesleft - 2]-
buffer[samplesleft - 1]);
int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
buffer[samplesleft - WindowSizeInt-2]);
....
}
Correct expression:
int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
buffer[samplesleft - WindowSizeInt-1]);
PDFium, C++
void sycc420_to_rgb(opj_image_t* img) {
....
opj_image_data_free(img->comps[0].data);
opj_image_data_free(img->comps[1].data);
opj_image_data_free(img->comps[2].data);
img->comps[0].data = d0;
img->comps[1].data = d1;
img->comps[2].data = d2;
img->comps[1].w = yw; // 1
img->comps[1].h = yh; // 1
img->comps[2].w = yw; // 1
img->comps[2].h = yh; // 1
img->comps[1].w = yw; // 2
img->comps[1].h = yh; // 2
img->comps[2].w = yw; // 2
img->comps[2].h = yh; // 2
img->comps[1].dx = img->comps[0].dx;
img->comps[2].dx = img->comps[0].dx;
img->comps[1].dy = img->comps[0].dy;
img->comps[2].dy = img->comps[0].dy;
}
Some actions aimed on initializing the structure repeat. Lines with the comment //2 can be removed
without changing anything. I doubted about adding this code fragment in the article. It's not exactly an
error, and not quite with indexes. Nevertheless, this redundant code has probably appeared here right
because of the fact that the programmer got confused in all these class members and 1, 2 indexes. So I think
this piece of code is great to demonstrate how easy it is to get confused in numbers.
CMake project, C
The code next up isn't written by CMake developers, but borrowed. As the comment says at the beginning
of the file, the utf8_encode function was written by Tim Kientzle back in 2007. Since then, this function
roams from project to project and can be met in many places. I didn't dig into the initial source, as it's not
the matter of importance. Once the CMake project includes this code, the error applies to CMake as well.
static char *
utf8_encode(const wchar_t *wval)
{
....
p[0] = 0xfc | ((wc >> 30) & 0x01);
p[1] = 0x80 | ((wc >> 24) & 0x3f);
p[1] = 0x80 | ((wc >> 18) & 0x3f);
p[2] = 0x80 | ((wc >> 12) & 0x3f);
p[3] = 0x80 | ((wc >> 6) & 0x3f);
p[4] = 0x80 | (wc & 0x3f);
p += 6;
....
}
As you can see, there is some confusion with the indexes. The value is written twice in the p[1] array
element. If you look at the adjacent code, it becomes clear that the correct code should be this:
p[0] = 0xfc | ((wc >> 30) & 0x01);
p[1] = 0x80 | ((wc >> 24) & 0x3f);
p[2] = 0x80 | ((wc >> 18) & 0x3f);
p[3] = 0x80 | ((wc >> 12) & 0x3f);
p[4] = 0x80 | ((wc >> 6) & 0x3f);
p[5] = 0x80 | (wc & 0x3f);
p += 6;
Note
Please, note that all errors in this section relate to the code in C and C++. There is no code in C# or Java!
It's very interesting, I didn't expect this. In my opinion, the typos considered don't depend on the language.
In the sections below, there will be errors in code, written in other languages. I think it's just a coincidence.
The PVS-Studio analyzer has started to support the C#/Java languages much later than C/C++, and we just
didn't have enough time to collect the examples of the above errors types.
However, this conclusion is still interesting. Apparently, C and C++ programmers are more inclined to use
numbers 0, 1, 2 when working with arrays :).
Typos in names
This will be the largest section. It is very easy for people to get confused in names such as a1 and a2. You
might think: "How could you ever get confused here"? You can. And very easily. Now the reader will be able
to see it.
Hive project, Java
@Override
public List<ServiceInstance> getAllInstancesOrdered() {
List<ServiceInstance> list = new LinkedList<>();
list.addAll(instances.values());
Collections.sort(list, new Comparator<ServiceInstance>() {
@Override
public int compare(ServiceInstance o1, ServiceInstance o2) {
return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity());
}
});
return list;
}
The comparison function compare receives two objects: o1 and o2. But due to the typo only o2 is used.
Interestingly, this error has made its way to another function because of Copy-Paste:
@Override
public List<ServiceInstance> getAllInstancesOrdered() {
List<ServiceInstance> list = new LinkedList<>();
readLock.lock();
try {
list.addAll(instances.values());
} finally {
readLock.unlock();
}
Collections.sort(list, new Comparator<ServiceInstance>() {
@Override
public int compare(ServiceInstance o1, ServiceInstance o2) {
return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity());
}
});
return list;
}
Infer.NET project, C#
private void MergeParallelTransitions()
{
....
if (double.IsInfinity(transition1.Weight.Value) &&
double.IsInfinity(transition1.Weight.Value))
....
}
Doom 3 project, C++
uint AltOp::fixedLength()
{
uint l1 = exp1->fixedLength();
uint l2 = exp1->fixedLength();
if (l1 != l2 || l1 == ~0u)
return ~0;
return l1;
}
If you didn't notice the typo, look at the line, where the l2 variable is initialized. exp2 had to be used.
Source Engine SDK project, C++
void GetFPSColor( int nFps, unsigned char ucColor[3] )
{
....
int nFPSThreshold1 = 20;
int nFPSThreshold2 = 15;
if (IsPC() &&
g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 95)
{
nFPSThreshold1 = 60;
nFPSThreshold1 = 50;
}
....
}
The correct version:
nFPSThreshold1 = 60;
nFPSThreshold2 = 50;
Linux Kernel project, C
By the way, in addition to variable names, typos can be in macros' names. Here are a few examples.
int private_ioctl(struct vnt_private *pDevice, struct ifreq *rq)
{
....
if (sStartAPCmd.byBasicRate & BIT3) {
pMgmt->abyIBSSSuppRates[2] |= BIT7;
pMgmt->abyIBSSSuppRates[3] |= BIT7;
pMgmt->abyIBSSSuppRates[4] |= BIT7;
pMgmt->abyIBSSSuppRates[5] |= BIT7;
} else if (sStartAPCmd.byBasicRate & BIT2) {
pMgmt->abyIBSSSuppRates[2] |= BIT7;
pMgmt->abyIBSSSuppRates[3] |= BIT7;
pMgmt->abyIBSSSuppRates[4] |= BIT7;
} else if (sStartAPCmd.byBasicRate & BIT1) { // <=
pMgmt->abyIBSSSuppRates[2] |= BIT7;
pMgmt->abyIBSSSuppRates[3] |= BIT7;
} else if (sStartAPCmd.byBasicRate & BIT1) { // <=
pMgmt->abyIBSSSuppRates[2] |= BIT7;
} else {
/* default 1,2M */
pMgmt->abyIBSSSuppRates[2] |= BIT7;
pMgmt->abyIBSSSuppRates[3] |= BIT7;
}
....
}
As you can see, the mask with the BIT1 name is used twice, which makes the second check pointless. The
body of the second conditional operator marked by the comment will never execute.
CMaNGOS project, C++
void AttackedBy(Unit* pAttacker) override
{
....
DoScriptText(urand(0, 1) ?
SAY_BELNISTRASZ_AGGRO_1 :
SAY_BELNISTRASZ_AGGRO_1,
m_creature, pAttacker);
....
}
The project was intended to include random behavior, but the same constant SAY_BELNISTRASZ_AGGRO_1
is chosen every time.
Vangers project: One For The Road, C++
const char* iGetJoyBtnNameText(int vkey,int lang)
{
....
if (vkey >= VK_STICK_SWITCH_1 && vkey <= VK_STICK_SWITCH_9)
{
ret = (lang)
? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]
: iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1];
return ret;
}
....
}
According to the written code, the correct version has to be the following:
ret = (lang)
? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]
: iJoystickStickSwitch1[vkey - VK_STICK_SWITCH_1];
RT-Thread project, C
uint8_t can_receive_message_length(uint32_t can_periph,
uint8_t fifo_number)
{
uint8_t val = 0U;
if(CAN_FIFO0 == fifo_number){
val = (uint8_t)(CAN_RFIFO0(can_periph) & CAN_RFIFO_RFL0_MASK);
}else if(CAN_FIFO0 == fifo_number){
val = (uint8_t)(CAN_RFIFO1(can_periph) & CAN_RFIFO_RFL0_MASK);
}else{
/* illegal parameter */
}
return val;
}
RT-Thread is a real-time open source OS for embedded devices. Here we see confusion between FIFO 0 and
FIFO 1. And somewhere, someone's going to stumble upon a glitchy device.
The error is here:
if (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO0 == fifo_number){
The second check always gives false. The correct version:
if (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO1 == fifo_number){
Hive project, Java
private void
generateDateTimeArithmeticIntervalYearMonth(String[] tdesc) throws Exception {
String operatorName = tdesc[1];
String operatorSymbol = tdesc[2];
String operandType1 = tdesc[3];
String colOrScalar1 = tdesc[4];
String operandType2 = tdesc[5];
String colOrScalar2 = tdesc[6];
....
if (colOrScalar1.equals("Col") && colOrScalar1.equals("Column")) {
....
} else if (colOrScalar1.equals("Col") && colOrScalar1.equals("Scalar")) {
....
}
The PVS-Studio analyzer indicates about 2 errors at once:
1. The string in colOrScalar1 can't be equal to both strings "Col" and "Column";
2. The string in colOrScalar1 can't be equal to both strings "Col" and "Scalar";
Variable names are definitely muddled up.
Shareaza project, C++
void CDownloadWithSources::MergeMetadata(const CXMLElement* pXML)
{
CQuickLock pLock( Transfers.m_pSection );
CXMLAttribute* pAttr1 =
m_pXML->GetAttribute(CXMLAttribute::schemaName);
CXMLAttribute* pAttr2 =
pXML->GetAttribute(CXMLAttribute::schemaName);
if (pAttr1 && pAttr2 &&
!pAttr1->GetValue().CompareNoCase(pAttr1->GetValue()))
....
}
The correct version:
pAttr1->GetValue().CompareNoCase(pAttr2->GetValue())
Note
Let's take a small break. There is concern, that when looking through a bunch of banal mistakes, we will
forget why we do it.
The goal is not to scorn at someone else's code. All this is not the reason to play blame game and say: "Oh,
my goodness, that's stupid!" This is the reason to pause to think!
Posts of our team are intended to show that none of us is immune to mistakes. The errors described in the
article appear in code much more often than you might expect. It is also important that the probability of
getting confused in 0, 1, 2 almost doesn't depend on the programmer's skill.
It is useful to realize that people tend to make mistakes. Without this, you can't take the next step in
improving the quality and reliability of the code. Realizing that we all might be wrong, people begin to try to
identify errors at the earliest stages, using coding standards, code reviews, unit tests, static and dynamic
analyzers. That's very good.
Then why are we writing about obvious things? Unfortunately, based on numerous conversations with
developers, we have to state that it is not always so clear to everyone. Many people have too high self-
esteem and they simply don't allow the idea that they are able to make simple mistakes. It's sad.
If you are a teamlead/manager, I invite you to read this note.
Qt project, C++
AtomicComparator::ComparisonResult
IntegerComparator::compare(const Item &o1,
const AtomicComparator::Operator,
const Item &o2) const
{
const Numeric *const num1 = o1.as<Numeric>();
const Numeric *const num2 = o1.as<Numeric>();
if(num1->isSigned() || num2->isSigned())
....
}
The correct version:
const Numeric *const num2 = o2.as<Numeric>();
Android project, C++
static inline bool isAudioPlaybackRateEqual(
const AudioPlaybackRate &pr1,
const AudioPlaybackRate &pr2)
{
return fabs(pr1.mSpeed - pr2.mSpeed) <
AUDIO_TIMESTRETCH_SPEED_MIN_DELTA &&
fabs(pr1.mPitch - pr2.mPitch) <
AUDIO_TIMESTRETCH_PITCH_MIN_DELTA &&
pr2.mStretchMode == pr2.mStretchMode &&
pr2.mFallbackMode == pr2.mFallbackMode;
}
There are two typos at once, due to which, variables pr2.mStretchMode and pr2.mFallbackMode are
compared with themselves.
Boost project, C++
point3D operator/(const point3D &p1, const point3D &p2)
{
return point3D(p1.x/p2.x, p1.y/p2.y, p1.z/p1.z);
}
At the very end, thanks to a typo, the p1.z variable is divided into itself.
Clang project, C++
bool haveSameType(QualType Ty1, QualType Ty2) {
return (Context.getCanonicalType(Ty1) ==
Context.getCanonicalType(Ty2) ||
(Ty2->isIntegerType() &&
Ty2->isIntegerType()));
}
Yes, believe it or not, the PVS-Studio analyzer detects such bugs in compilers. The correct version:
(Ty1->isIntegerType() &&
Ty2->isIntegerType())
Clang project, C++
Instruction *InstCombiner::visitXor(BinaryOperator &I) {
....
if (Op0I && Op1I && Op0I->isShift() &&
Op0I->getOpcode() == Op1I->getOpcode() &&
Op0I->getOperand(1) == Op1I->getOperand(1) &&
(Op1I->hasOneUse() || Op1I->hasOneUse())) {
....
}
The correct version:
(Op0I->hasOneUse() || Op1I->hasOneUse())
Qt project, C++
inline bool qCompare(QImage const &t1, QImage const &t2, ....)
{
....
if (t1.width() != t2.width() || t2.height() != t2.height()) {
....
}
NCBI Genome Workbench project, C++
static bool s_PCRPrimerSetLess(const CPCRPrimerSet& s1, const CPCRPrimerSet& s2)
{
if (!s1.IsSet() && s1.IsSet()) {
return true;
} else if (s1.IsSet() && !s2.IsSet()) {
return false;
} else if (!s1.IsSet() && !s2.IsSet()) {
return false;
} else if (s1.Get().size() < s2.Get().size()) {
return true;
} else if (s1.Get().size() > s2.Get().size()) {
return false;
} else {
.....
}
Error in the very first check. It should be like this:
if (!s1.IsSet() && s2.IsSet()) {
NCBI Genome Workbench project, C++
CRef<CSeq_align> CNWAligner::Run(CScope &scope, const CSeq_loc &loc1,
const CSeq_loc &loc2, bool trim_end_gaps)
{
if ((!loc1.IsInt() && !loc1.IsWhole()) ||
(!loc1.IsInt() && !loc1.IsWhole()))
{
NCBI_THROW(CException, eUnknown,
"Only whole and interval locations supported");
}
....
}
The first line of condition was copy-pasted, then the programmer got in a hurry and forgot to replace loc1
with loc2.
FlashDevelop project, C#
public void SetPrices(....)
{
UInt32 a0 = _choice.GetPrice0();
UInt32 a1 = _choice.GetPrice1();
UInt32 b0 = a1 + _choice2.GetPrice0(); // <=
UInt32 b1 = a1 + _choice2.GetPrice1();
....
}
FreeCAD project, C++
inline void insEdgeVec(std::map<int,std::set<int> > &map,
int n1, int n2)
{
if(n1<n2)
map[n2].insert(n1);
else
map[n2].insert(n1);
};
Regardless of the condition, one and the same action is executed. It would seem such a simple case. How
was it possible to copy the line and not fix it? As you can see, it is possible.
LibreOffice project, C++
class SVX_DLLPUBLIC SdrMarkView : public SdrSnapView
{
....
const Point& GetRef1() const { return maRef1; }
const Point& GetRef2() const { return maRef1; }
....
};
Classic Copy-Paste bug. The correct version:
const Point& GetRef2() const { return maRef2; }
LibreOffice project, C++
bool CmpAttr(
const SfxPoolItem& rItem1, const SfxPoolItem& rItem2)
{
....
::boost::optional<sal_uInt16> oNumOffset1 =
static_cast<const SwFmtPageDesc&>(rItem1).GetNumOffset();
::boost::optional<sal_uInt16> oNumOffset2 =
static_cast<const SwFmtPageDesc&>(rItem1).GetNumOffset();
....
}
And another classic Copy-Paste error :). In one fragment authors changed 1 for 2, but they forgot to do it in
the other one.
LibreOffice project, C++
XMLTransformerOOoEventMap_Impl::XMLTransformerOOoEventMap_Impl(
XMLTransformerEventMapEntry *pInit,
XMLTransformerEventMapEntry *pInit2 )
{
if( pInit )
AddMap( pInit );
if( pInit )
AddMap( pInit2 );
}
Here the mistake isn't about changing 1 for 2, here the author simply forgot to add 2 in the second
condition.
Perhaps, you now feel a bit tired. Therefore, let's make some tea or coffee and we'll continue to explore the
world of 0, 1, and 2 numbers.
Geant4 software project, C++
void G4VTwistSurface::GetBoundaryLimit(G4int areacode,
G4double limit[]) const
{
....
if (areacode & sC0Min1Max) {
limit[0] = fAxisMin[0];
limit[1] = fAxisMin[1];
} else if (areacode & sC0Max1Min) {
limit[0] = fAxisMax[0];
limit[1] = fAxisMin[1];
} else if (areacode & sC0Max1Max) {
limit[0] = fAxisMax[0];
limit[1] = fAxisMax[1];
} else if (areacode & sC0Min1Max) {
limit[0] = fAxisMin[0];
limit[1] = fAxisMax[1];
}
....
}
I hope you took the advice and had some rest. Are you ready to find the error in this code now?
Congrats to those who managed to do it! You did great!
However, I understand those who got a bit lazy. Reviewing such code is very tedious and you probably want
to somehow quickly move on to checking something more interesting. Static analyzers are excellent for such
cases, because they don't get tired.
The error is that these two checks are the same:
if (areacode & sC0Min1Max) {
} else if (areacode & sC0Min1Max) {
If you carefully review the code, it becomes clear that the very first check is erroneous. The correct version:
if (areacode & sC0Min1Min) {
} else if (areacode & sC0Max1Min) {
} else if (areacode & sC0Max1Max) {
} else if (areacode & sC0Min1Max) {
CryEngine V project, C++
bool
CompareRotation(const Quat& q1, const Quat& q2, float epsilon)
{
return (fabs_tpl(q1.v.x - q2.v.x) <= epsilon)
&& (fabs_tpl(q1.v.y - q2.v.y) <= epsilon)
&& (fabs_tpl(q2.v.z - q2.v.z) <= epsilon) // <=
&& (fabs_tpl(q1.w - q2.w) <= epsilon);
}
TortoiseGit project, C++
void CGitStatusListCtrl::OnContextMenuList(....)
{
....
if( (!this->m_Rev1.IsEmpty()) ||
(!this->m_Rev1.IsEmpty()) )
....
}
Geant4 software project, C++
G4double G4MesonAbsorption::
GetTimeToAbsorption(const G4KineticTrack& trk1,
const G4KineticTrack& trk2)
{
....
if(( trk1.GetDefinition() == G4Neutron::Neutron() ||
trk1.GetDefinition() == G4Neutron::Neutron() ) &&
sqrtS>1.91*GeV && pi*distance>maxChargedCrossSection)
return time;
....
}
MonoDevelop project, C#
private bool MembersMatch(ISymbol member1, ISymbol member2)
{
....
if (member1.DeclaredAccessibility !=
member1.DeclaredAccessibility
|| member1.IsStatic != member1.IsStatic)
{
return false;
}
....
}
As you can see, the above code fragments go unexplained so far. Actually, there is nothing to explain. You
can only groan and offer your empathy.
Dolphin Emulator project, C++
bool IRBuilder::maskedValueIsZero(InstLoc Op1, InstLoc Op2) const
{
return (~ComputeKnownZeroBits(Op1) &
~ComputeKnownZeroBits(Op1)) == 0;
}
RunAsAdmin Explorer Shim project, C++
bool IsLuidsEqual(LUID luid1, LUID luid2)
{
return (luid1.LowPart == luid2.LowPart) &&
(luid2.HighPart == luid2.HighPart);
}
IT++, C++
Gold::Gold(const ivec &mseq1_connections,
const ivec &mseq2_connections)
{
....
it_assert(mseq1.get_length() == mseq1.get_length(),
"Gold::Gold(): dimension mismatch");
}
QuantLib, C++
Distribution ManipulateDistribution::convolve(
const Distribution& d1, const Distribution& d2) {
....
QL_REQUIRE (d1.xmin_ == 0.0 && d1.xmin_ == 0.0,
"distributions offset larger than 0");
....
}
Samba project, C++
static bool samu_correct(struct samu *s1, struct samu *s2)
{
....
} else if (s1_len != s1_len) {
DEBUG(0, ("Password history not written correctly, "
"lengths differ, want %d, got %dn",
s1_len, s2_len));
....
}
Mozilla Firefox project, C++
static PRBool IsZPositionLEQ(nsDisplayItem* aItem1,
nsDisplayItem* aItem2,
void* aClosure) {
if (!aItem1->GetUnderlyingFrame()->Preserves3D() ||
!aItem1->GetUnderlyingFrame()->Preserves3D()) {
return IsContentLEQ(aItem1, aItem2, aClosure);
}
....
}
Haiku Operation System, C++
void trans_double_path::reset()
{
m_src_vertices1.remove_all();
m_src_vertices2.remove_all();
m_kindex1 = 0.0; // <=
m_kindex1 = 0.0; // <=
m_status1 = initial;
m_status2 = initial;
}
Qt project, C++
Ok, now let's get to more complicated cases. Try to find the error here just for the sake of interest:
static ShiftResult shift(....)
{
....
qreal l = (orig->x1 - orig->x2)*(orig->x1 - orig->x2) +
(orig->y1 - orig->y2)*(orig->y1 - orig->y1) *
(orig->x3 - orig->x4)*(orig->x3 - orig->x4) +
(orig->y3 - orig->y4)*(orig->y3 - orig->y4);
....
}
Here's the picture not to see the answer straight away so that you got a chance to think about the fragment.
Right, orig->y1 - orig->y2 has to be written instead of orig->y1 - orig->y1.
.NET Compiler Platform project, C#
public void IndexerMemberRace()
{
....
for (int i = 0; i < 20; i++)
{
....
if (i % 2 == 0)
{
thread1.Start();
thread2.Start();
}
else
{
thread1.Start();
thread2.Start();
}
....
}
....
}
That's an interesting case. For testing purposes, you want to run threads in a different order. However, due
to a typo, threads always start in the same way, so the test checks less than it should.
The correct version:
if (i % 2 == 0)
{
thread1.Start();
thread2.Start();
}
else
{
thread2.Start();
thread1.Start();
}
Samba project, C
static int compare_procids(const void *p1, const void *p2)
{
const struct server_id *i1 = (struct server_id *)p1;
const struct server_id *i2 = (struct server_id *)p2;
if (i1->pid < i2->pid) return -1;
if (i2->pid > i2->pid) return 1;
return 0;
}
The comparison function will never return 1, as the i2->pid > i2->pid condition is pointless.
Naturally, that's a trivial typo, in fact, the following has to be written:
if (i1->pid > i2->pid) return 1;
ChakraCore project, C++
The last case in this section. Yippee!
bool Lowerer::GenerateFastBrSrEq(....,
IR::RegOpnd * srcReg1,
IR::RegOpnd * srcReg2,
....)
{
....
else if (srcReg1 && (srcReg1->m_sym->m_isStrConst))
....
else if (srcReg1 && (srcReg1->m_sym->m_isStrConst))
....
}
Miscellaneous errors
Now I'm going to mention error patterns related to 0, 1, 2 usage with fewer examples.
Typos in conditions with the explicit usage of the constant 0/1/2
ROOT project, C++
Int_t TProofMonSenderML::SendSummary(TList *recs, const char *id)
{
....
if (fSummaryVrs == 0) {
if ((dsn = recs->FindObject("dataset"))) recs->Remove(dsn);
} else if (fSummaryVrs == 0) {
....
}
It's strange to compare the fSummaryVrs variable with 0 twice.
.NET CoreCLR, C#
void PutIA64Imm22(UINT64 * pBundle, UINT32 slot, INT32 imm22)
{
if (slot == 0) // <=
{
....
}
else if (slot == 1)
{
....
}
else if (slot == 0) // <=
{
....
}
....
}
FFmpeg project, C
static int imc_decode_block(....)
{
....
if (stream_format_code & 0x1)
imc_decode_level_coefficients_raw(....);
else if (stream_format_code & 0x1)
imc_read_level_coeffs_raw(....);
....
}
Index / name
Previously, we have looked at cases where an index or a name is incorrect. And here is a situation where
you can't immediately tell how to classify the error. This example could be attributed to both chapters. So I
decided to bring it separately.
Mesa 3D Graphics Library project, C++
bool
ir_algebraic_visitor::reassociate_constant(....)
{
....
if (ir1->operands[0]->type->is_matrix() ||
ir1->operands[0]->type->is_matrix() ||
ir2->operands[1]->type->is_matrix() ||
ir2->operands[1]->type->is_matrix())
return false;
....
}
This code can be fixed as follows:
if (ir1->operands[0]->type->is_matrix() ||
ir1->operands[1]->type->is_matrix() ||
ir2->operands[0]->type->is_matrix() ||
ir2->operands[1]->type->is_matrix())
As well as in this way:
if (ir1->operands[0]->type->is_matrix() ||
ir2->operands[0]->type->is_matrix() ||
ir1->operands[1]->type->is_matrix() ||
ir2->operands[1]->type->is_matrix())
Redundant 0
Sometimes 0 is superfluous and harmful. Because of it, the number can turn into an octal one, in the place
where it shouldn't. Or spoil the format string.
These errors aren't suitable for this article, but I think they are worth mentioning. I won't give you the code
with these errors in the article, but if you're interested, you can check them out here:
• V536 Be advised that the utilized constant value is represented by an octal form, examples;
• V638 A terminal null is present inside a string. The '0xNN' characters were encountered. Probably
meant: 'xNN', examples.
Forgot to write +1
Haiku Operation System, C++
int
UserlandFS::KernelEmu::new_path(const char *path, char **copy)
{
....
// append a dot, if desired
if (appendDot) {
copiedPath[len] = '.';
copiedPath[len] = '0';
}
....
}
Here is the correct version:
copiedPath[len] = '.';
copiedPath[len + 1] = '0';
Note. The case when one forgets to add 1 is not uncommon. I remember exactly that I have seen such cases
quite often. However, when I wanted to collect such examples for an article, I found only this example. I'm
sorry I can't scare you with more errors. I do apologize.
Formatting errors (C#)
Most often functions for building strings operate with a small number of arguments. So it turns out that
errors relate to the usage of {0}, {1} or {2}.
Azure PowerShell project, C#
protected override void ProcessRecordInternal()
{
....
if (this.ShouldProcess(this.Name,
string.Format("Creating Log Alert Rule '{0}' in resource group {0}",
this.Name, this.ResourceGroupName)))
{
....
}
....
}
The author made a typo and wrote {0} twice. As a result, the this.Name name will be inserted in the string
twice. As for the this.ResourceGroupName name, it won't get into the created string.
Mono project, C#
void ReadEntropy ()
{
if (reader.IsEmptyElement)
throw new XmlException (
String.Format ("WS-Trust Entropy element is empty.{2}",
LineInfo ()));
....
}
The above code is so weird. You are supposed to insert something that doesn't exist. Most likely, this code
underwent failed refactoring and the logic was violated.
Xenko project, C#
public string ToString(string format,
IFormatProvider formatProvider)
{
if (format == null)
return ToString(formatProvider);
return string.Format(
formatProvider,
"Red:{1} Green:{2} Blue:{3}",
R.ToString(format, formatProvider),
G.ToString(format, formatProvider),
B.ToString(format, formatProvider));
}
The programmer forgot that the numbering begins with {0}, not with {1}. Correct code:
return string.Format(
formatProvider,
"Red:{0} Green:{1} Blue:{2}",
R.ToString(format, formatProvider),
G.ToString(format, formatProvider),
B.ToString(format, formatProvider));
.NET Compiler Platform project, C#
private void DumpAttributes(Symbol s)
{
....
Console.WriteLine("{0} {1} {2}", pa.ToString());
....
}
Arguments are clearly not enough.
Conclusions and recommendations
I had to demonstrate quite many examples to show that typos related to 0, 1, and 2 deserve special
attention.
If I'd just said: "It's easy to confuse o1 and o2", you would have agreed, but wouldn't have given it special
attention, as you're giving it now after reading or at least looking through the article.
Now you're forewarned, and that's good. Forewarned is forearmed. From now on you'll be more attentive
during code reviews and will pay extra attention to variables with 0, 1, 2 in the names.
It's difficult to give certain recommendations on code formatting so as to avoid the above errors. As you
have seen, errors occur even in such simple code, where there is actually nothing to format.
Therefore, I won't call to avoid 0, 1, 2 and give variables long names. If you start writing
First/Second/Left/Right instead of numbers, then the temptation to copy the name or expression will be
even greater. Perhaps this recommendation won't ultimately reduce, but will increase the number of errors.
However, when you write a lot of similar code, the recommendation of "table code formatting" is still
relevant. Table formatting doesn't guarantee the absence of typos, but helps to notice them more easily
and faster. See the chapter 13 from the mini-book "The Ultimate Question of Programming, Refactoring,
and Everything" for more details.
There's another piece of good news. All of the errors discussed in this article are detected by the PVS-Studio
static code analyzer. Accordingly, by introducing static analysis tools into the development process, you will
be able to identify many typos at the earliest stage.
Thank you for your attention. I hope you were interested and scared. I wish you reliable code and less errors
with 0, 1, 2, so that Freddy didn't come to you.

More Related Content

What's hot

What's hot (20)

Rechecking TortoiseSVN with the PVS-Studio Code Analyzer
Rechecking TortoiseSVN with the PVS-Studio Code AnalyzerRechecking TortoiseSVN with the PVS-Studio Code Analyzer
Rechecking TortoiseSVN with the PVS-Studio Code Analyzer
 
Static code analysis: what? how? why?
Static code analysis: what? how? why?Static code analysis: what? how? why?
Static code analysis: what? how? why?
 
PVS-Studio vs Chromium
PVS-Studio vs ChromiumPVS-Studio vs Chromium
PVS-Studio vs Chromium
 
PVS-Studio vs Chromium
PVS-Studio vs ChromiumPVS-Studio vs Chromium
PVS-Studio vs Chromium
 
Tesseract. Recognizing Errors in Recognition Software
Tesseract. Recognizing Errors in Recognition SoftwareTesseract. Recognizing Errors in Recognition Software
Tesseract. Recognizing Errors in Recognition Software
 
Analyzing the Blender project with PVS-Studio
Analyzing the Blender project with PVS-StudioAnalyzing the Blender project with PVS-Studio
Analyzing the Blender project with PVS-Studio
 
Miranda NG Project to Get the "Wild Pointers" Award (Part 1)
Miranda NG Project to Get the "Wild Pointers" Award (Part 1) Miranda NG Project to Get the "Wild Pointers" Award (Part 1)
Miranda NG Project to Get the "Wild Pointers" Award (Part 1)
 
Checking OpenCV with PVS-Studio
Checking OpenCV with PVS-StudioChecking OpenCV with PVS-Studio
Checking OpenCV with PVS-Studio
 
The Unicorn Getting Interested in KDE
The Unicorn Getting Interested in KDEThe Unicorn Getting Interested in KDE
The Unicorn Getting Interested in KDE
 
Checking the Cross-Platform Framework Cocos2d-x
Checking the Cross-Platform Framework Cocos2d-xChecking the Cross-Platform Framework Cocos2d-x
Checking the Cross-Platform Framework Cocos2d-x
 
64-Bit Code in 2015: New in the Diagnostics of Possible Issues
64-Bit Code in 2015: New in the Diagnostics of Possible Issues64-Bit Code in 2015: New in the Diagnostics of Possible Issues
64-Bit Code in 2015: New in the Diagnostics of Possible Issues
 
Intel IPP Samples for Windows - error correction
Intel IPP Samples for Windows - error correctionIntel IPP Samples for Windows - error correction
Intel IPP Samples for Windows - error correction
 
Date Processing Attracts Bugs or 77 Defects in Qt 6
Date Processing Attracts Bugs or 77 Defects in Qt 6Date Processing Attracts Bugs or 77 Defects in Qt 6
Date Processing Attracts Bugs or 77 Defects in Qt 6
 
Top 10 C# projects errors found in 2016
Top 10 C# projects errors found in 2016Top 10 C# projects errors found in 2016
Top 10 C# projects errors found in 2016
 
Picking Mushrooms after Cppcheck
Picking Mushrooms after CppcheckPicking Mushrooms after Cppcheck
Picking Mushrooms after Cppcheck
 
Re-checking the ReactOS project - a large report
Re-checking the ReactOS project - a large reportRe-checking the ReactOS project - a large report
Re-checking the ReactOS project - a large report
 
ChakraCore: analysis of JavaScript-engine for Microsoft Edge
ChakraCore: analysis of JavaScript-engine for Microsoft EdgeChakraCore: analysis of JavaScript-engine for Microsoft Edge
ChakraCore: analysis of JavaScript-engine for Microsoft Edge
 
Checking 7-Zip with PVS-Studio analyzer
Checking 7-Zip with PVS-Studio analyzerChecking 7-Zip with PVS-Studio analyzer
Checking 7-Zip with PVS-Studio analyzer
 
The operation principles of PVS-Studio static code analyzer
The operation principles of PVS-Studio static code analyzerThe operation principles of PVS-Studio static code analyzer
The operation principles of PVS-Studio static code analyzer
 
PVS-Studio 5.00, a solution for developers of modern resource-intensive appl...
PVS-Studio 5.00, a solution for developers of modern resource-intensive appl...PVS-Studio 5.00, a solution for developers of modern resource-intensive appl...
PVS-Studio 5.00, a solution for developers of modern resource-intensive appl...
 

Similar to Zero, one, two, Freddy's coming for you

Similar to Zero, one, two, Freddy's coming for you (20)

LibRaw, Coverity SCAN, PVS-Studio
LibRaw, Coverity SCAN, PVS-StudioLibRaw, Coverity SCAN, PVS-Studio
LibRaw, Coverity SCAN, PVS-Studio
 
Consequences of using the Copy-Paste method in C++ programming and how to dea...
Consequences of using the Copy-Paste method in C++ programming and how to dea...Consequences of using the Copy-Paste method in C++ programming and how to dea...
Consequences of using the Copy-Paste method in C++ programming and how to dea...
 
The Ultimate Question of Programming, Refactoring, and Everything
The Ultimate Question of Programming, Refactoring, and EverythingThe Ultimate Question of Programming, Refactoring, and Everything
The Ultimate Question of Programming, Refactoring, and Everything
 
The Ultimate Question of Programming, Refactoring, and Everything
The Ultimate Question of Programming, Refactoring, and EverythingThe Ultimate Question of Programming, Refactoring, and Everything
The Ultimate Question of Programming, Refactoring, and Everything
 
PVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's codePVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's code
 
PVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's codePVS-Studio: analyzing ReactOS's code
PVS-Studio: analyzing ReactOS's code
 
Archeology for Entertainment, or Checking Microsoft Word 1.1a with PVS-Studio
Archeology for Entertainment, or Checking Microsoft Word 1.1a with PVS-StudioArcheology for Entertainment, or Checking Microsoft Word 1.1a with PVS-Studio
Archeology for Entertainment, or Checking Microsoft Word 1.1a with PVS-Studio
 
Checking Clang 11 with PVS-Studio
Checking Clang 11 with PVS-StudioChecking Clang 11 with PVS-Studio
Checking Clang 11 with PVS-Studio
 
Of complicacy of programming, or won't C# save us?
Of complicacy of programming, or won't C# save us?Of complicacy of programming, or won't C# save us?
Of complicacy of programming, or won't C# save us?
 
Analysis of Godot Engine's Source Code
Analysis of Godot Engine's Source CodeAnalysis of Godot Engine's Source Code
Analysis of Godot Engine's Source Code
 
The First C# Project Analyzed
The First C# Project AnalyzedThe First C# Project Analyzed
The First C# Project Analyzed
 
An Experiment with Checking the glibc Library
An Experiment with Checking the glibc LibraryAn Experiment with Checking the glibc Library
An Experiment with Checking the glibc Library
 
How to avoid bugs using modern C++
How to avoid bugs using modern C++How to avoid bugs using modern C++
How to avoid bugs using modern C++
 
Checking the code of Valgrind dynamic analyzer by a static analyzer
Checking the code of Valgrind dynamic analyzer by a static analyzerChecking the code of Valgrind dynamic analyzer by a static analyzer
Checking the code of Valgrind dynamic analyzer by a static analyzer
 
CppCat Static Analyzer Review
CppCat Static Analyzer ReviewCppCat Static Analyzer Review
CppCat Static Analyzer Review
 
PVS-Studio. Static code analyzer. Windows/Linux, C/C++/C#. 2017
PVS-Studio. Static code analyzer. Windows/Linux, C/C++/C#. 2017PVS-Studio. Static code analyzer. Windows/Linux, C/C++/C#. 2017
PVS-Studio. Static code analyzer. Windows/Linux, C/C++/C#. 2017
 
Analyzing the Dolphin-emu project
Analyzing the Dolphin-emu projectAnalyzing the Dolphin-emu project
Analyzing the Dolphin-emu project
 
Accord.Net: Looking for a Bug that Could Help Machines Conquer Humankind
Accord.Net: Looking for a Bug that Could Help Machines Conquer HumankindAccord.Net: Looking for a Bug that Could Help Machines Conquer Humankind
Accord.Net: Looking for a Bug that Could Help Machines Conquer Humankind
 
PVS-Studio for Linux Went on a Tour Around Disney
PVS-Studio for Linux Went on a Tour Around DisneyPVS-Studio for Linux Went on a Tour Around Disney
PVS-Studio for Linux Went on a Tour Around Disney
 
PVS-Studio vs Chromium. 3-rd Check
PVS-Studio vs Chromium. 3-rd CheckPVS-Studio vs Chromium. 3-rd Check
PVS-Studio vs Chromium. 3-rd Check
 

More from Andrey Karpov

More from Andrey Karpov (20)

60 антипаттернов для С++ программиста
60 антипаттернов для С++ программиста60 антипаттернов для С++ программиста
60 антипаттернов для С++ программиста
 
60 terrible tips for a C++ developer
60 terrible tips for a C++ developer60 terrible tips for a C++ developer
60 terrible tips for a C++ developer
 
Ошибки, которые сложно заметить на code review, но которые находятся статичес...
Ошибки, которые сложно заметить на code review, но которые находятся статичес...Ошибки, которые сложно заметить на code review, но которые находятся статичес...
Ошибки, которые сложно заметить на code review, но которые находятся статичес...
 
PVS-Studio in 2021 - Error Examples
PVS-Studio in 2021 - Error ExamplesPVS-Studio in 2021 - Error Examples
PVS-Studio in 2021 - Error Examples
 
PVS-Studio in 2021 - Feature Overview
PVS-Studio in 2021 - Feature OverviewPVS-Studio in 2021 - Feature Overview
PVS-Studio in 2021 - Feature Overview
 
PVS-Studio в 2021 - Примеры ошибок
PVS-Studio в 2021 - Примеры ошибокPVS-Studio в 2021 - Примеры ошибок
PVS-Studio в 2021 - Примеры ошибок
 
PVS-Studio в 2021
PVS-Studio в 2021PVS-Studio в 2021
PVS-Studio в 2021
 
Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
Make Your and Other Programmer’s Life Easier with Static Analysis (Unreal Eng...
 
Best Bugs from Games: Fellow Programmers' Mistakes
Best Bugs from Games: Fellow Programmers' MistakesBest Bugs from Games: Fellow Programmers' Mistakes
Best Bugs from Games: Fellow Programmers' Mistakes
 
Does static analysis need machine learning?
Does static analysis need machine learning?Does static analysis need machine learning?
Does static analysis need machine learning?
 
Typical errors in code on the example of C++, C#, and Java
Typical errors in code on the example of C++, C#, and JavaTypical errors in code on the example of C++, C#, and Java
Typical errors in code on the example of C++, C#, and Java
 
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
How to Fix Hundreds of Bugs in Legacy Code and Not Die (Unreal Engine 4)
 
Game Engine Code Quality: Is Everything Really That Bad?
Game Engine Code Quality: Is Everything Really That Bad?Game Engine Code Quality: Is Everything Really That Bad?
Game Engine Code Quality: Is Everything Really That Bad?
 
C++ Code as Seen by a Hypercritical Reviewer
C++ Code as Seen by a Hypercritical ReviewerC++ Code as Seen by a Hypercritical Reviewer
C++ Code as Seen by a Hypercritical Reviewer
 
The Use of Static Code Analysis When Teaching or Developing Open-Source Software
The Use of Static Code Analysis When Teaching or Developing Open-Source SoftwareThe Use of Static Code Analysis When Teaching or Developing Open-Source Software
The Use of Static Code Analysis When Teaching or Developing Open-Source Software
 
Static Code Analysis for Projects, Built on Unreal Engine
Static Code Analysis for Projects, Built on Unreal EngineStatic Code Analysis for Projects, Built on Unreal Engine
Static Code Analysis for Projects, Built on Unreal Engine
 
Safety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
Safety on the Max: How to Write Reliable C/C++ Code for Embedded SystemsSafety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
Safety on the Max: How to Write Reliable C/C++ Code for Embedded Systems
 
The Great and Mighty C++
The Great and Mighty C++The Great and Mighty C++
The Great and Mighty C++
 
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOpsPVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
PVS-Studio Is Now in Chocolatey: Checking Chocolatey under Azure DevOps
 
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
PVS-Studio Static Analyzer as a Tool for Protection against Zero-Day Vulnerab...
 

Recently uploaded

CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
9953056974 Low Rate Call Girls In Saket, Delhi NCR
 

Recently uploaded (20)

Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
call girls in Vaishali (Ghaziabad) 🔝 >༒8448380779 🔝 genuine Escort Service 🔝✔️✔️
 
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS LiveVip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
Vip Call Girls Noida ➡️ Delhi ➡️ 9999965857 No Advance 24HRS Live
 
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
Tech Tuesday-Harness the Power of Effective Resource Planning with OnePlan’s ...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected WorkerHow To Troubleshoot Collaboration Apps for the Modern Connected Worker
How To Troubleshoot Collaboration Apps for the Modern Connected Worker
 
Right Money Management App For Your Financial Goals
Right Money Management App For Your Financial GoalsRight Money Management App For Your Financial Goals
Right Money Management App For Your Financial Goals
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Microsoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdfMicrosoft AI Transformation Partner Playbook.pdf
Microsoft AI Transformation Partner Playbook.pdf
 
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICECHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
CHEAP Call Girls in Pushp Vihar (-DELHI )🔝 9953056974🔝(=)/CALL GIRLS SERVICE
 
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdfLearn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
Learn the Fundamentals of XCUITest Framework_ A Beginner's Guide.pdf
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
Hand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptxHand gesture recognition PROJECT PPT.pptx
Hand gesture recognition PROJECT PPT.pptx
 
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
W01_panagenda_Navigating-the-Future-with-The-Hitchhikers-Guide-to-Notes-and-D...
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
How To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.jsHow To Use Server-Side Rendering with Nuxt.js
How To Use Server-Side Rendering with Nuxt.js
 
A Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docxA Secure and Reliable Document Management System is Essential.docx
A Secure and Reliable Document Management System is Essential.docx
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 

Zero, one, two, Freddy's coming for you

  • 1. Zero, one, two, Freddy's coming for you Author: Andrey Karpov Date: 14.02.2020 Tags: Cpp, Java, CSharp, Knowledge This post continues the series of articles, which can well be called "horrors for developers". This time it will also touch upon a typical pattern of typos related to the usage of numbers 0, 1, 2. The language you're writing in doesn't really matter: it can be C, C++, C#, or Java. If you're using constants 0, 1, 2 or variables' names contain these numbers, most likely, Freddy will come to visit you at night. Go on, read and don't say we didn't warn you. Introduction I continue the series of articles on the patterns noticed of how people make mistakes. Previous posts: 1. Last Line Effect 2. The most dangerous function in the C/C++ world 3. The Evil within the Comparison Functions This time it wasn't me who took note of the pattern, but my colleague Svyatoslav Razmyslov. He noticed that in his articles he was constantly describing problems involving variables with numbers 1 and 2 in their names. Svyatoslav invited me to explore this issue in more detail. Eventually, efforts made ended up very fruitful. It emerged that in our error collection there are a lot of code fragments that are erroneous because of the fact that people got confused in 0, 1, 2 indexes or variables names, containing such numbers. A new interesting pattern has been revealed, which will be discussed below. I am grateful to Svyatoslav for a hint to look into this topic and, therefore, I dedicate this article to him.
  • 2. Svyatoslav Razmyslov, manager, attentive bug hunter and just a talented person. What is the purpose of this article? To show how easy it is for all of us to make mistakes and make typos. Forewarned developers - more attentive developers. Especially during code reviews when they focus on these ill-fated 0, 1, 2. Developers will also be able to appreciate the contribution of static code analyzers that help to spot such errors. It's not about advertising PVS-Studio (well, to some extent, it is :). Until now, many developers consider static analysis superfluous, preferring to focus on their own accuracy and code reviews. Unfortunately, attempts to write clean code are laudable but not enough. This article will once again convincingly demonstrate this. No one is immune to errors. Below you will see epic blunders in even such well-known projects as Qt, Clang, Hive, LibreOffice, Linux Kernel, .NET Compiler Platform, XNU kernel, Mozilla Firefox. By the way, these are not some exotic rare mistakes, but the most common ones. Still not convincing enough? Then let's get going! "Talk is cheap. Show me bugs!" (c) remade quote by Linus Torvalds. Typos in constants when indexing arrays Usually in our articles we cite warnings that helped to find certain errors. This time I'll omit these warnings, as even without them errors will still be obvious and clear. Even though these bugs leap out in a short code fragment, they are great at hiding in projects' code. Let's start with confusions with numerical literals, used for arrays indexing. Despite the banality of these errors, they are many and they can be found in projects that are much greater than students' laboratory researches. XNU kernel project, C uint32_t gss_krb5_3des_unwrap_mbuf(....)
  • 3. { .... for (cflag = 1; cflag >= 0; cflag--) { *minor = gss_krb5_3des_token_get( ctx, &itoken, wrap, &hash, &offset, &length, reverse); if (*minor == 0) break; wrap.Seal_Alg[0] = 0xff; wrap.Seal_Alg[0] = 0xff; } .... } The line was copied, but the index stayed the same. Most likely, the code here is supposed to be as follows: wrap.Seal_Alg[0] = 0xff; wrap.Seal_Alg[1] = 0xff; LibreOffice project, C++ Sequence< OUString > FirebirdDriver:: getSupportedServiceNames_Static() throw (RuntimeException) { Sequence< OUString > aSNS( 2 ); aSNS[0] = "com.sun.star.sdbc.Driver"; aSNS[0] = "com.sun.star.sdbcx.Driver"; return aSNS; } As in the previous case, the authors copied the line, but forgot to change 0 for 1. Only fixed the string literal. One might ask the philosophical question - how can you make such an error in a four-line function? You can and that's it. That's what programming is like. Quake-III-Arena project, C int VL_FindAdjacentSurface(....) { .... if (fabs(dir[0]) > test->radius || fabs(dir[1]) > test->radius || fabs(dir[1]) > test->radius) { .... } The developer forgot to change dir[1] for dir[2] in the copied line. As a result - the value on Z axis is out of control. OpenCOLLADA project, C++ struct short2 { short values[2];
  • 4. short2(short s1, short s2) { values[0] = s1; values[2] = s2; } .... }; Yes, even in such a short constructor one can find a way to be out of array bounds during its initialization. Godot Engine, C++ Array PhysicsDirectSpaceState::_cast_motion(....) { .... Array ret(true); ret.resize(2); ret[0]=closest_safe; ret[0]=closest_unsafe; return ret; } No comment is needed. Asterisk, C static void sip_threadinfo_destructor(void *obj) { struct sip_threadinfo *th = obj; struct tcptls_packet *packet; if (th->alert_pipe[1] > -1) { // <= close(th->alert_pipe[0]); } if (th->alert_pipe[1] > -1) {
  • 5. close(th->alert_pipe[1]); } th->alert_pipe[0] = th->alert_pipe[1] = -1; .... } When writing similar blocks, an error is usually in the last one. All above cases were like this, except for the last one. Here the typo is in an unusual place, namely, in the first block. It's hard to say why it happened so. I'll just leave the picture of a unicorn shrugging his shoulders: Open CASCADE Technology, C++ inline void Prepend(const Standard_Integer theIndex) { if (myIndex[1] >= 0) Standard_OutOfRange::Raise ("BRepMesh_PairOfIndex...."); myIndex[1] = myIndex[0]; myIndex[1] = theIndex; } Different values are copied twice in the same array slot. Obviously, it's an error. The project code is unfamiliar to me, so it's not clear how to fix this bug. So I just looked at how the developers fixed the code after our team pointed out this error to them. Here is the correct version: myIndex[1] = myIndex[0]; myIndex[0] = theIndex; Trans-Proteomic Pipeline, C++ void ASAPRatio_getProDataStrct(proDataStrct *data,
  • 6. char **pepBofFiles) { .... if (data->indx == -1) { data->ratio[0] = -2.; data->ratio[0] = 0.; // <= data->inv_ratio[0] = -2.; data->inv_ratio[1] = 0.; return; } .... } I'm concerned that such errors take place in research packages. Trans-Proteomic Pipeline is designed to handle the tasks in biology. One might make a real mess of things and screw up the entire research. We found many intriguing things in this package: check in 2012, check in 2013. Perhaps, we should take another look at this project. ITK project, C++ Here is another project for medical research: Medicine Insight Segmentation and Registration Toolkit (ITK). The project is different, and the bugs are the same. template< typename TCoordRepType > void VoronoiDiagram2D< TCoordRepType >::SetOrigin(PointType vorsize) { m_VoronoiBoundaryOrigin[0] = vorsize[0]; m_VoronoiBoundaryOrigin[0] = vorsize[1]; } ITK project, C++ int itkPointSetToSpatialObjectDemonsRegistrationTest(....) { .... // Set its position EllipseType::TransformType::OffsetType offset; offset[0]=50; offset[1]=50; offset[1]=50; .... } Copy-Paste at its finest. ReactOS project, C++ HPALETTE CardWindow::CreateCardPalette() { .... //include button text colours
  • 7. cols[0] = RGB(0, 0, 0); cols[1] = RGB(255, 255, 255); //include the base background colour cols[1] = crBackgnd; //include the standard button colours... cols[3] = CardButton::GetHighlight(crBackgnd); cols[4] = CardButton::GetShadow(crBackgnd); cols[5] = CardButton::GetFace(crBackgnd); .... } Apparently, the crBackgnd constant had to be written in the cols[2] slot. Coin3D project, C++ SoVRMLInline::GLRender(SoGLRenderAction * action) { .... if ((size[0] >= 0.0f && size[1] >= 0.0f && size[1] >= 0.0f) && ((vis == ALWAYS) || (vis == UNTIL_LOADED && child == NULL))) { .... } The size[1] array element is checked twice, whereas the size[2] element isn't checked at all. That's how strange artifacts appear in the images. OpenCV project, C++ bool Jpeg2KDecoder::readHeader() { .... cmptlut[0] = .... cmptlut[1] = .... cmptlut[2] = .... if( cmptlut[0] < 0 || cmptlut[1] < 0 || cmptlut[0] < 0 ) result = false; .... } My gut is telling me that the cmptlut[0] < 0 expression was copied twice, but 0 was changed just once. Visualization Toolkit (VTK) project, C++ void vtkImageStencilRaster::PrepareForNewData(....) { .... if (allocateExtent && allocateExtent[1] >= allocateExtent[1]) .... }
  • 8. In this case and later I won't comment many similar errors. Why comment? The main thing when looking through such code fragments is to become aware of the fact that even if the error is simple it doesn't mean a developer will definitely notice it. Visualization Toolkit (VTK) project, C++ template <class iterT> void vtkDataSetAttributesCopyValues(....) { .... inZPtr += (outExt[0] - outExt[0])*inIncs[0] * data_type_size + (outExt[2] - outExt[2])*inIncs[1] * data_type_size + (outExt[4] - outExt[4])*inIncs[2] * data_type_size; .... } Here the programmer was clearly in a hurry and wrote the code very quickly. It's hard to explain how he made a mistake three times. The elements of the array are subtracted from themselves. The result is that this code equals the following: inZPtr += (0)*inIncs[0] * data_type_size + (0)*inIncs[1] * data_type_size + (0)*inIncs[2] * data_type_size; However, this code can be shortened even more: inZPtr += 0; Just great. There's a long, serious-looking expression in the code that doesn't really do anything. I just love such cases. Visualization Toolkit (VTK) project, C++ A similar case of hasty coding. void vtkPiecewiseControlPointsItem::SetControlPoint( vtkIdType index, double* newPos) { double oldPos[4]; this->PiecewiseFunction->GetNodeValue(index, oldPos); if (newPos[0] != oldPos[0] || newPos[1] != oldPos[1] || newPos[2] != oldPos[2] || newPos[2] != oldPos[2]) { this->PiecewiseFunction->SetNodeValue(index, newPos); } } The newPos[2] != oldPos[2] comparison repeats twice. ADAPTIVE Communication Environment (ACE), C++ bool URL_Base::strip_scheme (ACE_CString& url_string) {
  • 9. .... ACE_CString::size_type pos = url_string.find (':'); if (pos > 0 && url_string[pos+1] == '/' && url_string[pos+1] == '/') { .... // skip '<protocol>://' url_string = url_string.substr (pos+3); } .... } The condition should check that there are two slashes after the colon. In other words, we look for the substring "://". Due to a typo, the check gets blinded and considers any character as a second slash. IPP Samples, C++ void MeBase::MakeVlcTableDecision() { .... Ipp32s BestMV = IPP_MIN(IPP_MIN(m_cur.MvRate[0],m_cur.MvRate[1]), IPP_MIN(m_cur.MvRate[2],m_cur.MvRate[3])); Ipp32s BestAC = IPP_MIN(IPP_MIN(m_cur.AcRate[0],m_cur.AcRate[1]), IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2])); .... } The typo lies here in the macro arguments: IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2]) As a result, the minimum value is chosen from two equal ones. In fact, the following should be written: IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[3]) By the way, this code might demonstrate the benefit of the standard library. If we write in the following way: Ipp32s BestMV = std::min_element(begin(m_cur.MvRate), end(m_cur.MvRate)); Ipp32s BestAC = std::min_element(begin(m_cur.AcRate), end(m_cur.AcRate)); The code will be shorter and less prone to errors. Actually, the less of the same-type code, the more likely it is to be written correctly. Audacity, C++ sampleCount VoiceKey::OnBackward (....) { .... int atrend = sgn(buffer[samplesleft - 2]- buffer[samplesleft - 1]); int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
  • 10. buffer[samplesleft - WindowSizeInt-2]); .... } Correct expression: int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]- buffer[samplesleft - WindowSizeInt-1]); PDFium, C++ void sycc420_to_rgb(opj_image_t* img) { .... opj_image_data_free(img->comps[0].data); opj_image_data_free(img->comps[1].data); opj_image_data_free(img->comps[2].data); img->comps[0].data = d0; img->comps[1].data = d1; img->comps[2].data = d2; img->comps[1].w = yw; // 1 img->comps[1].h = yh; // 1 img->comps[2].w = yw; // 1 img->comps[2].h = yh; // 1 img->comps[1].w = yw; // 2 img->comps[1].h = yh; // 2 img->comps[2].w = yw; // 2 img->comps[2].h = yh; // 2 img->comps[1].dx = img->comps[0].dx; img->comps[2].dx = img->comps[0].dx; img->comps[1].dy = img->comps[0].dy; img->comps[2].dy = img->comps[0].dy; } Some actions aimed on initializing the structure repeat. Lines with the comment //2 can be removed without changing anything. I doubted about adding this code fragment in the article. It's not exactly an error, and not quite with indexes. Nevertheless, this redundant code has probably appeared here right because of the fact that the programmer got confused in all these class members and 1, 2 indexes. So I think this piece of code is great to demonstrate how easy it is to get confused in numbers. CMake project, C The code next up isn't written by CMake developers, but borrowed. As the comment says at the beginning of the file, the utf8_encode function was written by Tim Kientzle back in 2007. Since then, this function roams from project to project and can be met in many places. I didn't dig into the initial source, as it's not the matter of importance. Once the CMake project includes this code, the error applies to CMake as well. static char * utf8_encode(const wchar_t *wval) { .... p[0] = 0xfc | ((wc >> 30) & 0x01); p[1] = 0x80 | ((wc >> 24) & 0x3f);
  • 11. p[1] = 0x80 | ((wc >> 18) & 0x3f); p[2] = 0x80 | ((wc >> 12) & 0x3f); p[3] = 0x80 | ((wc >> 6) & 0x3f); p[4] = 0x80 | (wc & 0x3f); p += 6; .... } As you can see, there is some confusion with the indexes. The value is written twice in the p[1] array element. If you look at the adjacent code, it becomes clear that the correct code should be this: p[0] = 0xfc | ((wc >> 30) & 0x01); p[1] = 0x80 | ((wc >> 24) & 0x3f); p[2] = 0x80 | ((wc >> 18) & 0x3f); p[3] = 0x80 | ((wc >> 12) & 0x3f); p[4] = 0x80 | ((wc >> 6) & 0x3f); p[5] = 0x80 | (wc & 0x3f); p += 6; Note Please, note that all errors in this section relate to the code in C and C++. There is no code in C# or Java! It's very interesting, I didn't expect this. In my opinion, the typos considered don't depend on the language. In the sections below, there will be errors in code, written in other languages. I think it's just a coincidence. The PVS-Studio analyzer has started to support the C#/Java languages much later than C/C++, and we just didn't have enough time to collect the examples of the above errors types. However, this conclusion is still interesting. Apparently, C and C++ programmers are more inclined to use numbers 0, 1, 2 when working with arrays :). Typos in names This will be the largest section. It is very easy for people to get confused in names such as a1 and a2. You might think: "How could you ever get confused here"? You can. And very easily. Now the reader will be able to see it. Hive project, Java @Override public List<ServiceInstance> getAllInstancesOrdered() { List<ServiceInstance> list = new LinkedList<>(); list.addAll(instances.values()); Collections.sort(list, new Comparator<ServiceInstance>() { @Override public int compare(ServiceInstance o1, ServiceInstance o2) { return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity()); } }); return list; } The comparison function compare receives two objects: o1 and o2. But due to the typo only o2 is used.
  • 12. Interestingly, this error has made its way to another function because of Copy-Paste: @Override public List<ServiceInstance> getAllInstancesOrdered() { List<ServiceInstance> list = new LinkedList<>(); readLock.lock(); try { list.addAll(instances.values()); } finally { readLock.unlock(); } Collections.sort(list, new Comparator<ServiceInstance>() { @Override public int compare(ServiceInstance o1, ServiceInstance o2) { return o2.getWorkerIdentity().compareTo(o2.getWorkerIdentity()); } }); return list; } Infer.NET project, C# private void MergeParallelTransitions() { .... if (double.IsInfinity(transition1.Weight.Value) && double.IsInfinity(transition1.Weight.Value)) .... } Doom 3 project, C++ uint AltOp::fixedLength() { uint l1 = exp1->fixedLength(); uint l2 = exp1->fixedLength(); if (l1 != l2 || l1 == ~0u) return ~0; return l1; } If you didn't notice the typo, look at the line, where the l2 variable is initialized. exp2 had to be used.
  • 13. Source Engine SDK project, C++ void GetFPSColor( int nFps, unsigned char ucColor[3] ) { .... int nFPSThreshold1 = 20; int nFPSThreshold2 = 15; if (IsPC() && g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 95) { nFPSThreshold1 = 60; nFPSThreshold1 = 50; } .... } The correct version: nFPSThreshold1 = 60; nFPSThreshold2 = 50; Linux Kernel project, C By the way, in addition to variable names, typos can be in macros' names. Here are a few examples. int private_ioctl(struct vnt_private *pDevice, struct ifreq *rq) { .... if (sStartAPCmd.byBasicRate & BIT3) { pMgmt->abyIBSSSuppRates[2] |= BIT7; pMgmt->abyIBSSSuppRates[3] |= BIT7; pMgmt->abyIBSSSuppRates[4] |= BIT7; pMgmt->abyIBSSSuppRates[5] |= BIT7; } else if (sStartAPCmd.byBasicRate & BIT2) { pMgmt->abyIBSSSuppRates[2] |= BIT7; pMgmt->abyIBSSSuppRates[3] |= BIT7; pMgmt->abyIBSSSuppRates[4] |= BIT7; } else if (sStartAPCmd.byBasicRate & BIT1) { // <= pMgmt->abyIBSSSuppRates[2] |= BIT7; pMgmt->abyIBSSSuppRates[3] |= BIT7; } else if (sStartAPCmd.byBasicRate & BIT1) { // <= pMgmt->abyIBSSSuppRates[2] |= BIT7; } else { /* default 1,2M */ pMgmt->abyIBSSSuppRates[2] |= BIT7; pMgmt->abyIBSSSuppRates[3] |= BIT7; } .... }
  • 14. As you can see, the mask with the BIT1 name is used twice, which makes the second check pointless. The body of the second conditional operator marked by the comment will never execute. CMaNGOS project, C++ void AttackedBy(Unit* pAttacker) override { .... DoScriptText(urand(0, 1) ? SAY_BELNISTRASZ_AGGRO_1 : SAY_BELNISTRASZ_AGGRO_1, m_creature, pAttacker); .... } The project was intended to include random behavior, but the same constant SAY_BELNISTRASZ_AGGRO_1 is chosen every time. Vangers project: One For The Road, C++ const char* iGetJoyBtnNameText(int vkey,int lang) { .... if (vkey >= VK_STICK_SWITCH_1 && vkey <= VK_STICK_SWITCH_9) { ret = (lang) ? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1] : iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]; return ret; } .... } According to the written code, the correct version has to be the following: ret = (lang) ? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1] : iJoystickStickSwitch1[vkey - VK_STICK_SWITCH_1]; RT-Thread project, C uint8_t can_receive_message_length(uint32_t can_periph, uint8_t fifo_number) { uint8_t val = 0U; if(CAN_FIFO0 == fifo_number){ val = (uint8_t)(CAN_RFIFO0(can_periph) & CAN_RFIFO_RFL0_MASK); }else if(CAN_FIFO0 == fifo_number){ val = (uint8_t)(CAN_RFIFO1(can_periph) & CAN_RFIFO_RFL0_MASK); }else{ /* illegal parameter */ }
  • 15. return val; } RT-Thread is a real-time open source OS for embedded devices. Here we see confusion between FIFO 0 and FIFO 1. And somewhere, someone's going to stumble upon a glitchy device. The error is here: if (CAN_FIFO0 == fifo_number){ .... }else if(CAN_FIFO0 == fifo_number){ The second check always gives false. The correct version: if (CAN_FIFO0 == fifo_number){ .... }else if(CAN_FIFO1 == fifo_number){ Hive project, Java private void generateDateTimeArithmeticIntervalYearMonth(String[] tdesc) throws Exception { String operatorName = tdesc[1]; String operatorSymbol = tdesc[2]; String operandType1 = tdesc[3]; String colOrScalar1 = tdesc[4]; String operandType2 = tdesc[5]; String colOrScalar2 = tdesc[6]; .... if (colOrScalar1.equals("Col") && colOrScalar1.equals("Column")) { ....
  • 16. } else if (colOrScalar1.equals("Col") && colOrScalar1.equals("Scalar")) { .... } The PVS-Studio analyzer indicates about 2 errors at once: 1. The string in colOrScalar1 can't be equal to both strings "Col" and "Column"; 2. The string in colOrScalar1 can't be equal to both strings "Col" and "Scalar"; Variable names are definitely muddled up. Shareaza project, C++ void CDownloadWithSources::MergeMetadata(const CXMLElement* pXML) { CQuickLock pLock( Transfers.m_pSection ); CXMLAttribute* pAttr1 = m_pXML->GetAttribute(CXMLAttribute::schemaName); CXMLAttribute* pAttr2 = pXML->GetAttribute(CXMLAttribute::schemaName); if (pAttr1 && pAttr2 && !pAttr1->GetValue().CompareNoCase(pAttr1->GetValue())) .... } The correct version: pAttr1->GetValue().CompareNoCase(pAttr2->GetValue()) Note Let's take a small break. There is concern, that when looking through a bunch of banal mistakes, we will forget why we do it. The goal is not to scorn at someone else's code. All this is not the reason to play blame game and say: "Oh, my goodness, that's stupid!" This is the reason to pause to think! Posts of our team are intended to show that none of us is immune to mistakes. The errors described in the article appear in code much more often than you might expect. It is also important that the probability of getting confused in 0, 1, 2 almost doesn't depend on the programmer's skill. It is useful to realize that people tend to make mistakes. Without this, you can't take the next step in improving the quality and reliability of the code. Realizing that we all might be wrong, people begin to try to identify errors at the earliest stages, using coding standards, code reviews, unit tests, static and dynamic analyzers. That's very good. Then why are we writing about obvious things? Unfortunately, based on numerous conversations with developers, we have to state that it is not always so clear to everyone. Many people have too high self- esteem and they simply don't allow the idea that they are able to make simple mistakes. It's sad. If you are a teamlead/manager, I invite you to read this note. Qt project, C++
  • 17. AtomicComparator::ComparisonResult IntegerComparator::compare(const Item &o1, const AtomicComparator::Operator, const Item &o2) const { const Numeric *const num1 = o1.as<Numeric>(); const Numeric *const num2 = o1.as<Numeric>(); if(num1->isSigned() || num2->isSigned()) .... } The correct version: const Numeric *const num2 = o2.as<Numeric>(); Android project, C++ static inline bool isAudioPlaybackRateEqual( const AudioPlaybackRate &pr1, const AudioPlaybackRate &pr2) { return fabs(pr1.mSpeed - pr2.mSpeed) < AUDIO_TIMESTRETCH_SPEED_MIN_DELTA && fabs(pr1.mPitch - pr2.mPitch) < AUDIO_TIMESTRETCH_PITCH_MIN_DELTA && pr2.mStretchMode == pr2.mStretchMode && pr2.mFallbackMode == pr2.mFallbackMode; } There are two typos at once, due to which, variables pr2.mStretchMode and pr2.mFallbackMode are compared with themselves. Boost project, C++ point3D operator/(const point3D &p1, const point3D &p2) { return point3D(p1.x/p2.x, p1.y/p2.y, p1.z/p1.z); } At the very end, thanks to a typo, the p1.z variable is divided into itself. Clang project, C++ bool haveSameType(QualType Ty1, QualType Ty2) { return (Context.getCanonicalType(Ty1) == Context.getCanonicalType(Ty2) || (Ty2->isIntegerType() && Ty2->isIntegerType())); } Yes, believe it or not, the PVS-Studio analyzer detects such bugs in compilers. The correct version: (Ty1->isIntegerType() &&
  • 18. Ty2->isIntegerType()) Clang project, C++ Instruction *InstCombiner::visitXor(BinaryOperator &I) { .... if (Op0I && Op1I && Op0I->isShift() && Op0I->getOpcode() == Op1I->getOpcode() && Op0I->getOperand(1) == Op1I->getOperand(1) && (Op1I->hasOneUse() || Op1I->hasOneUse())) { .... } The correct version: (Op0I->hasOneUse() || Op1I->hasOneUse()) Qt project, C++ inline bool qCompare(QImage const &t1, QImage const &t2, ....) { .... if (t1.width() != t2.width() || t2.height() != t2.height()) { .... } NCBI Genome Workbench project, C++ static bool s_PCRPrimerSetLess(const CPCRPrimerSet& s1, const CPCRPrimerSet& s2) { if (!s1.IsSet() && s1.IsSet()) { return true; } else if (s1.IsSet() && !s2.IsSet()) { return false; } else if (!s1.IsSet() && !s2.IsSet()) { return false; } else if (s1.Get().size() < s2.Get().size()) { return true; } else if (s1.Get().size() > s2.Get().size()) { return false; } else { ..... } Error in the very first check. It should be like this: if (!s1.IsSet() && s2.IsSet()) { NCBI Genome Workbench project, C++ CRef<CSeq_align> CNWAligner::Run(CScope &scope, const CSeq_loc &loc1, const CSeq_loc &loc2, bool trim_end_gaps) { if ((!loc1.IsInt() && !loc1.IsWhole()) || (!loc1.IsInt() && !loc1.IsWhole())) { NCBI_THROW(CException, eUnknown, "Only whole and interval locations supported"); }
  • 19. .... } The first line of condition was copy-pasted, then the programmer got in a hurry and forgot to replace loc1 with loc2. FlashDevelop project, C# public void SetPrices(....) { UInt32 a0 = _choice.GetPrice0(); UInt32 a1 = _choice.GetPrice1(); UInt32 b0 = a1 + _choice2.GetPrice0(); // <= UInt32 b1 = a1 + _choice2.GetPrice1(); .... } FreeCAD project, C++ inline void insEdgeVec(std::map<int,std::set<int> > &map, int n1, int n2) { if(n1<n2) map[n2].insert(n1); else map[n2].insert(n1); }; Regardless of the condition, one and the same action is executed. It would seem such a simple case. How was it possible to copy the line and not fix it? As you can see, it is possible. LibreOffice project, C++ class SVX_DLLPUBLIC SdrMarkView : public SdrSnapView { .... const Point& GetRef1() const { return maRef1; } const Point& GetRef2() const { return maRef1; } .... }; Classic Copy-Paste bug. The correct version: const Point& GetRef2() const { return maRef2; } LibreOffice project, C++ bool CmpAttr( const SfxPoolItem& rItem1, const SfxPoolItem& rItem2) { .... ::boost::optional<sal_uInt16> oNumOffset1 = static_cast<const SwFmtPageDesc&>(rItem1).GetNumOffset(); ::boost::optional<sal_uInt16> oNumOffset2 = static_cast<const SwFmtPageDesc&>(rItem1).GetNumOffset();
  • 20. .... } And another classic Copy-Paste error :). In one fragment authors changed 1 for 2, but they forgot to do it in the other one. LibreOffice project, C++ XMLTransformerOOoEventMap_Impl::XMLTransformerOOoEventMap_Impl( XMLTransformerEventMapEntry *pInit, XMLTransformerEventMapEntry *pInit2 ) { if( pInit ) AddMap( pInit ); if( pInit ) AddMap( pInit2 ); } Here the mistake isn't about changing 1 for 2, here the author simply forgot to add 2 in the second condition. Perhaps, you now feel a bit tired. Therefore, let's make some tea or coffee and we'll continue to explore the world of 0, 1, and 2 numbers. Geant4 software project, C++ void G4VTwistSurface::GetBoundaryLimit(G4int areacode, G4double limit[]) const { .... if (areacode & sC0Min1Max) { limit[0] = fAxisMin[0]; limit[1] = fAxisMin[1]; } else if (areacode & sC0Max1Min) { limit[0] = fAxisMax[0]; limit[1] = fAxisMin[1]; } else if (areacode & sC0Max1Max) { limit[0] = fAxisMax[0]; limit[1] = fAxisMax[1]; } else if (areacode & sC0Min1Max) { limit[0] = fAxisMin[0];
  • 21. limit[1] = fAxisMax[1]; } .... } I hope you took the advice and had some rest. Are you ready to find the error in this code now? Congrats to those who managed to do it! You did great! However, I understand those who got a bit lazy. Reviewing such code is very tedious and you probably want to somehow quickly move on to checking something more interesting. Static analyzers are excellent for such cases, because they don't get tired. The error is that these two checks are the same: if (areacode & sC0Min1Max) { } else if (areacode & sC0Min1Max) { If you carefully review the code, it becomes clear that the very first check is erroneous. The correct version: if (areacode & sC0Min1Min) { } else if (areacode & sC0Max1Min) { } else if (areacode & sC0Max1Max) { } else if (areacode & sC0Min1Max) { CryEngine V project, C++ bool CompareRotation(const Quat& q1, const Quat& q2, float epsilon) { return (fabs_tpl(q1.v.x - q2.v.x) <= epsilon) && (fabs_tpl(q1.v.y - q2.v.y) <= epsilon) && (fabs_tpl(q2.v.z - q2.v.z) <= epsilon) // <= && (fabs_tpl(q1.w - q2.w) <= epsilon); } TortoiseGit project, C++ void CGitStatusListCtrl::OnContextMenuList(....) { .... if( (!this->m_Rev1.IsEmpty()) || (!this->m_Rev1.IsEmpty()) ) .... } Geant4 software project, C++ G4double G4MesonAbsorption:: GetTimeToAbsorption(const G4KineticTrack& trk1, const G4KineticTrack& trk2) { .... if(( trk1.GetDefinition() == G4Neutron::Neutron() || trk1.GetDefinition() == G4Neutron::Neutron() ) &&
  • 22. sqrtS>1.91*GeV && pi*distance>maxChargedCrossSection) return time; .... } MonoDevelop project, C# private bool MembersMatch(ISymbol member1, ISymbol member2) { .... if (member1.DeclaredAccessibility != member1.DeclaredAccessibility || member1.IsStatic != member1.IsStatic) { return false; } .... } As you can see, the above code fragments go unexplained so far. Actually, there is nothing to explain. You can only groan and offer your empathy. Dolphin Emulator project, C++ bool IRBuilder::maskedValueIsZero(InstLoc Op1, InstLoc Op2) const { return (~ComputeKnownZeroBits(Op1) & ~ComputeKnownZeroBits(Op1)) == 0; } RunAsAdmin Explorer Shim project, C++ bool IsLuidsEqual(LUID luid1, LUID luid2) { return (luid1.LowPart == luid2.LowPart) && (luid2.HighPart == luid2.HighPart); } IT++, C++ Gold::Gold(const ivec &mseq1_connections, const ivec &mseq2_connections) { .... it_assert(mseq1.get_length() == mseq1.get_length(), "Gold::Gold(): dimension mismatch"); } QuantLib, C++ Distribution ManipulateDistribution::convolve( const Distribution& d1, const Distribution& d2) { .... QL_REQUIRE (d1.xmin_ == 0.0 && d1.xmin_ == 0.0,
  • 23. "distributions offset larger than 0"); .... } Samba project, C++ static bool samu_correct(struct samu *s1, struct samu *s2) { .... } else if (s1_len != s1_len) { DEBUG(0, ("Password history not written correctly, " "lengths differ, want %d, got %dn", s1_len, s2_len)); .... } Mozilla Firefox project, C++ static PRBool IsZPositionLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2, void* aClosure) { if (!aItem1->GetUnderlyingFrame()->Preserves3D() || !aItem1->GetUnderlyingFrame()->Preserves3D()) { return IsContentLEQ(aItem1, aItem2, aClosure); } .... } Haiku Operation System, C++ void trans_double_path::reset() { m_src_vertices1.remove_all(); m_src_vertices2.remove_all(); m_kindex1 = 0.0; // <= m_kindex1 = 0.0; // <= m_status1 = initial; m_status2 = initial; } Qt project, C++ Ok, now let's get to more complicated cases. Try to find the error here just for the sake of interest: static ShiftResult shift(....) { .... qreal l = (orig->x1 - orig->x2)*(orig->x1 - orig->x2) + (orig->y1 - orig->y2)*(orig->y1 - orig->y1) * (orig->x3 - orig->x4)*(orig->x3 - orig->x4) + (orig->y3 - orig->y4)*(orig->y3 - orig->y4); .... }
  • 24. Here's the picture not to see the answer straight away so that you got a chance to think about the fragment. Right, orig->y1 - orig->y2 has to be written instead of orig->y1 - orig->y1. .NET Compiler Platform project, C# public void IndexerMemberRace() { .... for (int i = 0; i < 20; i++) { .... if (i % 2 == 0) { thread1.Start(); thread2.Start(); } else { thread1.Start(); thread2.Start(); }
  • 25. .... } .... } That's an interesting case. For testing purposes, you want to run threads in a different order. However, due to a typo, threads always start in the same way, so the test checks less than it should. The correct version: if (i % 2 == 0) { thread1.Start(); thread2.Start(); } else { thread2.Start(); thread1.Start(); } Samba project, C static int compare_procids(const void *p1, const void *p2) { const struct server_id *i1 = (struct server_id *)p1; const struct server_id *i2 = (struct server_id *)p2; if (i1->pid < i2->pid) return -1; if (i2->pid > i2->pid) return 1; return 0; } The comparison function will never return 1, as the i2->pid > i2->pid condition is pointless. Naturally, that's a trivial typo, in fact, the following has to be written: if (i1->pid > i2->pid) return 1; ChakraCore project, C++ The last case in this section. Yippee! bool Lowerer::GenerateFastBrSrEq(...., IR::RegOpnd * srcReg1, IR::RegOpnd * srcReg2, ....) { .... else if (srcReg1 && (srcReg1->m_sym->m_isStrConst)) .... else if (srcReg1 && (srcReg1->m_sym->m_isStrConst)) .... }
  • 26. Miscellaneous errors Now I'm going to mention error patterns related to 0, 1, 2 usage with fewer examples. Typos in conditions with the explicit usage of the constant 0/1/2 ROOT project, C++ Int_t TProofMonSenderML::SendSummary(TList *recs, const char *id) { .... if (fSummaryVrs == 0) { if ((dsn = recs->FindObject("dataset"))) recs->Remove(dsn); } else if (fSummaryVrs == 0) { .... } It's strange to compare the fSummaryVrs variable with 0 twice. .NET CoreCLR, C# void PutIA64Imm22(UINT64 * pBundle, UINT32 slot, INT32 imm22) { if (slot == 0) // <= { .... } else if (slot == 1) { .... } else if (slot == 0) // <= { .... } .... } FFmpeg project, C static int imc_decode_block(....) { .... if (stream_format_code & 0x1) imc_decode_level_coefficients_raw(....); else if (stream_format_code & 0x1) imc_read_level_coeffs_raw(....); .... } Index / name Previously, we have looked at cases where an index or a name is incorrect. And here is a situation where you can't immediately tell how to classify the error. This example could be attributed to both chapters. So I decided to bring it separately.
  • 27. Mesa 3D Graphics Library project, C++ bool ir_algebraic_visitor::reassociate_constant(....) { .... if (ir1->operands[0]->type->is_matrix() || ir1->operands[0]->type->is_matrix() || ir2->operands[1]->type->is_matrix() || ir2->operands[1]->type->is_matrix()) return false; .... } This code can be fixed as follows: if (ir1->operands[0]->type->is_matrix() || ir1->operands[1]->type->is_matrix() || ir2->operands[0]->type->is_matrix() || ir2->operands[1]->type->is_matrix()) As well as in this way: if (ir1->operands[0]->type->is_matrix() || ir2->operands[0]->type->is_matrix() || ir1->operands[1]->type->is_matrix() || ir2->operands[1]->type->is_matrix()) Redundant 0 Sometimes 0 is superfluous and harmful. Because of it, the number can turn into an octal one, in the place where it shouldn't. Or spoil the format string. These errors aren't suitable for this article, but I think they are worth mentioning. I won't give you the code with these errors in the article, but if you're interested, you can check them out here: • V536 Be advised that the utilized constant value is represented by an octal form, examples; • V638 A terminal null is present inside a string. The '0xNN' characters were encountered. Probably meant: 'xNN', examples. Forgot to write +1 Haiku Operation System, C++ int UserlandFS::KernelEmu::new_path(const char *path, char **copy) { .... // append a dot, if desired if (appendDot) { copiedPath[len] = '.'; copiedPath[len] = '0'; } .... }
  • 28. Here is the correct version: copiedPath[len] = '.'; copiedPath[len + 1] = '0'; Note. The case when one forgets to add 1 is not uncommon. I remember exactly that I have seen such cases quite often. However, when I wanted to collect such examples for an article, I found only this example. I'm sorry I can't scare you with more errors. I do apologize. Formatting errors (C#) Most often functions for building strings operate with a small number of arguments. So it turns out that errors relate to the usage of {0}, {1} or {2}. Azure PowerShell project, C# protected override void ProcessRecordInternal() { .... if (this.ShouldProcess(this.Name, string.Format("Creating Log Alert Rule '{0}' in resource group {0}", this.Name, this.ResourceGroupName))) { .... } .... } The author made a typo and wrote {0} twice. As a result, the this.Name name will be inserted in the string twice. As for the this.ResourceGroupName name, it won't get into the created string. Mono project, C# void ReadEntropy () { if (reader.IsEmptyElement) throw new XmlException ( String.Format ("WS-Trust Entropy element is empty.{2}", LineInfo ())); .... } The above code is so weird. You are supposed to insert something that doesn't exist. Most likely, this code underwent failed refactoring and the logic was violated. Xenko project, C# public string ToString(string format, IFormatProvider formatProvider) { if (format == null) return ToString(formatProvider); return string.Format( formatProvider,
  • 29. "Red:{1} Green:{2} Blue:{3}", R.ToString(format, formatProvider), G.ToString(format, formatProvider), B.ToString(format, formatProvider)); } The programmer forgot that the numbering begins with {0}, not with {1}. Correct code: return string.Format( formatProvider, "Red:{0} Green:{1} Blue:{2}", R.ToString(format, formatProvider), G.ToString(format, formatProvider), B.ToString(format, formatProvider)); .NET Compiler Platform project, C# private void DumpAttributes(Symbol s) { .... Console.WriteLine("{0} {1} {2}", pa.ToString()); .... } Arguments are clearly not enough. Conclusions and recommendations I had to demonstrate quite many examples to show that typos related to 0, 1, and 2 deserve special attention. If I'd just said: "It's easy to confuse o1 and o2", you would have agreed, but wouldn't have given it special attention, as you're giving it now after reading or at least looking through the article. Now you're forewarned, and that's good. Forewarned is forearmed. From now on you'll be more attentive during code reviews and will pay extra attention to variables with 0, 1, 2 in the names. It's difficult to give certain recommendations on code formatting so as to avoid the above errors. As you have seen, errors occur even in such simple code, where there is actually nothing to format. Therefore, I won't call to avoid 0, 1, 2 and give variables long names. If you start writing First/Second/Left/Right instead of numbers, then the temptation to copy the name or expression will be even greater. Perhaps this recommendation won't ultimately reduce, but will increase the number of errors. However, when you write a lot of similar code, the recommendation of "table code formatting" is still relevant. Table formatting doesn't guarantee the absence of typos, but helps to notice them more easily and faster. See the chapter 13 from the mini-book "The Ultimate Question of Programming, Refactoring, and Everything" for more details. There's another piece of good news. All of the errors discussed in this article are detected by the PVS-Studio static code analyzer. Accordingly, by introducing static analysis tools into the development process, you will be able to identify many typos at the earliest stage.
  • 30. Thank you for your attention. I hope you were interested and scared. I wish you reliable code and less errors with 0, 1, 2, so that Freddy didn't come to you.