亚马逊伐木场:灵魂的呐喊


游戏是最受欢迎的软件产品之一。 这是一个巨大的行业,其中新的游戏引擎-Amazon Lumberyard。 该项目仍处于测试阶段,它有时间修复错误并提高代码质量。 引擎的开发人员在不久的将来要做很多工作,以免使数百万游戏玩家和游戏开发人员失望。

引言


Amazon Lumberyard是由Amazon开发的免费AAA跨平台游戏引擎,基于CryEngine引擎架构,该架构于2015年获得Crytek的许可。 顺便说一句,我在20168月2017 4月已经对CryEngine进行了两次分析。 同时,我必须指出,一年之后,代码只会变得更糟。 前几天,我决定看看亚马逊基于该游戏引擎做了什么。 他们在环境方面做得很好。 开发人员和用于部署工作环境的软件的文档非常酷,而且层次很高。 但是麻烦又出在代码上! 我希望亚马逊有更多的资源来处理该项目,并且他们仍然关注代码的质量。 通过这次回顾,我希望引起开发人员对代码质量的关注,并推动在此游戏引擎的开发中寻求新方法。 代码的当前状态处于令人沮丧的状态,以至于当我查看带有分析结果的报告时,我多次更改了文章标题并重新绘制了标题图片。 图片的第一版不那么令人激动:



我们分析了最新可用版本1.14.0.1的Amazon Lumberyard的来源。 源代码来自Github上的存储库。 Lumberyard引擎上最早的游戏之一应该是Star Citizen 。 等待她的潜在玩家,我也邀请您看看游戏的“幕后花絮”。

与PVS-Studio集成


PVS-Studio被用作静态代码分析器。 它可用于Windows,Linux和macOS。 即 对于跨平台项目的分析,甚至还可以选择一些更舒适的工作。 除了C和C ++,还支持使用C#语言进行项目分析。 Java计划 。 世界上绝大多数代码都是用列出的语言编写的(当然,并非没有错误),因此在您的项目上尝试使用PVS-Studio分析仪,您将学到很多有趣的东西;-)。

作为Lumberyard装配系统,使用了WAF,该系统也在CryEngine中使用。 分析仪没有与该装配系统集成的特殊方法。 我决定在Windows上处理一个项目,并选择了这种开始分析的方法: 编译监视系统 。 Visual Studio的项目文件是自动生成的。 它可用于构建项目和查看分析器报告。

用于分析的命令列表如下所示:

cd /path/to/lumberyard/dev lmbr_waf.bat ... CLMonitor.exe monitor MSBuild.exe ... LumberyardSDK_vs15.sln ... CLMonitor.exe analyze --log /path/to/report.plog 

正如我所说,该报告可以在Visual Studio中查看。

关于伊戈尔和高通


Amazon Lumberyard将自己定位为跨平台游戏引擎。 利用这种功能向大众推广项目很容易,但是维护起来却很困难。 PVS-Studio发出的警告之一是在代码片段上发出的,程序员Igor与Qualcomm编译器进行了对抗。 也许他解决了他的问题,但是留下了一个非常可疑的代码。 我决定用图片来画。

V523'then '语句等效于'else'语句。 toglsloperand.c 700


在此,无论计算出的条件如何,均执行相同的代码。 在留下评论的背景下,这样的决定显得可疑。

通常,项目不是唯一需要简化条件以使其清晰或纠正实际错误的地方。 这是这些地方的列表:

  • V523'then'语句等效于'else'语句。 第1385章
  • V523'then'语句等效于'else'语句。 tometalinstruction.c 4201
  • V523'then'语句等效于'else'语句。 脚本表cpp 905
  • V523'then'语句等效于'else'语句。 预算系统.cpp 701
  • V523'then'语句等效于'else'语句。 editorframeworkapplication.cpp 562
  • V523'then'语句等效于'else'语句。 粒子项目cpp 130
  • V523'then'语句等效于'else'语句。 trackviewnodes.cpp 1223
  • V523'then'语句等效于'else'语句。 propertyoarchive.cpp 447

Python ++




分析器发现了这样一个有趣的代码:

V709 CWE-682发现可疑比较:“ a == b == c”。 请记住,“ a == b == c”不等于“ a == b && b == c”。 第564章

 void CallBinaryOp(....) { .... uint32_t src1SwizCount = GetNumSwizzleElements(....); uint32_t src0SwizCount = GetNumSwizzleElements(....); uint32_t dstSwizCount = GetNumSwizzleElements(....); .... if (src1SwizCount == src0SwizCount == dstSwizCount) // <= { .... } .... } 

不幸的是,在C ++中,这样的代码可以成功编译,但看起来却根本无法执行。 C ++中的表达式按优先级求值,并在必要时执行隐式种姓。

例如,在Python语言中,这种检查是正确的。 但是在这种情况下,开发人员“陷入困境”。

另外3张控制照:

  • V709 CWE-682发现可疑比较:“ a == b == c”。 请记住,“ a == b == c”不等于“ a == b && b == c”。 第654章
  • V709 CWE-682发现可疑比较:“ a == b == c”。 请记住,“ a == b == c”不等于“ a == b && b == c”。 第469章
  • V709 CWE-682发现可疑比较:“ a == b == c”。 请记住,“ a == b == c”不等于“ a == b && b == c”。 金属指令c 539

最好的诊断方法之一




这将与警告V501有关-PVS -Studio中的第一个通用诊断。 仅凭此诊断发现的错误就足以写一篇文章。 Amazon Lumberyard项目很好地证明了这一点。

不幸的是,长时间查看相同类型的错误很无聊,因此在本节中,我将仅评论几个代码片段,其余警告将被列出。

V501在'||'的左侧和右侧有相同的子表达式 运算符:hotX <0 || hotX <0编辑器utils.cpp 166

 QCursor CMFCUtils::LoadCursor(....) { .... if (!pm.isNull() && (hotX < 0 || hotX < 0)) { QFile f(path); f.open(QFile::ReadOnly); QDataStream stream(&f); stream.setByteOrder(QDataStream::LittleEndian); f.read(10); quint16 x; stream >> x; hotX = x; stream >> x; hotY = x; } .... } 

条件缺少变量hotY。 经典错字。

V501在'&&'运算符的左侧和右侧有相同的子表达式'sp.m_pTexture == m_pTexture'。 shadercomponents.h 487

V501在'&&'运算符的左侧和右侧有相同的子表达式'sp.m_eCGTextureType == m_eCGTextureType'。 shadercomponents.h 487

 bool operator != (const SCGTexture& sp) const { if (sp.m_RegisterOffset == m_RegisterOffset && sp.m_Name == m_Name && sp.m_pTexture == m_pTexture && // <= 1 sp.m_RegisterCount == m_RegisterCount && sp.m_eCGTextureType == m_eCGTextureType && // <= 2 sp.m_BindingSlot == m_BindingSlot && sp.m_Flags == m_Flags && sp.m_pAnimInfo == m_pAnimInfo && sp.m_pTexture == m_pTexture && // <= 1 sp.m_eCGTextureType == m_eCGTextureType && // <= 2 sp.m_bSRGBLookup == m_bSRGBLookup && sp.m_bGlobal == m_bGlobal) { return false; } return true; } 

一次在此片段中发现了两个复制粘贴。 为了清楚起见,我画了箭头。

V501在'=='运算符的左侧和右侧有相同的子表达式:pSrc.GetLen()== pSrc.GetLen()fbxpropertytypes.h 978

 inline bool FbxTypeCopy(FbxBlob& pDst, const FbxString& pSrc) { bool lCastable = pSrc.GetLen() == pSrc.GetLen(); FBX_ASSERT( lCastable ); if( lCastable ) pDst.Assign(pSrc.Buffer(), (int)pSrc.GetLen()); return lCastable; } 

在这里,我想向AUTODESK的开发人员问好。 此错误来自其FBX SDK库。 混淆变量pSrcpDst 。 我认为,除了Lumberyard之外,还有许多其他用户的项目依赖于此错误的代码。

V501 '&&'运算符的左侧和右侧都有相同的子表达式:pTS-> pRT_ALD_1 && pTS-> pRT_ALD_1 d3d_svo.cpp 857

 void CSvoRenderer::ConeTracePass(SSvoTargetsSet* pTS) { .... if (pTS->pRT_ALD_1 && pTS->pRT_ALD_1) { static int nPrevWidth = 0; if (....) { .... } else { pTS->pRT_ALD_1->Apply(10, m_nTexStateLinear); pTS->pRT_RGB_1->Apply(11, m_nTexStateLinear); } } .... } 

返回Lumberyard代码。 在这种情况下,检查相同的指针pTS-> pRT_ALD_1 。 其中之一应该是pTS-> pRT_RGB_1 。 也许即使在解释之后,也无法立即看到差异,但有一个差异:差异在于短子串ALDRGB 。 如果系统提示您手动进行代码审查就足够了,请显示此示例。

如果这个例子还不够,那么还有5个类似的例子。
  • V501在'||'的左侧和右侧有相同的子表达式 运算符:!pTS-> pRT_ALD_0 ||!pTS-> pRT_ALD_0 d3d_svo.cpp 1041
  • V501'&&'运算符的左侧和右侧有相同的子表达式:m_pRT_AIR_MIN && m_pRT_AIR_MIN d3d_svo.cpp 1808
  • V501'&&'运算符的左侧和右侧有相同的子表达式:m_pRT_AIR_MAX && m_pRT_AIR_MAX d3d_svo.cpp 1819
  • V501'&&'运算符的左侧和右侧有相同的子表达式:m_pRT_AIR_SHAD && m_pRT_AIR_SHAD d3d_svo.cpp 1830
  • V501'&&'运算符的左侧和右侧有相同的子表达式:s_pPropertiesPanel && s_pPropertiesPanelentityobject.cpp 1700

正如我所承诺的,这是其余V501警告的列表,没有代码示例:
展开清单
  • V501在“ ||”的左侧和右侧有相同的子表达式“ MaxX <0” 操作员。 czbufferculler.h 128
  • V501有相同的子表达式'm_joints [op [1]]。在'-'运算符的左侧和右侧限制[1] [i]'。 第795章
  • V501在'-'运算符的左侧和右侧有相同的子表达式'm_joints [i] .limits [1] [j]'。 第2044章
  • V501在'|'的左侧和右侧有相同的子表达式'irect [0] .x +1-irect [1] .x >> 31' 操作员。 trimesh.cpp 4029
  • V501在“ ||”的左侧和右侧有相同的子表达式“ b-> mlen <= 0” 操作员。 bstrlib.c 1779
  • V501在“ ||”的左侧和右侧有相同的子表达式“ b-> mlen <= 0” 操作员。 bstrlib.c 1827
  • V501在“ ||”的左侧和右侧有相同的子表达式“ b-> mlen <= 0” 操作员。 bstrlib.c 1865
  • V501在“ ||”的左侧和右侧有相同的子表达式“ b-> mlen <= 0” 操作员。 bstrlib.c 1779
  • V501在“ ||”的左侧和右侧有相同的子表达式“ b-> mlen <= 0” 操作员。 bstrlib.c 1827
  • V501在“ ||”的左侧和右侧有相同的子表达式“ b-> mlen <= 0” 操作员。 bstrlib.c 1865
  • V501'-'运算符的左侧和右侧有相同的子表达式:dd-dd finalizingspline.h 669
  • V501在'^'运算符的左侧和右侧有相同的子表达式'pVerts [2]-pVerts [3]'。 roadrendernode.cpp 307
  • V501在“ ||”的左侧和右侧有相同的子表达式“!PGroup-> GetStatObj()”。 操作员。 terrain_node.cpp 594
  • V501在'||'的左侧和右侧有相同的子表达式 运算符:val == 0 || val ==-0 xmlcpb_attrwriter.cpp 367
  • V501在“ |”的左侧和右侧有相同的子表达式“ geom_colltype_solid” 操作员。 附件管理器.cpp 1058
  • V501在“ |”的左侧和右侧有相同的子表达式“(TriMiddle-RMWPosition)” 操作员。 modelmesh.cpp 174
  • V501在'|'的左边和右边有相同的子表达式'(goal-pAbsPose [b3] .t)'。 操作员。 posemodifierhelper.cpp 115
  • V501在'|'的左侧和右侧有相同的子表达式'(goal-pAbsPose [b4] .t)'。 操作员。 posemodifierhelper.cpp 242
  • V501在“ ||”的左侧和右侧有相同的子表达式“(m_eTFSrc == eTF_BC6UH)”。 操作员。 第983章
  • V501'-'运算符左右两侧有相同的子表达式:q2.vz-q2.vz azentitynode.cpp 102
  • V501'-'运算符的左侧和右侧有相同的子表达式:q2.vz-q2.vz entitynode.cpp 107
  • V501在'||'的左侧和右侧有相同的子表达式'm_listRect.contains(event-> pos())'。 操作员。 aidebuggerview.cpp 463
  • V501'&&'运算符的左侧和右侧有相同的子表达式:pObj-> GetParent()&& pObj-> GetParent()designerpanel.cpp 253

关于相机在游戏中的位置




PVS-Studio- V502中的诊断速度第二快。 该诊断比某些新的编程语言要早,在新的编程语言中,不再会出现这种错误。 对于C ++,此错误可能是相关的,也许总是如此。

让我们从一个简单的例子开始。

V502也许'?:'运算符的工作方式与预期的不同。 '?:'运算符的优先级低于'+'运算符。 zipencryptor.cpp 217

 bool ZipEncryptor::ParseKey(....) { .... size_t pos = i * 2 + (v1 == 0xff) ? 1 : 2; RCLogError("....", pos); return false; .... } 

加法运算的优先级高于三元运算符。 因此,该表达式的计算逻辑与作者假设的完全不同。

您可以通过以下方式解决错误:

 size_t pos = i * 2 + (v1 == 0xff ? 1 : 2); 

V502也许'?:'运算符的工作方式与预期的不同。 '?:'运算符的优先级低于'-'运算符。 3dengine.cpp 1898

 float C3DEngine::GetDistanceToSectorWithWater() { .... return (bCameraInTerrainBounds && (m_pTerrain && m_pTerrain->GetDistanceToSectorWithWater() > 0.1f)) ? m_pTerrain->GetDistanceToSectorWithWater() : max(camPostion.z - OceanToggle::IsActive() ? OceanRequest::GetOceanLevel() : GetWaterLevel(), 0.1f); } 

这是一个示例代码,它们可用于相机位置。 一个例子很难用眼睛察觉,并且有一个错误。 为了发布,代码的格式已更改,但是在源文件中,此代码甚至更不可读。

该子字符串中存在错误:

 camPostion.z - OceanToggle::IsActive() ? .... : .... 

如果游戏中的摄像头突然开始出现异常行为,那么您应该知道开发人员保存了静态代码分析:D。

带有类似警告的其他示例:

  • V502也许'?:'运算符的工作方式与预期的不同。 '?:'运算符的优先级低于'-'运算符。 scriptbind_ai.cpp 5203
  • V502也许'?:'运算符的工作方式与预期的不同。 '?:'运算符的优先级低于'+'运算符。 第136章
  • V502也许'?:'运算符的工作方式与预期的不同。 '?:'运算符的优先级低于'&&'运算符。 shapetool.h 98

CryEngine旧版


Amazon Lumberyard基于CryEngine代码。 而不是最佳版本。 通过查看分析仪报告,我得出了这样的结论。 在我的两次代码审查中,发现的一些错误已在最新版本的CryEngine中修复,但仍然存在于Lumberyard代码中。 另外,在过去的一年中,分析器得到了显着改进,并且有可能发现两个游戏引擎中现在存在的其他错误。 但是有了Lumberyard,情况变得更糟。 亚马逊基本上继承了CryEngine的所有技术债务。 但是,他自己的技术职责当然会在每个公司中自己出现:)。

在本节中,我将为您提供一些在最新版本的CryEngine中修复的错误,现在仅是Lumberyard项目的问题。

V519'BlendFactor [2]'变量连续两次被赋值。 也许这是一个错误。 检查行:1283,1284。ccrydxgldevicecontext.cpp 1284



当Lumberyard开发人员发现此错误仅存在于他们身上时,大约会遇到这种情绪。

顺便说一句,还有两个:

  • V519连续两次给'm_auBlendFactor [2]'变量赋值两次。 也许这是一个错误。 检查行:919、920。ccrydxgldevicecontext.cpp 920
  • V519连续两次给'm_auBlendFactor [2]'变量赋值两次。 也许这是一个错误。 检查行:926、927。ccrydxgldevicecontext.cpp 927

出现这样的错误:

V546类的成员由其自身初始化:'eConfigMax(eConfigMax.VeryHigh)'。 1837年

 ParticleParams() : .... fSphericalApproximation(1.f), fVolumeThickness(1.0f), fSoundFXParam(1.f), eConfigMax(eConfigMax.VeryHigh), // <= fFadeAtViewCosAngle(0.f) {} 

在CryEngine中,通常重写了该类,但此处仍然存在初始化错误。

V521使用','运算符的此类表达式很危险。 确保表达式“!SWords [iWord] .empty(),iWord ++”正确。 tacticalpointsystem.cpp 3376

 bool CTacticalPointSystem::Parse(....) const { string sInput(sSpec); const int MAXWORDS = 8; string sWords[MAXWORDS]; int iC = 0, iWord = 0; for (; iWord < MAXWORDS; !sWords[iWord].empty(), iWord++) { sWords[iWord] = sInput.Tokenize("_", iC); } .... } 

CryEngine也已经重写了可疑循环。

错误的寿命比您想象的更长


对于第一次开始使用PVS-Studio的用户,情况大致相同:他们发现一个错误,发现它是几个月前添加的,并且很高兴地意识到他们奇迹般地避免了用户中问题的出现。 经历了这样的故事之后,我们的许多客户开始定期使用PVS-Studio。

有时,为了启动代码质量控制流程,公司必须多次访问这种情况。 这是有关CryEngine和Lumberyard的示例:

V557 CWE-119阵列可能超限。 'id'索引指向数组边界之外。 gameobjectsystem.cpp 113

 uint32 CGameObjectSystem::GetExtensionSerializationPriority(....) { if (id > m_extensionInfo.size()) { return 0xffffffff; // minimum possible priority } else { return m_extensionInfo[id].serializationPriority; } } 

如您所知,Amazon Lumberyard不是基于最新版本的CryEngine。 但是,借助PVS-Studio分析仪,有可能发现两个游戏引擎中当前存在的错误。 有必要使用运算符'> ='检查索引...

索引错误很严重。 而且,有六个这样的地方 这是另一个示例:

V557 CWE-119阵列可能超限。 “索引”索引指向数组边界之外。 carseatgroup.cpp 73

 CVehicleSeat* CVehicleSeatGroup::GetSeatByIndex(unsigned index) { if (index >= 0 && index <= m_seats.size()) { return m_seats[index]; } return NULL; } 

有人犯了相同类型的错误,而这些错误并没有得到解决,因为它们一次也没有被包括在CryEngine Bug评论中。

剩余警告:

  • V557 CWE-119阵列可能超限。 'id'索引指向数组边界之外。 gameobjectsystem.cpp 195
  • V557 CWE-119阵列可能超限。 'id'索引指向数组边界之外。 gameobjectsystem.cpp 290
  • V557 CWE-119阵列可能超限。 'stateId'索引指向数组边界之外。 车辆动画cpp 311
  • V557 CWE-119阵列可能超限。 'stateId'索引指向数组边界之外。 车辆动画cpp 354

代码中长期存在的错误只能通过相应级别的项目测试来解释。 据信,静态分析只能在未使用的代码中发现错误。 因此,事实并非如此。 开发人员忘记了,大多数用户静默地遭受程序中不明显的错误,而这些非常罕见的错误的出现通常会对整个公司的工作,声誉及其销售(如果有)产生不利影响。

复制粘贴编程的不同阴影




当您到达本文的这一部分时,您可能会注意到复制粘贴编程是造成许多问题的原因。 在PVS-Studio中,通过各种诊断来实现对此类错误的搜索。 本节将提供V561附带的复制粘贴示例

这是在重叠范围中声明具有相同名称的变量时的可疑代码示例。

V561 CWE-563将值分配给'pLibrary'变量可能比重新声明它更好。 先前的声明:entityobject.cpp,第4703行。Entityobject.cpp4706

 void CEntityObject::OnMenuConvertToPrefab() { .... IDataBaseLibrary* pLibrary = GetIEditor()->Get....; if (pLibrary == NULL) { IDataBaseLibrary* pLibrary = GetIEditor()->Get....; } if (pLibrary == NULL) { QString sError = tr(....); CryMessageBox(....); return; } .... } 

pLibrary指针不会按预期覆盖。 该指针的初始化在条件和类型声明的情况下被完全复制。

我将列出所有类似的地方:

  • V561 CWE-563将值分配给'eType'变量可能比重新声明它更好。 先前的声明:toglsloperand.c,第838行。toglsloperand.c 1224
  • V561 CWE-563将值分配给'eType'变量可能比重新声明它更好。 先前的声明:toglsloperand.c,第838行。toglsloperand.c 1305
  • V561 CWE-563最好给'rSkelPose'变量赋值,而不是重新声明它。 先前的声明:attachmentmanager.cpp,第409行。attachmentmanager.cpp458
  • V561 CWE-563将值分配给'nThreadID'变量可能比重新声明它更好。 先前的声明:d3dmeshbaker.cpp,第797行。d3dmeshbaker.cpp 867
  • V561 CWE-563将值分配给'directoryNameList'变量可能比重新声明它更好。 先前的声明:assetimportermanager.cpp,第720行。assetimportermanager.cpp 728
  • V561 CWE-563将值分配给'pNode'变量可能比重新声明它更好。 先前的声明:breakpointsctrl.cpp,第340行。breakpointsctrl.cpp 349
  • V561 CWE-563将值分配给'pLibrary'变量可能比重新声明它更好。 先前的声明:prefabobject.cpp,行1443。prefabobject.cpp 1446
  • V561 CWE-563将值分配给'pLibrary'变量可能比重新声明它更好。 先前的声明:prefabobject.cpp,第1470行。prefabobject.cpp 1473
  • V561 CWE-563最好给'cmdLine'变量赋值而不是重新声明。 先前的声明:fileutil.cpp,第110行。fileutil.cpp 130
  • V561 CWE-563将值赋给'sfunctionArgs'变量可能比重新声明它更好。 先前的声明:attributeitemlogiccallbacks.cpp,第291行。attributeitemlogiccallbacks.cpp 303
  • V561 CWE-563将值赋给'curveName'变量可能比重新声明它更好。 先前的声明:qgradientselectorwidget.cpp,第475行。qgradientselectorwidget.cpp 488

一长串...列出的一些地方甚至是所描述示例的完整副本。

特征值初始化




在游戏引擎的代码中,发现了很多将变量分配给自身的地方。 这些代码留给了调试的地方,代码只是经过精心设计的地方(它通常也是错误的来源),因此,我将为您提供一段对我来说最可疑的代码。

V570将'behaviorParams.ignoreOnVehicleDestroyed'变量分配给它自己。 vehiclecomponent.cpp 168

 bool CVehicleComponent::Init(....) { .... if (!damageBehaviorTable.getAttr(....) { behaviorParams.ignoreOnVehicleDestroyed = false; } else { behaviorParams.ignoreOnVehicleDestroyed = // <= behaviorParams.ignoreOnVehicleDestroyed; // <= } .... } 

在当前视图中,根本不需要else分支。 但是也许这段代码包含一个错误:他们想为变量赋相反的值:

 bValue = !bValue 

但是,开发人员最好熟悉此诊断的结果。

问题处理




本节将提供许多示例,说明在错误处理期间出现问题时。

例子1

V606无主令牌'nullptr'。 dx12rootsignature.cpp 599

 RootSignature* RootSignatureCache::AcquireRootSignature(....) { .... RootSignature* result = new RootSignature(m_pDevice); if (!result->Init(params)) { DX12_ERROR("Could not create root signature!"); nullptr; } m_RootSignatureMap[hash] = result; return result; } } 

忘记写返回nullptr; 。 现在, 结果变量的无效值将在代码的其他地方使用。

完全相同的代码被复制到另一个地方:

  • V606无主令牌'nullptr'。 dx12rootsignature.cpp 621

例子2

V606无主令牌'false'。 fillspacetool.cpp 191

 bool FillSpaceTool::FillHoleBasedOnSelectedElements() { .... if (validEdgeList.size() == 2) { .... } if (validEdgeList.empty()) { .... for (int i = 0, iVertexSize(....); i < iVertexSize; ++i) { validEdgeList.push_back(....); } } if (validEdgeList.empty()) // <= { false; // <= fail } std::vector<BrushEdge3D> linkedEdgeList; std::set<int> usedEdgeSet; linkedEdgeList.push_back(validEdgeList[0]); // <= fail .... } 

缺少return语句的错误的非常有趣的例子。 现在可以访问一个空容器来访问索引了。

例子3

V564 CWE-480'&'运算符应用于布尔类型值。 您可能忘记了括号或打算使用'&&'运算符。 2914年

 void SetDataTypes(....) { .... // Check assumption that both the values which MOVC might pick // have the same basic data type. if(!psContext->flags & HLSLCC_FLAG_AVOID_TEMP_REGISTER_ALIASING) { ASSERT(GetOperandDataType(psContext, &psInst->asOperands[2]) == GetOperandDataType(psContext, &psInst->asOperands[3])); } .... } 

错误地检查了标志中是否存在位。 否定运算符应用于标志值,而不是整个表达式。 正确地这样写:

 if(!(psContext->flags & ....)) 

更多类似的警告:

  • V564 CWE-480'|' 运算符应用于布尔类型值。 您可能忘记了括号或打算使用'||' 操作员。 d3dhwshader.cpp 1832
  • V564 CWE-480'&'运算符应用于布尔类型值。 您可能忘记了括号或打算使用'&&'运算符。 trackviewdialog.cpp 2112
  • V564 CWE-480'|' 运算符应用于布尔类型值。 您可能忘记了括号或打算使用'||' 操作员。 imagecompiler.cpp 1039

例子4

V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1491

 static std::vector<std::string> PyGetPrefabLibrarys() { CPrefabManager* pPrefabManager = GetIEditor()->GetPrefabMa....; if (!pPrefabManager) { std::runtime_error("Invalid Prefab Manager."); } .... } 

引发异常时出错。 有必要这样写:

 throw std::runtime_error("Invalid Prefab Manager."); 

此类错误的完整列表:

  • V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1515
  • V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1521
  • V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1543
  • V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1549
  • V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1603
  • V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1619
  • V596 CWE-390已创建对象,但未使用该对象。 可能没有'throw'关键字:throw runtime_error(FOO); prefabobject.cpp 1644


使用内存时的几个问题




V549 CWE- 688'memcmp '函数的第一个参数等于第二个参数。 meshutils.h 894

 struct VertexLess { .... bool operator()(int a, int b) const { .... if (m.m_links[a].links.size() != m.m_links[b].links.size()) { res = (m.m_links[a].links.size() < m.m_links[b].links.size()) ? -1 : +1; } else { res = memcmp(&m.m_links[a].links[0], &m.m_links[a].links[0], sizeof(m.m_links[a].links[0]) * m.m_links[a].links.size()); } .... } .... }; 

该条件比较两个向量的大小。如果它们相等,那么在else分支中,使用memcmp()函数比较向量的第一元素但是此函数的第一个和第二个参数相同!访问数组元素相当麻烦。有索引ab他们中最有可能出现错字。

V611 CWE-762使用“ new T []”运算符分配了内存,但使用“ delete”运算符释放了内存。考虑检查此代码。最好使用'delete [] data;'。向量n.h 102

 ~vectorn_tpl() { if (!(flags & mtx_foreign_data)) { delete[] data; } } vectorn_tpl& operator=(const vectorn_tpl<ftype>& src) { if (src.len != len && !(flags & mtx_foreign_data)) { delete data; // <= data = new ftype[src.len]; } .... } 

数据指针存储器正在使用无效的语句释放。delete []运算符必须在所有地方使用

无法访问的代码


V779 CWE-561检测不到代码。可能存在错误。fbxskinimporter.cpp 67

 Events::ProcessingResult FbxSkinImporter::ImportSkin(....) { .... if (BuildSceneMeshFromFbxMesh(....) { context.m_createdData.push_back(std::move(createdData)); return Events::ProcessingResult::Success; // <= } else { return Events::ProcessingResult::Failure; // <= } context.m_createdData.push_back(); // <= fail return Events::ProcessingResult::Success; } 

条件语句的所有分支以函数的退出结束。但是,某些代码未执行。

V779 CWE-561检测不到代码。可能存在错误。第153章

 bool DockableLibraryTreeView::Init(IDataBaseLibrary* lib) { .... if (m_treeView && m_titleBar && m_defaultView) { if (m_treeView->topLevelItemCount() > 0) { ShowTreeView(); } else { ShowDefaultView(); } return true; // <= } else { return false; // <= } emit SignalFocused(this); // <= fail } 

在这段代码中很容易发现错误。但是,如果您长时间编写代码,则注意力会急剧下降,并且此类错误很容易落入项目中。

V622 CWE-478考虑检查“ switch”语句。第一个“ case”运算符可能会丢失。datum.cpp 872

 AZ_INLINE bool IsDataGreaterEqual(....) { switch (type.GetType()) { AZ_Error("ScriptCanvas", false, "...."); return false; case Data::eType::Number: return IsDataGreaterEqual<Data::NumberType>(lhs, rhs); .... case Data::eType::AABB: AZ_Error("ScriptCanvas", false, "....", Data::Traits<Data::AABBType>::GetName()); return false; case Data::eType::OBB: AZ_Error("ScriptCanvas", false, "....", Data::Traits<Data::OBBType>::GetName()); return false; .... } 

如果开关包含在case / default语句之外的代码,则永远不会执行它。

结论


本文包括95个分析器警告,其中25个带有代码示例。整个分析仪报告中有多少材料?我迅速滚动了高级警报三分之一。还有“中”和“低”,一组用于搜索优化的诊断程序以及分析仪的其他尚未开发的机会-这些是数百个更明显的错误和数千个未开发的警告。

然后,读者需要问自己一个问题:“这种方法是否可以发布一个好的游戏引擎?”没有代码质量控制。具有旧错误的CryEngine代码被作为基础,添加了新错误。只有在下一次检查代码之后,CryEngine本身才能最终确定。亚马逊拥有一切机会利用其资源朝着代码质量的方向努力,并发布最酷的游戏引擎!

不要很沮丧。在PVS-Studio客户中,还有三十多家参与游戏的公司。通过选择过滤器“游戏开发”,您可以在我们的网站“ 客户页面上了解他们及其产品。因此,我们正在逐步改善世界。也许我们可以改善Amazon Lumberyard :)。

一位同事最近写了一篇有关游戏软件质量的文章,建议您阅读:“ 视频游戏行业的静态分析:十大软件错误”

链接下载PVS-Studio分析仪,没有它怎么办;-)



如果您想与说英语的读者分享这篇文章,请使用以下链接:Svyatoslav Razmyslov。亚马逊伐木场:痛苦的尖叫

您阅读文章并有疑问吗?
通常我们的文章会被问同样的问题。我们在这里收集了答案读者对有关PVS-Studio版本2015的文章回答请参阅清单。

Source: https://habr.com/ru/post/zh-CN416565/


All Articles