Zero, um, dois, Freddy vai buscá-lo

Imagem 1

Aqui está uma continuação de uma série de artigos que podem ser intitulados "horrores para programadores". Desta vez, falaremos sobre um padrão de erro tipográfico associado ao uso dos números 0, 1, 2. Não importa se você escreve em C, C ++, C # ou Java. Se você usar constantes 0, 1, 2 ou se esses números estiverem contidos em nomes de variáveis, provavelmente o Freddy o visitará à noite. Leia e não diga mais tarde que você não foi avisado.


Introdução


Continuo uma série de artigos dedicados a padrões notados em como as pessoas cometem erros. Publicações anteriores:
  1. Efeito da última linha
  2. A função mais perigosa do mundo de C / C ++
  3. O mal vive em funções de comparação

Dessa vez, o padrão não foi percebido por mim, mas pelo meu colega Svyatoslav Razmyslov. Ele observou que constantemente descrevia em seus artigos problemas em que variáveis ​​contendo os números 1 e 2. aparecem em seu nome.Svyatoslav sugeriu que eu estudasse esse tópico com mais detalhes e ele realmente se mostrou muito proveitoso. Verificou-se que nossa coleção de erros contém um grande número de fragmentos de código incorretos devido ao fato de as pessoas ficarem confusas nos índices 0, 1, 2 ou nos nomes de variáveis ​​que contêm esses números. Uma nova regularidade interessante é revelada, que será discutida abaixo. Sou grato a Svyatoslav pelo prompt para investigar este tópico e dedicar este artigo a ele.

Figura 14

Svyatoslav Razmyslov, gerente, apanhador de bugs atento e apenas uma pessoa talentosa.

Qual é o objetivo deste artigo? Mostre como é fácil cometer erros e erros de digitação. Se os programadores forem avisados, eles estarão mais atentos no processo de revisão de código, concentrando-se nos 0, 1, 2. malfadados. Além disso, os programadores poderão sentir melhor o valor dos analisadores de código estático que ajudam a detectar tais erros. Não se trata de publicidade do PVS-Studio (embora também seja :). Até agora, muitos programadores consideram a análise estática supérflua, preferindo se concentrar em suas próprias análises de precisão e código. Infelizmente, tentar escrever código sem erros é bom, mas não é suficiente. Este artigo mais uma vez demonstrará isso claramente.

Ninguém está imune a esses erros. Abaixo, você verá erros épicos em projetos conhecidos como Qt, Clang, Hive, LibreOffice, Kernel Linux, .NET Compiler Platform, kernel XNU, Mozilla Firefox. E esses não são alguns erros raros exóticos, mas os mais frequentes. Não convencido? Então vamos começar!

“O Chatter é inútil! Mostre-me os insetos!

© citações modificadas por Linus Torvalds.

Erros de digitação em constantes ao indexar matrizes


Como regra, em nossos artigos, emitimos avisos com a ajuda de quais erros são encontrados. Desta vez, omitirei esses avisos, pois sem eles o erro será facilmente perceptível e compreensível. No entanto, embora esses erros sejam imediatamente evidentes em um pequeno pedaço de código, eles sabem como se esconder no código dos projetos.

Vamos começar com os erros quando houver confusão com os literais numéricos usados ​​para indexar matrizes. Apesar da banalidade desses erros, existem muitos deles e eles não são revelados no trabalho de laboratório dos estudantes.

Projeto de kernel XNU, linguagem 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;
  }
  ....
}

A linha foi copiada, mas esqueceu de corrigir o índice. Pelo que entendi, deve ser escrito aqui:
wrap.Seal_Alg[0] = 0xff;
wrap.Seal_Alg[1] = 0xff;

Projeto LibreOffice, 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;
}

Como no caso anterior, a linha foi copiada, mas esqueceu de corrigir 0 por 1. Somente a literal da string foi corrigida.

Alguém pode fazer uma pergunta filosófica, como alguém pode cometer esse erro em uma função de quatro linhas? Tudo é possivel. Aqui está, programação.

Projeto Quake-III-Arena, linguagem C
int VL_FindAdjacentSurface(....)
{
  ....
  if (fabs(dir[0]) > test->radius ||
      fabs(dir[1]) > test->radius ||
      fabs(dir[1]) > test->radius)
  {
  ....
}

Na linha copiada, eles esqueceram de substituir o diretório [1] pelo diretório [2] . Como resultado, o valor ao longo do eixo Z não é controlado.

Projeto OpenCOLLADA, C ++
struct short2
{
  short values[2];
  short2(short s1, short s2)
  {
    values[0] = s1;
    values[2] = s2;
  }
  ....
};

Sim, mesmo em um construtor tão curto, você pode ir além dos limites de uma matriz quando ela é inicializada.

Figura 8


Projeto Godot Engine, C ++
Array PhysicsDirectSpaceState::_cast_motion(....)
{
  ....
  Array ret(true);
  ret.resize(2);
  ret[0]=closest_safe;
  ret[0]=closest_unsafe;
  return ret;
}

Nenhum comentário é necessário.

Projeto Asterisk, linguagem 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;
  ....
}

Ao escrever o mesmo tipo de bloco, o erro, em regra, está localizado no subjacente. Antes disso, todos os casos considerados eram exatamente isso. Aqui, o erro de digitação está em um lugar incomum, a saber, no primeiro bloco. Por que isso aconteceu é difícil de dizer. Não tenho escolha a não ser trazer uma foto de um unicórnio encolher os ombros:

Figura 9


Projeto de Tecnologia Open CASCADE, C ++
inline void Prepend(const Standard_Integer theIndex)
{
  if (myIndex[1] >= 0)
    Standard_OutOfRange::Raise ("BRepMesh_PairOfIndex....");

  myIndex[1] = myIndex[0];
  myIndex[1] = theIndex;
}

Duas vezes na mesma célula da matriz, valores diferentes são copiados. Um erro óbvio, mas como corrigi-lo não estava claro para mim, pois o código do projeto não é familiar para mim. Então, acabei de ver como os desenvolvedores corrigiram o código depois que nossa equipe apontou esse erro para eles. A opção correta:
myIndex[1] = myIndex[0];
myIndex[0] = theIndex;

Projeto de Oleoduto Transproteômico, 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;
  }
  ....
}

Eu me preocupo que os pacotes de pesquisa contenham esses erros. O Trans-Proteomic Pipeline foi projetado para resolver problemas no campo da biologia. Isso pode ser decidido e "investigado". Este pacote geralmente encontra muitas coisas interessantes: verifique em 2012 , verifique 2013 . Talvez você possa tentar novamente olhar para este projeto.

Projeto ITK, linguagem C ++

Estamos diante de outro projeto para a realização de pesquisas no campo da medicina: o Kit de Ferramentas de Segmentação e Registro do Medicine Insight (ITK). O projeto é diferente, mas os erros são os mesmos.
template< typename TCoordRepType >
void
VoronoiDiagram2D< TCoordRepType >::SetOrigin(PointType vorsize)
{
  m_VoronoiBoundaryOrigin[0] = vorsize[0];
  m_VoronoiBoundaryOrigin[0] = vorsize[1];
}

Projeto ITK, C ++


int itkPointSetToSpatialObjectDemonsRegistrationTest(....)
{
  ....
  // Set its position
  EllipseType::TransformType::OffsetType offset;
  offset[0]=50;
  offset[1]=50;
  offset[1]=50;
  ....
}

Limpe copiar e colar.

Projeto ReactOS, 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);
  ....
}

Provavelmente, a constante crBackgnd deve ser gravada na célula cols [2] .

Projeto Coin3D, 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))) {
  ....
}

O elemento da matriz size [1] é verificado duas vezes , e o elemento size [2] não é verificado. É assim que artefatos estranhos aparecem nas imagens.

Projeto OpenCV, C ++
bool Jpeg2KDecoder::readHeader()
{
  ....
  cmptlut[0] = ....
  cmptlut[1] = ....
  cmptlut[2] = ....
  if( cmptlut[0] < 0 || cmptlut[1] < 0 || cmptlut[0] < 0 )
    result = false;
  ....
}

Considera-se diretamente que a expressão cmptlut [0] <0 foi duplicada duas vezes pela cópia, mas corrigiu zero em apenas um lugar.

Projeto Visualization Toolkit (VTK), C ++
void vtkImageStencilRaster::PrepareForNewData(....)
{
  ....
  if (allocateExtent &&
      allocateExtent[1] >= allocateExtent[1])
  ....
}

Daqui em diante, não comentarei muitos desses erros. O que há para comentar? O principal ao analisar esses fragmentos de código é achar que, embora o erro seja simples, isso não significa que ele será percebido pelo programador.

Projeto Visualization Toolkit (VTK), 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;
  ....
}

Aqui, o programador estava claramente com pressa de escrever código mais rapidamente. É difícil explicar de outra maneira como ele cometeu um erro três vezes. Os elementos da matriz são subtraídos deles mesmos. O resultado é que este código é equivalente:
inZPtr +=
  (0)*inIncs[0] * data_type_size +
  (0)*inIncs[1] * data_type_size +
  (0)*inIncs[2] * data_type_size;

No entanto, esse código pode ser reduzido ainda mais:
inZPtr += 0;

Sumptuosamente. Há uma expressão longa e séria no código que, de fato, não faz nada. Eu amo esses casos.

Projeto Visualization Toolkit (VTK), linguagem C ++

Um caso semelhante de código de escrita apressado.
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);
    }
}

A comparação newPos [2]! = OldPos [2] é repetida duas vezes .

Projeto 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);
  }
  ....
}

A condição deve verificar se duas barras após os dois pontos são encontradas. Em outras palavras, a substring ": //" é pesquisada. Devido a um erro de digitação, o cheque é "cego" e está pronto para contar qualquer caractere como uma segunda barra.

Projeto de amostras IPP, 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]));
  ....
}

Um erro de digitação está aqui, nos argumentos passados ​​para a macro:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[2])

Acontece que um mínimo de dois valores idênticos é selecionado. De fato, deve ser escrito:
IPP_MIN(m_cur.AcRate[2],m_cur.AcRate[3])

A propósito, esse código pode demonstrar a utilidade da biblioteca padrão. Se você escreve assim:
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));

Esse código se tornará mais curto e menos propenso a erros. Na verdade, quanto menos o mesmo tipo de código, maior a probabilidade de ele ser escrito corretamente.

Audácia do projeto, C ++
sampleCount VoiceKey::OnBackward (....) {
  ....
  int atrend = sgn(buffer[samplesleft - 2]-
                   buffer[samplesleft - 1]);
  int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
                   buffer[samplesleft - WindowSizeInt-2]);
  ....
}

A expressão correta é:
int ztrend = sgn(buffer[samplesleft - WindowSizeInt-2]-
                 buffer[samplesleft - WindowSizeInt-1]);

Projeto PDFium, linguagem 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;
}

Uma série de ações para inicializar a estrutura é duplicada. As linhas marcadas com o comentário // 2 podem ser excluídas e nada será alterado. Eu duvidava de incluir esse pedaço de código no artigo. Isso não é um erro e nem um pouco com os índices. No entanto, esse código extra provavelmente apareceu precisamente porque o programador ficou confuso em todos esses membros da classe e nos índices 1, 2. Portanto, acho que esse trecho de código é adequado para demonstrar como é fácil ficar confuso em números.

Projeto CMake, C

O código discutido abaixo não foi escrito pelos desenvolvedores do CMake, mas foi emprestado. A julgar pelo comentário no início do arquivo, a função utf8_encodeFoi escrita por Tim Kientzle em 2007. Desde então, essa função tem andado de projeto para projeto, e existem muitas delas. Não estudei a questão da fonte original, pois esse não é o ponto agora. Como esse código está no projeto CMake, o erro se aplica ao CMake.
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;
  ....
}

Como você pode ver, há algum tipo de confusão com os índices. Por duas vezes, há um registro no elemento do array p [1] . Se você estuda o código na vizinhança, fica claro que o código correto deve ser assim:
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;

Nota

Observe que todos os erros discutidos neste capítulo estão relacionados ao código C ou C ++. Sem C # ou código Java!

Isso é muito interessante, eu não esperava isso. Na minha opinião, os erros de digitação considerados não dependem do idioma. E nos capítulos seguintes, erros de código em outros idiomas aparecerão de fato. Eu acho que isso é apenas uma coincidência. O analisador PVS-Studio começou muito mais tarde a suportar linguagens C # / Java em comparação com C / C ++, e simplesmente não conseguimos acumular os exemplos de erros correspondentes no banco de dados.

No entanto, a observação ainda é interessante. Aparentemente, os programadores de C e C ++ gostam de usar os números 0, 1 e 2 ao trabalhar com matrizes :).

Erros ortográficos nos nomes


Esta será a maior seção. É muito fácil para as pessoas ficarem confusas com nomes como a1 e a2 . Parece que você pode ficar confuso aqui? Pode. Fácil. E agora o leitor poderá verificar isso.

Projeto Hive, 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;
}

A função comparar compara dois objetos: o1 e o2 . Mas devido a um erro de digitação, apenas o2 é usado ainda mais .

Curiosamente, graças ao Copy-Paste, esse erro foi migrado para outra função:
@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;
}

Figura 10


Projeto Infer.NET, linguagem C #
private void MergeParallelTransitions()
{
  ....
  if (double.IsInfinity(transition1.Weight.Value) &&    
      double.IsInfinity(transition1.Weight.Value))
  ....
}

Projeto Doom 3, C ++
uint AltOp::fixedLength()
{
  uint l1 = exp1->fixedLength();
  uint l2 = exp1->fixedLength();

  if (l1 != l2 || l1 == ~0u)
    return ~0;

  return l1;
}

Se alguém não notou imediatamente um erro de digitação, é necessário observar a linha em que a variável l2 é inicializada . Deve usar exp2 .

Projeto SDK do mecanismo de origem, 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;
  }
  ....
}

Corretamente:
nFPSThreshold1 = 60;
nFPSThreshold2 = 50;

Projeto Linux Kernel, linguagem C

A propósito, erros de digitação podem estar não apenas em nomes de variáveis, mas também em nomes de macro. Agora haverá vários exemplos.
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;
  }
  ....
}

Como você pode ver, uma máscara chamada BIT1 é usada duas vezes , o que torna a segunda verificação inútil. O corpo da segunda instrução condicional comentada nunca será executado.

Projeto CMaNGOS, C ++
void AttackedBy(Unit* pAttacker) override
{
  ....
  DoScriptText(urand(0, 1) ?
               SAY_BELNISTRASZ_AGGRO_1 :
               SAY_BELNISTRASZ_AGGRO_1,
               m_creature, pAttacker);
  ....
}

O comportamento aleatório foi planejado no jogo, mas a mesma constante SAY_BELNISTRASZ_AGGRO_1 sempre é selecionada .

Projeto Vangers: Um para a estrada, 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;
  }
  ....
}

A julgar pelo código escrito ao lado, a opção correta deve ser assim:
ret = (lang)
  ? iJoystickStickSwitch2[vkey - VK_STICK_SWITCH_1]
  : iJoystickStickSwitch1[vkey - VK_STICK_SWITCH_1];

Projeto RT-Thread, linguagem 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;
}

O RT-Thread é um sistema operacional de código aberto e em tempo real para dispositivos incorporados. Aqui vemos a confusão entre FIFO 0 e FIFO 1. E em algum lugar, alguém encontrará um dispositivo de buggy.

Figura 11


O erro está aqui:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO0 == fifo_number){

A segunda verificação sempre dá falso. Corretamente:
if      (CAN_FIFO0 == fifo_number){
....
}else if(CAN_FIFO1 == fifo_number){

Projeto Hive, 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")) {
    ....
}

O analisador PVS-Studio indica imediatamente 2 erros:
  1. Uma sequência armazenada em colOrScalar1 não pode ser igual às seqüências de caracteres Col e Column;
  2. A cadeia armazenada em colOrScalar1 não pode ser igual às cadeias Col e Scalar ao mesmo tempo.

Há claramente uma confusão sobre nomes de variáveis.

Projeto Shareaza, linguagem 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()))
    ....
}

Corretamente:
pAttr1->GetValue().CompareNoCase(pAttr2->GetValue())

Nota

Vamos fazer uma breve pausa. Há um medo de que, olhando através de uma montanha de erros banais, esqueçamos por que estamos fazendo isso.

A tarefa não é rir do código de outra pessoa. Tudo isso não é um motivo para cutucar o dedo e dizer: "Ha ha, bem, você precisa". Esta razão para pensar!

As publicações de nossa equipe são projetadas para mostrar que nenhum de nós está imune a erros. Os erros descritos no artigo aparecem no código com muito mais frequência do que você poderia esperar. Também é importante que a probabilidade de se perder em 0, 1, 2 quase não dependa das qualificações do programador.

É útil perceber que as pessoas tendem a cometer erros. Sem isso, você não pode dar o próximo passo para melhorar a qualidade e a confiabilidade do código. Entendendo que todos nós estamos enganados, as pessoas começam a tentar identificar erros nos estágios iniciais, usando padrões de codificação, revisões de código, testes de unidade, analisadores estáticos e dinâmicos. É muito bom.

Por que as coisas são tão compreensíveis escritas? Infelizmente, ao nos comunicarmos com um grande número de desenvolvedores, somos forçados a afirmar que nem sempre é tão claro para todos. Muitos têm auto-estima muito alta e simplesmente não permitem pensar que são capazes de cometer erros simples. É triste.

Se você é um líder / gerente de equipe, convido você a se familiarizar com esta nota ao mesmo tempo .

Projeto Qt, 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())
  ....
}

Corretamente:
const Numeric *const num2 = o2.as<Numeric>();

Projeto Android, linguagem 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;
}

Dois erros de digitação ao mesmo tempo, devido aos quais as variáveis pr2.mStretchMode e pr2.mFallbackMode são comparadas entre si.

Projeto Boost, C ++
point3D operator/(const point3D &p1, const point3D &p2)
{
  return point3D(p1.x/p2.x, p1.y/p2.y, p1.z/p1.z);
}

No final, selaram-se e dividiu a variável p1.z si.

Projeto Clang, C ++
bool haveSameType(QualType Ty1, QualType Ty2) {
  return (Context.getCanonicalType(Ty1) ==
          Context.getCanonicalType(Ty2) ||
          (Ty2->isIntegerType() &&
           Ty2->isIntegerType()));
}

Sim, sim, o analisador PVS-Studio encontra erros semelhantes nos compiladores. Corretamente:
(Ty1->isIntegerType() &&
 Ty2->isIntegerType())

Projeto Clang, 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())) {
  ....
}

Corretamente:
(Op0I->hasOneUse() || Op1I->hasOneUse())

Projeto Qt, C ++
inline bool qCompare(QImage const &t1, QImage const &t2, ....)
{
  ....
  if (t1.width() != t2.width() || t2.height() != t2.height()) {
  ....
}

Projeto NCBI Genome Workbench, 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 {
  .....
}

Erro na primeira verificação. Deve ser escrito:
if (!s1.IsSet() && s2.IsSet()) {

Projeto NCBI Genome Workbench, 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");
  }
  ....
}

A primeira linha da condição foi propagada, mas o programador se apressou e esqueceu de substituir loc1 por loc2 .

Projeto FlashDevelop, C #
public void SetPrices(....)
{
  UInt32 a0 = _choice.GetPrice0();
  UInt32 a1 = _choice.GetPrice1();
  UInt32 b0 = a1 + _choice2.GetPrice0();   // <=
  UInt32 b1 = a1 + _choice2.GetPrice1();
  ....
}

Projeto FreeCAD, 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);
};

Independentemente da condição, a mesma ação é executada. Parece um caso tão simples. Como você pode copiar uma linha e não corrigi-la? Pode.

Projeto LibreOffice, C ++
class SVX_DLLPUBLIC SdrMarkView : public SdrSnapView
{
  ....
  const Point& GetRef1() const { return maRef1; }
  const Point& GetRef2() const { return maRef1; }
  ....
};

Erro clássico de copiar e colar. Corretamente:
const Point& GetRef2() const { return maRef2; }

Projeto LibreOffice, 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();
  ....
}

E mais um erro clássico de copiar e colar :). Em um lugar, 1 a 2 foram corrigidos e em outro eles esqueceram.

Projeto LibreOffice, C ++
XMLTransformerOOoEventMap_Impl::XMLTransformerOOoEventMap_Impl(
        XMLTransformerEventMapEntry *pInit,
        XMLTransformerEventMapEntry *pInit2 )
{
  if( pInit )
    AddMap( pInit );
  if( pInit )
    AddMap( pInit2 );
}

Não há erro em substituir 1 por 2, mas simplesmente não adicionamos 2 à segunda condição.

Figura 12


Você pode estar um pouco cansado. Portanto, proponho fazer chá ou café e continuaremos conhecendo o mundo dos números 0, 1 e 2.

Projeto de software Geant4, linguagem 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];
  }
  ....
}

Espero que você tenha seguido o conselho e descansado. Pronto para encontrar um erro neste código?

Parabéns aos leitores que notaram um erro. Você é ótimo!

Quem tem preguiça de procurar, eu também entendo. A revisão desse código é muito entediante e existe um desejo de verificar de alguma forma rapidamente algo mais interessante. É aqui que os analisadores estáticos ajudam muito porque não se cansam.

O erro é que essas duas verificações são iguais:
if        (areacode & sC0Min1Max) {
} else if (areacode & sC0Min1Max) {

Se você estuda o código, fica claro que a primeira verificação é incorreta. Corretamente:
if        (areacode & sC0Min1Min) {
} else if (areacode & sC0Max1Min) {
} else if (areacode & sC0Max1Max) {
} else if (areacode & sC0Min1Max) {

Projeto CryEngine V, 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);
}

Projeto TortoiseGit, C ++
void CGitStatusListCtrl::OnContextMenuList(....)
{
  ....
  if( (!this->m_Rev1.IsEmpty()) ||
      (!this->m_Rev1.IsEmpty()) )
  ....
}

Projeto de software Geant4, linguagem 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;
  ....
}

Projeto MonoDevelop, C #
private bool MembersMatch(ISymbol member1, ISymbol member2)
{
  ....
  if (member1.DeclaredAccessibility !=
      member1.DeclaredAccessibility
   || member1.IsStatic != member1.IsStatic)
  {
    return false;
  }
  ....
}

Como você pode ver, enquanto os fragmentos de código ficam sem explicação. Na verdade, não há nada para explicar aqui. Você pode apenas suspirar.

Projeto Dolphin Emulator, C ++
bool IRBuilder::maskedValueIsZero(InstLoc Op1, InstLoc Op2) const
{
  return (~ComputeKnownZeroBits(Op1) &
          ~ComputeKnownZeroBits(Op1)) == 0;
}

Projeto de calço do Explorer RunAsAdmin, C ++
bool IsLuidsEqual(LUID luid1, LUID luid2)
{
  return (luid1.LowPart == luid2.LowPart) &&
         (luid2.HighPart == luid2.HighPart);
}

Projeto IT ++, linguagem C ++
Gold::Gold(const ivec &mseq1_connections,
           const ivec &mseq2_connections)
{
  ....
  it_assert(mseq1.get_length() == mseq1.get_length(),
            "Gold::Gold(): dimension mismatch");
}

Projeto 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");
  ....
}

Projeto Samba, 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 %d\n",
          s1_len, s2_len));
  ....
}

Projeto Mozilla Firefox, C ++
static PRBool IsZPositionLEQ(nsDisplayItem* aItem1,
                             nsDisplayItem* aItem2,
                             void* aClosure) {
  if (!aItem1->GetUnderlyingFrame()->Preserves3D() ||
      !aItem1->GetUnderlyingFrame()->Preserves3D()) {
    return IsContentLEQ(aItem1, aItem2, aClosure);
  }
  ....
}

Projeto de sistema operacional Haiku, 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;
}

O projeto Qt, C ++

Ok, agora vamos ficar um pouco mais complicados. Por diversão, tente encontrar o erro aqui:
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);
  ....
}

Uma foto, para não ver a resposta imediatamente, e tive a oportunidade de pensar.

Figura 13


É isso mesmo, em vez de orig-> y1 - orig-> y1 deve ser escrito orig-> y1 - orig-> y2 .

Projeto de plataforma do compilador .NET, linguagem C #
public void IndexerMemberRace()
{
  ....
  for (int i = 0; i < 20; i++)
  {
    ....
    if (i % 2 == 0)
    {
      thread1.Start();
      thread2.Start();
    }
    else
    {
      thread1.Start();
      thread2.Start();
    }
    ....
  }
  ....
}

Caso interessante. Para fins de teste, você precisa executar threads em uma ordem diferente. No entanto, devido a um erro de digitação, os encadeamentos sempre começam da mesma maneira, como resultado do qual o teste verifica menos do que deveria.

Corretamente:
if (i % 2 == 0)
{
  thread1.Start();
  thread2.Start();
}
else
{
  thread2.Start();
  thread1.Start();
}

Projeto Samba, linguagem 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;
}

A função de comparação nunca retornará 1, pois a condição i2-> pid> i2-> pid não faz sentido.

Naturalmente, esse é um erro de digitação comum e, de fato, deve ser escrito:
if (i1->pid > i2->pid) return 1;

Projeto ChakraCore, C ++

O último caso deste capítulo. Viva!
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))
  ....
}


Outros erros


Agora, vamos falar sobre padrões de erro menos numerosos associados ao uso dos números 0, 1, 2.

Erros de digitação em condições em que a constante 0/1/2 é usada explicitamente


Projeto ROOT, 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) {
  ....
}

É estranho comparar fSummaryVrs a 0. duas

vezes.Projeto .NET CoreCLR, C #
void PutIA64Imm22(UINT64 * pBundle, UINT32 slot, INT32 imm22)
{
  if (slot == 0)             // <=
  {
    ....
  }
  else if (slot == 1)
  {
    ....
  }
  else if (slot == 0)        // <=
  {
    .... 
  }
  ....
}

Projeto FFmpeg, linguagem 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(....);
  ....
}


Zip / Nome


Consideramos anteriormente casos em que o índice ou o nome está incorreto. E aqui está uma situação em que você não informa imediatamente como classificar o erro. Este exemplo pode ser atribuído a um e ao outro capítulo. Por isso, decidi trazê-lo separadamente.

Projeto da Biblioteca de Gráficos 3D Mesa, 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;
  ....
}

Este código pode ser corrigido assim:
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())

E você pode corrigi-lo assim:
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())


Extra 0


Às vezes, 0 é redundante e prejudicial. Por causa disso, o número pode se transformar em octal onde não é necessário. Ou estrague a sequência de formatação.

Os erros mencionados não são adequados para este artigo, mas acho que vale a pena mencioná-los. Não darei um código com esses erros no artigo, mas se estiver interessado, você pode vê-los aqui:
  • V536 Esteja ciente de que o valor constante utilizado é representado por uma forma octal ; exemplos ;
  • V638 Um terminal nulo está presente dentro de uma sequência. Os caracteres '\ 0xNN' foram encontrados. Provavelmente significava: '\ xNN', exemplos .


Esqueceu de escrever +1


Projeto de sistema operacional Haiku, C ++
int
UserlandFS::KernelEmu::new_path(const char *path, char **copy)
{
  ....
  // append a dot, if desired
  if (appendDot) {
    copiedPath[len] = '.';
    copiedPath[len] = '\0';
  }
  ....
}

A opção correta:
copiedPath[len] = '.';
copiedPath[len + 1] = '\0';

Nota. A situação em que eles esqueceram de adicionar uma unidade não é de todo rara. Lembro-me exatamente disso mais de uma vez que conheci esses casos. No entanto, quando quis digitar exemplos semelhantes para o artigo, encontrei apenas este exemplo. Sinto muito por não conseguir mais assustá-lo com esses erros. Peço desculpas.

Erros de formatação (C #)


Na maioria das vezes, as funções para construção de strings operam com um pequeno número de argumentos. Portanto, na maioria dos casos, os erros estão associados ao uso de {0}, {1} ou {2}.

Projeto Azure PowerShell, 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)))
  {
    ....
  }
  ....
}

Selado e escreveu {0} duas vezes. Como resultado, o nome this.Name será inserido na string duas vezes . Mas o nome this.ResourceGroupName não entrará na string criada.

Projeto Mono, C #
void ReadEntropy ()
{
  if (reader.IsEmptyElement)
    throw new XmlException (
      String.Format ("WS-Trust Entropy element is empty.{2}",
                      LineInfo ()));
  ....
}

Isso geralmente é estranho. Você precisa inserir o que não é. Muito provavelmente, esse código passou por refatoração malsucedida e acabou sendo quebrado.

Projeto Xenko, 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));
}

O programador esqueceu que a numeração começa com {0}, não {1}. O código correto é:
return string.Format(
                    formatProvider,
                    "Red:{0} Green:{1} Blue:{2}",
                    R.ToString(format, formatProvider),
                    G.ToString(format, formatProvider),
                    B.ToString(format, formatProvider));

Projeto de plataforma do compilador .NET, C #
private void DumpAttributes(Symbol s)
{
  ....
  Console.WriteLine("{0} {1} {2}", pa.ToString());
  ....
}

Argumentos claramente não são suficientes.

Conclusões e Recomendações


Eu tive que demonstrar tantos exemplos para mostrar que erros de digitação relacionados a 0, 1 e 2 merecem atenção especial.

Se eu simplesmente dissesse: “É fácil confundir o1 e o2”, você concorda, mas não atribui a ele o significado que você anexa agora, depois de ler ou pelo menos rolar o artigo.

Agora você está avisado, e isso é bom. O aviso antecipado está previsto. Agora você estará mais atento às revisões de código e prestará atenção extra às variáveis, nos quais verá 0, 1, 2. É

difícil fazer algumas recomendações sobre como o código foi projetado para evitar esses erros. Como você viu, erros são encontrados mesmo em um código tão simples, onde não há nada para emitir.

Portanto, não vou insistir para que você evite 0, 1, 2 e forneça nomes longos às variáveis. Se, em vez de números, você começar a escrever Primeiro / Segundo / Esquerdo / Direito e assim por diante, a tentação de copiar o nome ou expressão será ainda maior. Talvez essa recomendação não reduza, mas aumente o número de erros.

No entanto, quando você escreve muito do mesmo tipo de código, a recomendação do "design de código tabular" ainda é relevante. A formatação tabular não garante a ausência de erros de digitação, mas os torna mais fáceis e rápidos de perceber. Veja o capítulo N13 no mini-livro " A principal questão de programação, refatoração e tudo isso ".

Há mais uma boa notícia. Todos os erros discutidos neste artigo foram encontrados usando o analisador de código estático PVS-Studio .. Assim, introduzindo ferramentas de análise estática no processo de desenvolvimento, você pode identificar muitos erros de digitação em um estágio inicial.

Obrigado pela atenção. Espero que você esteja interessado e assustado. Desejo a você um código confiável e menos erros com 0, 1, 2, para que o Freddy não chegue até você.



Se você deseja compartilhar este artigo com um público que fala inglês, use o link para a tradução: Andrey Karpov. Zero, um, dois, Freddy está vindo para você .

All Articles