
Antes do lançamento de Amnesia: Rebirth, a Fractional Games lançou o código-fonte do lendário Amnesia: The Dark Descent e sua sequência, Amnesia: A Machine For Pigs. Por que não usar uma ferramenta de análise estática para ver como erros terríveis estão escondidos no interior desses jogos de terror de culto?
Vendo no Reddit a notícia de que o código-fonte dos jogos " Amnesia: The Dark Descend " e " Amnesia: A Machine for Pigs " havia sido publicado, não pude passar e verificar esse código usando PVS-Studio, e ao mesmo tempo escrever sobre este artigo. Principalmente considerando o fato de que no dia 20 de outubro (e na época da publicação deste artigo, já havia saído) uma nova parte dessa série: " Amnésia: Renascimento ".
Amnesia: The Dark Descent foi lançado em 2010 e se tornou um jogo de terror de sobrevivência icônico. Pra ser sincero, nunca consegui passar, nem um pouquinho, já que jogo jogos de terror usando o mesmo algoritmo: apostar, entrar por cinco minutos, sair com "alt + f4" no primeiro creep e deletar o jogo.Mas gostei de assistir a passagem desse jogo no YouTube.
E caso alguém ainda não conheça o PVS-Studio, este é um analisador estático que procura erros e lugares suspeitos no código-fonte dos programas.

Gosto especialmente de olhar para o código-fonte dos jogos, então se você está se perguntando quais erros são cometidos nele, você pode ler meus artigos anteriores. Bem, ou dê uma olhada nos artigos dos meus colegas sobre como verificar o código-fonte dos jogos.
Depois de verificar, descobriu-se que muito do código em "The Dark Descend" e "A Machine For Pigs" se sobrepõe, e os relatórios dos dois projetos eram muito semelhantes. Assim, quase todos os erros que citarei a seguir estão contidos em ambos os projetos.
E a maior camada de erros de todos os analisadores detectados nesses projetos foi a camada de erros de "copiar e colar". Daí o título do artigo. A principal razão para esses erros é o " efeito de última linha ".
Bem, vamos ao que interessa.
Erros de copiar e colar
Havia muitos lugares suspeitos, semelhantes a cópias desatentas. Alguns casos, talvez, sejam causados de alguma forma pela lógica interna do próprio jogo. Mas se eles envergonharam a mim e ao analisador, então valeu a pena pelo menos fazer um comentário sobre eles. Afinal, outros desenvolvedores podem ser tão lentos quanto eu.
Snippet 1.
Vamos começar com um exemplo onde toda a função consiste em comparar os resultados de métodos e campos de dois objetos aObjectDataA e aObjectDataB . Darei essa função inteiramente para maior clareza. Tente perceber por si mesmo onde o erro foi cometido na função:
static bool SortStaticSubMeshesForBodies(const ....& aObjectDataA,
const ....& aObjectDataB)
{
//Is shadow caster check
if( aObjectDataA.mpObject->GetRenderFlagBit(....)
!= aObjectDataB.mpObject->GetRenderFlagBit(....))
{
return aObjectDataA.mpObject->GetRenderFlagBit(....)
< aObjectDataB.mpObject->GetRenderFlagBit(....);
}
//Material check
if( aObjectDataA.mpPhysicsMaterial != aObjectDataB.mpPhysicsMaterial)
{
return aObjectDataA.mpPhysicsMaterial < aObjectDataB.mpPhysicsMaterial;
}
//Char collider or not
if( aObjectDataA.mbCharCollider != aObjectDataB.mbCharCollider)
{
return aObjectDataA.mbCharCollider < aObjectDataB.mbCharCollider;
}
return aObjectDataA.mpObject->GetVertexBuffer()
< aObjectDataA.mpObject->GetVertexBuffer();
}
Uma foto, para não espionar acidentalmente a resposta:

Você conseguiu encontrar o erro? Sim, o último retorno é uma comparação usando aObjectDataA em ambos os lados. Observe que todas as expressões no código original foram escritas em uma linha, aqui eu hifenizei para que tudo se encaixe exatamente na largura da linha. Imagine o que esse defeito irá procurar no final da jornada de trabalho. E o analisador o encontrará imediatamente após montar e executar a análise incremental.
V501 Existem subexpressões idênticas 'aObjectDataA.mpObject-> GetVertexBuffer ()' à esquerda e à direita do operador '<'. WorldLoaderHplMap.cpp 1123
Como resultado, tal erro será encontrado quase no momento da escrita do código, em vez de se esconder nas profundezas do código de vários estágios de controle de qualidade, tornando sua pesquisa muitas vezes mais difícil.
Nota do colega Andrey Karpov. Sim, este é um clássico "erro de última linha". No entanto, esse também é um padrão de erro clássico ao comparar dois objetos. Veja o artigo " Evil Lives in Comparison Functions ".Fragmento 2.
Vamos dar uma olhada no código que causou o aviso:

Apresento o código com uma captura de tela para maior clareza.
O aviso em si é semelhante a este:
V501 Existem subexpressões idênticas 'lType == eLuxJournalState_OpenNote' à esquerda e à direita de '||' operador. LuxJournal.cpp 2262
O analisador detectou que há um erro na verificação do valor da variável lType . A igualdade é verificada duas vezes com o mesmo membro do enumerador eLuxJournalState_OpenNote .
Em primeiro lugar, gostaria que tal condição fosse escrita em uma forma "tabular" para melhorar a legibilidade. Consulte o capítulo N13 no mini-livro The Biggest Question of Programming, Refactoring, and All That .
if(!( lType == eLuxJournalState_OpenNote
|| lType == eLuxJournalState_OpenDiary
|| lType == eLuxJournalState_OpenNote
|| lType == eLuxJournalState_OpenNarratedDiary))
return false;
Desta forma, fica muito mais fácil detectar um erro, mesmo sem um analisador.
No entanto, surge a pergunta: essa verificação errônea leva à distorção da lógica do programa? Afinal, talvez você precise verificar algum outro valor de lType , mas a verificação foi perdida devido a um erro de copiar e colar. Vamos dar uma olhada na própria enumeração:
enum eLuxJournalState
{
eLuxJournalState_Main,
eLuxJournalState_Notes,
eLuxJournalState_Diaries,
eLuxJournalState_QuestLog,
eLuxJournalState_OpenNote,
eLuxJournalState_OpenDiary,
eLuxJournalState_OpenNarratedDiary,
eLuxJournalState_LastEnum,
};
Existem apenas três significados com a palavra "Abrir" no nome. E todos os três estão presentes no cheque. Muito provavelmente, não há distorção da lógica aqui, mas ainda é impossível dizer com certeza. Portanto, o analisador encontrou um erro lógico que o desenvolvedor do jogo poderia consertar ou encontrou um local escrito "feio" que valeria a pena reescrever para maior clareza.
Fragmento 3.
O caso a seguir é geralmente o exemplo mais óbvio de um erro de copiar e colar.
V778 Dois fragmentos de código semelhantes foram encontrados. Talvez seja um erro de digitação e a variável 'mvSearcherIDs' deva ser usada em vez de 'mvAttackerIDs'. LuxSavedGameTypes.cpp 615
void cLuxMusicHandler_SaveData::ToMusicHandler(....)
{
....
// Enemies
//Attackers
for(size_t i=0; i<mvAttackerIDs.Size(); ++i)
{
iLuxEntity *pEntity = apMap
->GetEntityByID(mvAttackerIDs[i]);
if(....)
{
....
}
else
{
Warning("....", mvAttackerIDs[i]);
}
}
//Searchers
for(size_t i=0; i<mvSearcherIDs.Size(); ++i)
{
iLuxEntity *pEntity = apMap->GetEntityByID(mvSearcherIDs[i]);
if(....)
{
....
}
else
{
Warning("....", mvAttackerIDs[i]);
}
}
}
No primeiro ciclo, o trabalho segue com o ponteiro pEntity , que foi recebido via mvAttackerIDs, e se a condição não for atendida, é emitida uma mensagem para depuração para os mesmos mvAttackerIDs . No entanto, no próximo loop, que é estilizado exatamente da mesma maneira que a seção anterior do código, pEntity é obtido usando mvSearcherIDs . Mas o aviso ainda é emitido com a menção de mvAttackerIDs .
Provavelmente, o bloco de código assinado "Searchers" foi copiado do bloco "Attackers", mvAttackerIDs foi substituído por mvSearcherIDs , mas o bloco else não foi alterado. Como resultado, a mensagem de erro usa um elemento da matriz errada.
Este erro não afeta a lógica do jogo, mas desta forma você pode colocar um porco sério em uma pessoa que terá que depurar este lugar e perder tempo trabalhando com o elemento mvSearcherIDs errado .

Fragmento 4.
O analisador indicou o seguinte local suspeito com três avisos:
- A expressão V547 'pEntity == 0' é sempre falsa. LuxScriptHandler.cpp 2444
- V649 Existem duas instruções 'if' com expressões condicionais idênticas. A primeira instrução 'if' contém o retorno da função. Isso significa que a segunda declaração 'if' não tem sentido. Verifique as linhas: 2433, 2444. LuxScriptHandler.cpp 2444
- V1051 Considere verificar se há erros de impressão. É possível que o 'pTargetEntity' deva ser verificado aqui. LuxScriptHandler.cpp 2444
Vejamos o código:
void __stdcall cLuxScriptHandler::PlaceEntityAtEntity(....)
{
cLuxMap *pMap = gpBase->mpMapHandler->GetCurrentMap();
iLuxEntity *pEntity = GetEntity(....);
if(pEntity == NULL) return;
if(pEntity->GetBodyNum() == 0)
{
....
}
iPhysicsBody *pBody = GetBodyInEntity(....);
if(pBody == NULL) return;
iLuxEntity *pTargetEntity = GetEntity(....);
if(pEntity == NULL) return; // <=
iPhysicsBody *pTargetBody = GetBodyInEntity(....);
if(pTargetBody == NULL) return;
....
}
Um aviso V547 foi emitido para a segunda verificação pEntity == NULL . Para o analisador, esta verificação sempre será falsa , pois se esta condição fosse verdadeira , a função sairia superior devido à verificação semelhante anterior.
O próximo aviso, V649 , foi emitido precisamente porque temos duas verificações idênticas. Normalmente, este caso pode não ser um erro. De repente, em uma parte do código, uma lógica é implementada, e em outra parte do código, de acordo com a mesma verificação, algo mais deve ser implementado. Mas, neste caso, o corpo do primeiro cheque consiste em devolução, então a segunda verificação, se a condição for verdadeira, nem mesmo alcançará o ponto. Ao rastrear essa lógica, o analisador reduz o número de mensagens falsas para códigos suspeitos e as envia apenas para lógicas muito estranhas.
O erro indicado pelo último aviso é inerentemente muito semelhante ao exemplo anterior. Provavelmente, todas as verificações foram duplicadas da primeira verificação if (pEntity == NULL) e, em seguida, o objeto verificado foi substituído pelo necessário. No caso de pBody e objetos pTargetBody, a substituição foi feita, mas o pTargetEntity objeto foi esquecido. Como resultado, este objeto não é verificado.
No exemplo que estamos considerando, se você se aprofundar um pouco mais no código, verá que esse erro em geral não afetará o curso do programa. O ponteiro pTargetBody obtém seu valor da função GetBodyInEntity :
iPhysicsBody *pTargetBody = GetBodyInEntity(pTargetEntity,
asTargetBodyName);
O primeiro argumento aqui é apenas um ponteiro não verificado anteriormente, que não é usado em nenhum outro lugar. E, felizmente, dentro desta função há uma verificação do primeiro argumento para NULL :
iPhysicsBody* ....::GetBodyInEntity(iLuxEntity* apEntity, ....)
{
if(apEntity == NULL){
return NULL;
}
....
}
Como resultado, esse código funciona corretamente no final, embora contenha um erro.
Fragmento 5.
E mais um lugar suspeito com copiar e colar!

Neste método, os campos do objeto da classe cLuxPlayer são zerados .
void cLuxPlayer::Reset()
{
....
mfRoll=0;
mfRollGoal=0;
mfRollSpeedMul=0; //<-
mfRollMaxSpeed=0; //<-
mfLeanRoll=0;
mfLeanRollGoal=0;
mfLeanRollSpeedMul=0;
mfLeanRollMaxSpeed=0;
mvCamAnimPos =0;
mvCamAnimPosGoal=0;
mfRollSpeedMul=0; //<-
mfRollMaxSpeed=0; //<-
....
}
Mas, por algum motivo, as duas variáveis mfRollSpeedMul e mfRollMaxSpeed são anuladas duas vezes:
- V519 A variável 'mfRollSpeedMul' recebe valores duas vezes sucessivamente. Talvez seja um engano. Verifique as linhas: 298, 308. LuxPlayer.cpp 308
- V519 A variável 'mfRollMaxSpeed' recebe valores duas vezes sucessivamente. Talvez seja um engano. Verifique as linhas: 299, 309. LuxPlayer.cpp 309
Vamos dar uma olhada na própria classe e ver quais campos ela contém:
class cLuxPlayer : ....
{
....
private:
....
float mfRoll;
float mfRollGoal;
float mfRollSpeedMul;
float mfRollMaxSpeed;
float mfLeanRoll;
float mfLeanRollGoal;
float mfLeanRollSpeedMul;
float mfLeanRollMaxSpeed;
cVector3f mvCamAnimPos;
cVector3f mvCamAnimPosGoal;
float mfCamAnimPosSpeedMul;
float mfCamAnimPosMaxSpeed;
....
}
Curiosamente, existem três blocos semelhantes de variáveis com nomes relacionados: mfRoll , mfLeanRoll e mvCamAnimPos . Em Reset, esses três blocos são limpos, exceto para as duas últimas variáveis do terceiro bloco, mfCamAnimPosSpeedMul e mfCamAnimPosMaxSpeed . E é exatamente no lugar dessas duas variáveis que as atribuições duplicadas são encontradas. Muito provavelmente, todas essas atribuições foram copiadas do primeiro bloco de atribuição e, em seguida, os nomes das variáveis foram substituídos pelos necessários.
Pode ser que duas variáveis ausentes não devam ter sido zeradas, mas o oposto também é muito provável. E as reatribuições obviamente não ajudarão a manter esse código. Como você pode ver, em um longo footcloth das mesmas ações, você pode não perceber tal erro, e o analisador ajuda aqui.
Fragmento 5.5.
Este exemplo é muito semelhante ao anterior. Vou lhe dar um trecho de código e um aviso do analisador para ele.
V519 A variável 'mfTimePos' recebe valores duas vezes sucessivamente. Talvez isso seja um erro. Verifique as linhas: 49, 53. AnimationState.cpp 53
cAnimationState::cAnimationState(....)
{
....
mfTimePos = 0;
mfWeight = 1;
mfSpeed = 1.0f;
mfBaseSpeed = 1.0f;
mfTimePos = 0;
mfPrevTimePos=0;
....
}
A variável mfTimePos recebeu o valor 0 duas vezes. Como no exemplo anterior, vamos dar uma olhada na declaração deste campo:
class cAnimationState
{
....
private:
....
//Properties of the animation
float mfLength;
float mfWeight;
float mfSpeed;
float mfTimePos;
float mfPrevTimePos;
....
}
Você pode ver que este bloco de declaração também corresponde à ordem de atribuição no trecho de código de erro, como no exemplo anterior. Aqui, na atribuição, em vez da variável mfLength, o valor obtém mfTimePos . Mas aqui o erro não pode ser explicado copiando o bloco e o "efeito da última linha". Pode ser que mfLength não precise ser atribuído a um novo valor, mas este local ainda é suspeito.
Fragmento 6.
O analisador emitiu um monte de avisos para o próximo fragmento de código de "Amnesia: A Machine For Pigs". Darei apenas uma parte do código para a qual foram emitidos erros do mesmo tipo:
void cLuxEnemyMover::UpdateMoveAnimation(float afTimeStep)
{
....
if(prevMoveState != mMoveState)
{
....
//Backward
if(mMoveState == eLuxEnemyMoveState_Backward)
{
....
}
....
//Walking
else if(mMoveState == eLuxEnemyMoveState_Walking)
{
bool bSync = prevMoveState == eLuxEnemyMoveState_Running
|| eLuxEnemyMoveState_Jogging
? true : false;
....
}
....
}
}
Onde está o erro aqui?
O analisador emitiu os seguintes avisos:
- V768 A constante de enumeração 'eLuxEnemyMoveState_Jogging' é usada como uma variável do tipo booleano. LuxEnemyMover.cpp 672
- V768 A constante de enumeração 'eLuxEnemyMoveState_Walking' é usada como uma variável do tipo booleano. LuxEnemyMover.cpp 680
- V768 A constante de enumeração 'eLuxEnemyMoveState_Jogging' é usada como uma variável do tipo booleano. LuxEnemyMover.cpp 688
A cadeia if-else-if é repetida no código original e, então, esses avisos foram emitidos apenas para cada corpo de cada else if .
Considere a linha apontada pelo analisador:
bool bSync = prevMoveState == eLuxEnemyMoveState_Running
|| eLuxEnemyMoveState_Jogging
? true : false;
Não é de surpreender que um erro tenha ocorrido em tal expressão, que também está escrita em uma linha no original. E provavelmente você já percebeu isso. O membro da enumeração eLuxEnemyMoveState_Jogging não é comparado com nada; seu valor é verificado. Muito provavelmente, a expressão 'prevMoveState == eLuxEnemyMoveState_Jogging' estava implícita.
Esse erro pode parecer totalmente inofensivo. Mas em meu outro artigo, sobre verificação do Bullet Engine , entre os commits do projeto, encontrei uma correção de bugdo mesmo tipo, resultando em forças aplicadas a objetos do lado errado. E, neste caso, esse erro foi cometido várias vezes. Bem, observo também que a condição ternária é completamente sem sentido, uma vez que será cumprida, em último lugar, aos resultados booleanos dos operadores lógicos.
Fragmento 7.
E, finalmente, o último par de exemplos de erros de copiar e colar. Desta vez, novamente em uma declaração condicional. O analisador emitiu um aviso para o seguinte trecho de código:
void iParticleEmitter::SetSubDivUV(const cVector2l &avSubDiv)
{
//Check so that there is any subdivision
// and that no sub divison axis is
//equal or below zero
if( (avSubDiv.x > 1 || avSubDiv.x > 1) && (avSubDiv.x >0 && avSubDiv.y >0))
{
....
}
....
}
Acho que é muito fácil notar um lugar suspeito em tal fragmento, separado de todo o código. No entanto, esse erro conseguiu se esconder dos desenvolvedores deste jogo.
O analisador emitiu o seguinte aviso:
V501 Existem subexpressões idênticas à esquerda e à direita de '||' operador: avSubDiv.x> 1 || avSubDiv.x> 1 ParticleEmitter.cpp 199
O segundo parêntesis nos mostra a condição de que ambos X e y campos são verificados . Mas no primeiro parêntese, por algum motivo este momento foi perdido e apenas o campo x é verificado... Além disso, a julgar pelo comentário de validação, ambos os campos deveriam ter sido validados. Aqui, de alguma forma, não foi o "efeito da última linha" que funcionou, mas sim o primeiro, porque no primeiro parêntese eles se esqueceram de substituir a chamada do campo x pela chamada do campo y .
Portanto, esses erros são bastante insidiosos, já que, neste caso, o desenvolvedor nem mesmo foi ajudado por escrever um comentário explicativo para a condição.
Eu recomendaria, em tais casos, tomar como hábito registrar os cheques relacionados em uma forma tabular. É mais fácil editar desta forma, e a falha será muito mais perceptível:
if( (avSubDiv.x > 1 || avSubDiv.x > 1)
&& (avSubDiv.x > 0 && avSubDiv.y > 0))
Fragmento 7.5.
Absolutamente o mesmo, na verdade, o erro foi encontrado em um lugar completamente diferente:
static bool EdgeTriEqual(const cTriEdge &edge1, const cTriEdge &edge2)
{
if(edge1.tri1 == edge2.tri1 && edge1.tri2 == edge2.tri2)
return true;
if(edge1.tri1 == edge1.tri1 && edge1.tri2 == edge2.tri1)
return true;
return false;
}
Bem, como você conseguiu ver imediatamente onde ela se escondeu? Não é à toa que tantos exemplos já foram classificados :)
O analisador emitiu o seguinte aviso:
V501 Existem subexpressões idênticas à esquerda e à direita do operador '==': edge1.tri1 == edge1.tri1 Math.cpp 2914
Vamos analisar isto fragmento em ordem. Obviamente, a primeira verificação verifica a igualdade dos campos edge1.tri1 e edge2.tri2 , e ao mesmo tempo a igualdade de edge1.tri2 e edge2.tri2 :
edge1.tri1 -> edge2.tri1
edge1.tri2 -> edge2.tri2
E na segunda verificação, a julgar pela parte correta da verificação 'edge1.tri2 == edge2.tri1', foi necessário verificar a igualdade desses campos "transversalmente":

Mas em vez de verificar edge1.tri1 == edge2.tri2 , uma verificação sem sentido foi executada edge1.tri1 == edge1.tri1 . Mas isso é todo o conteúdo da função, não apaguei nada dela. E, ao mesmo tempo, esse erro entrou no código.
Outros erros
Fragmento 1.
Darei o seguinte fragmento de código com recuos originais.
void iCharacterBody::CheckMoveCollision(....)
{
....
/////////////////////////////////////
//Forward velocity reflection
//Make sure that new velocity points in the right direction
//and that it is not too large!
if(mfMoveSpeed[eCharDir_Forward] != 0)
{
vForwardVel = ....;
float fForwardSpeed = vForwardVel.Length();
if(mfMoveSpeed[eCharDir_Forward] > 0)
if(mfMoveSpeed[eCharDir_Forward] > fForwardSpeed)
mfMoveSpeed[eCharDir_Forward] = fForwardSpeed;
else
if(mfMoveSpeed[eCharDir_Forward] < fForwardSpeed)
mfMoveSpeed[eCharDir_Forward] = -fForwardSpeed;
}
....
}
Aviso do PVS-Studio: V563 É possível que esta ramificação 'else' deva ser aplicada à instrução 'if' anterior. CharacterBody.cpp 1591
Este exemplo pode ser confuso. Por que outro motivo é indentado da mesma forma que o if externo ? É o else para a condição externa? Bem, então, você precisa colocar os parênteses, caso contrário, else se refere ao if imediatamente anterior .
if(mfMoveSpeed[eCharDir_Forward] > 0)
{
if(mfMoveSpeed[eCharDir_Forward] > fForwardSpeed)
mfMoveSpeed[eCharDir_Forward] = fForwardSpeed;
}
else if(mfMoveSpeed[eCharDir_Forward] < fForwardSpeed)
{
mfMoveSpeed[eCharDir_Forward] = -fForwardSpeed;
}
Ou não? No processo de escrever este artigo, mudei minha opinião várias vezes sobre qual variante da sequência de ações concebida para este código é mais provável.
Se nos aprofundarmos um pouco mais neste código, descobrimos que a variável fForwardSpeed , com a qual a comparação é realizada no menor if , não pode ter um valor menor que zero, pois recebe um valor do método Length :
inline T Length() const
{
return sqrt( x * x + y * y + z * z);
}
Então, provavelmente, a essência dessas verificações é que primeiro verificamos se o elemento mfMoveSpeed é maior que zero e, em seguida, verificamos seu valor em relação a fForwardSpeed . Além disso, os dois últimos ifs correspondem um ao outro em sua redação.
Nesse caso, o código original funcionará conforme o esperado! Mas com certeza fará a pessoa que vier editar / refatorar um quebra-cabeça.
Achei que nunca encontraria um código assim. Sem interesse, examinei nosso banco de dados de erros encontrados em projetos de código aberto e descritos em artigos. E exemplos desse erro foram encontrados em outros projetos - você pode olhar para eles mesmo .
E, por favor, não escreva assim, mesmo que você mesmo seja claro neste caso. Ou chaves ou recuo correto, ou melhor, ambos. Não mergulhe no sofrimento daqueles que vierem a entender o seu código e a si próprios no futuro;)
Fragmento 2.
O próximo erro me confundiu um pouco, e tentei encontrar a lógica aqui por muito tempo. Mas no final, ainda me parece que isso é provavelmente um erro, e um tanto grosseiro.
Vamos dar uma olhada no código:
bool cBinaryBuffer::DecompressAndAdd(char *apSrcData, size_t alSize)
{
....
///////////////////////////
// Init decompression
int ret = inflateInit(&zipStream);
if (ret != Z_OK) return false;
///////////////////////////
// Decompress, chunk by chunk
do
{
//Set current output chunk
zipStream.avail_out = lMaxChunkSize;
....
//Decompress as much as possible to current chunk
int ret = inflate(&zipStream, Z_NO_FLUSH);
if(ret != Z_OK && ret != Z_STREAM_END)
{
inflateEnd(&zipStream);
return false;
}
....
}
while (zipStream.avail_out == 0 && ret != Z_STREAM_END);
....
return true;
}
V711 É perigoso criar uma variável local dentro de um loop com o mesmo nome de uma variável que controla este loop. BinaryBuffer.cpp 371
Portanto, temos uma variável ret , que controla a saída do loop do-while . Mas dentro desse loop, em vez de atribuir um novo valor a essa variável externa, uma nova variável chamada ret é declarada . Como resultado, ele substitui a variável externa ret, e a variável que é verificada na condição de loop nunca mudará.
Em uma combinação infeliz de circunstâncias, esse ciclo pode se tornar infinito. Muito provavelmente, neste caso, este código salva uma condição interna que verifica o valor da variável interna ret e leva para sair da função.

Conclusão
Muitas vezes, os desenvolvedores não usam a análise estática regularmente, mas com longos intervalos. Ou até executam o projeto pelo analisador uma vez. Como resultado desta abordagem, o analisador muitas vezes não detecta nada sério ou encontra algo semelhante aos exemplos que estamos considerando, o que talvez não tenha afetado particularmente a funcionalidade do jogo. Tem-se a impressão de que o analisador não é particularmente útil. Bem, ele encontrou esses lugares, mas ainda funciona.
E o fato é que lugares semelhantes, mas em que o erro não foi mascarado, mas claramente levou a um bug no programa, já foram corrigidos por longas horas de depuração, execução de testes e do departamento de testes. Como resultado, ao verificar um projeto, o analisador mostra apenas aqueles problemas que não se manifestaram de forma alguma. Às vezes, entre esses problemas, há também momentos graves que realmente influenciaram o funcionamento do programa, mas a probabilidade de o programa seguir seu caminho era baixa e, portanto, esse erro era desconhecido para os desenvolvedores.
Portanto, é extremamente importante avaliar a utilidade da análise estática somente após seu uso regular. Se uma corrida única pelo PVS-Studio revelou tais lugares suspeitos e descuidados no código deste jogo, então quantos erros óbvios desse tipo tiveram que ser localizados e corrigidos no processo de desenvolvimento.
Use um analisador estático regularmente!

Se você quiser compartilhar este artigo com um público que fala inglês, use o link de tradução: Victoria Khanieva. Amnesia: The Dark Descent ou como esquecer de corrigir o Copy Paste .