ΠΠ΄ΠΈΠ½ ΠΈΠ· ΡΠ°ΠΌΡΡ
Π°ΠΊΡΡΠ°Π»ΡΠ½ΡΡ
ΡΡΠ΅Π½Π°ΡΠΈΠ΅Π² ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΡ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ° PVS-Studio β Π΅Π³ΠΎ ΠΈΠ½ΡΠ΅Π³ΡΠ°ΡΠΈΡ Ρ CI ΡΠΈΡΡΠ΅ΠΌΠ°ΠΌΠΈ. Π Ρ
ΠΎΡΡ Π°Π½Π°Π»ΠΈΠ· ΠΏΡΠΎΠ΅ΠΊΡΠ° PVS-Studio ΠΏΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠΈ ΠΈΠ·-ΠΏΠΎΠ΄ Π»ΡΠ±ΠΎΠΉ continuous integration ΡΠΈΡΡΠ΅ΠΌΡ ΠΌΠΎΠΆΠ½ΠΎ Π²ΡΡΡΠΎΠΈΡΡ Π²ΡΠ΅Π³ΠΎ Π² Π½Π΅ΡΠΊΠΎΠ»ΡΠΊΠΎ ΠΊΠΎΠΌΠ°Π½Π΄, ΠΌΡ ΠΏΡΠΎΠ΄ΠΎΠ»ΠΆΠ°Π΅ΠΌ Π΄Π΅Π»Π°ΡΡ ΡΡΠΎΡ ΠΏΡΠΎΡΠ΅ΡΡ Π΅ΡΡ ΡΠ΄ΠΎΠ±Π½Π΅Π΅. Π PVS-Studio ΠΏΠΎΡΠ²ΠΈΠ»Π°ΡΡ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΊΠ° ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΠΎΠ²Π°Π½ΠΈΡ Π²ΡΠ²ΠΎΠ΄Π° Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ° Π² ΡΠΎΡΠΌΠ°Ρ Π΄Π»Ρ TeamCity β TeamCity Inspections Type. ΠΠ°Π²Π°ΠΉΡΠ΅ ΠΏΠΎΡΠΌΠΎΡΡΠΈΠΌ, ΠΊΠ°ΠΊ ΡΡΠΎ ΡΠ°Π±ΠΎΡΠ°Π΅Ρ.
ΠΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΎΠ± ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΠΎΠΌ ΠΠ
ΠΠ½ΡΠΎΡΠΌΠ°ΡΠΈΡ ΠΎΠ± ΠΈΡΡΠ»Π΅Π΄ΡΠ΅ΠΌΠΎΠΌ ΠΏΡΠΎΠ΅ΠΊΡΠ΅
ΠΠ°Π²Π°ΠΉΡΠ΅ ΠΏΠΎΠΏΡΠΎΠ±ΡΠ΅ΠΌ Π΄Π°Π½Π½ΡΡ ΡΡΠ½ΠΊΡΠΈΠΎΠ½Π°Π»ΡΠ½ΠΎΡΡΡ Π½Π° ΠΏΡΠ°ΠΊΡΠΈΡΠ΅ΡΠΊΠΎΠΌ ΠΏΡΠΈΠΌΠ΅ΡΠ΅ β ΠΏΡΠΎΠ°Π½Π°Π»ΠΈΠ·ΠΈΡΡΠ΅ΠΌ ΠΏΡΠΎΠ΅ΠΊΡ OpenRCT2.
ΠΠ°ΡΡΡΠΎΠΉΠΊΠ°
Π ΡΠ΅Π»ΡΡ ΡΠΊΠΎΠ½ΠΎΠΌΠΈΠΈ Π²ΡΠ΅ΠΌΠ΅Π½ΠΈ Ρ, ΠΏΠΎΠΆΠ°Π»ΡΠΉ, ΠΎΠΏΡΡΡ ΠΏΡΠΎΡΠ΅ΡΡ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ ΠΈ Π½Π°ΡΠ½Ρ Ρ ΡΠΎΠ³ΠΎ ΠΌΠΎΠΌΠ΅Π½ΡΠ°, ΠΊΠΎΠ³Π΄Π° Ρ ΠΌΠ΅Π½Ρ Π½Π° ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠ΅ Π·Π°ΠΏΡΡΠ΅Π½ ΡΠ΅ΡΠ²Π΅Ρ TeamCity. ΠΠ°ΠΌ Π½ΡΠΆΠ½ΠΎ ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ: localhost:{ΡΠΊΠ°Π·Π°Π½Π½ΡΠΉ Π² ΠΏΡΠΎΡΠ΅ΡΡΠ΅ ΡΡΡΠ°Π½ΠΎΠ²ΠΊΠΈ ΠΏΠΎΡΡ}(Π² ΠΌΠΎΡΠΌ ΡΠ»ΡΡΠ°Π΅, localhost:9090) ΠΈ Π²Π²Π΅ΡΡΠΈ Π΄Π°Π½Π½ΡΠ΅ Π΄Π»Ρ Π°Π²ΡΠΎΡΠΈΠ·Π°ΡΠΈΠΈ. ΠΠΎΡΠ»Π΅ Π²Ρ ΠΎΠ΄Π° Π½Π°Ρ Π²ΡΡΡΠ΅ΡΠΈΡ:
ΠΠ°ΠΆΠΌΡΠΌ Π½Π° ΠΊΠ½ΠΎΠΏΠΊΡ Create Project. ΠΠ°Π»Π΅Π΅ Π²ΡΠ±Π΅ΡΠ΅ΠΌ Manually, Π·Π°ΠΏΠΎΠ»Π½ΠΈΠΌ ΠΏΠΎΠ»Ρ.
ΠΠΎΡΠ»Π΅ Π½Π°ΠΆΠ°ΡΠΈΡ Π½Π° ΠΊΠ½ΠΎΠΏΠΊΡ Create, Π½Π°Ρ Π²ΡΡΡΠ΅ΡΠ°Π΅Ρ ΠΎΠΊΠ½ΠΎ Ρ Π½Π°ΡΡΡΠΎΠΉΠΊΠ°ΠΌΠΈ.
ΠΠ°ΠΆΠΌΡΠΌ Create build configuration.
ΠΠ°ΠΏΠΎΠ»Π½ΡΠ΅ΠΌ ΠΏΠΎΠ»Ρ, Π½Π°ΠΆΠΈΠΌΠ°Π΅ΠΌ Create. ΠΡ Π²ΠΈΠ΄ΠΈΠΌ ΠΎΠΊΠ½ΠΎ Ρ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠ΅Π½ΠΈΠ΅ΠΌ Π²ΡΠ±ΠΎΡΠ° ΡΠΈΡΡΠ΅ΠΌΡ ΠΊΠΎΠ½ΡΡΠΎΠ»Ρ Π²Π΅ΡΡΠΈΠΉ. Π’Π°ΠΊ ΠΊΠ°ΠΊ ΠΈΡΡ
ΠΎΠ΄Π½ΠΈΠΊΠΈ ΡΠΆΠ΅ Π»Π΅ΠΆΠ°Ρ Π»ΠΎΠΊΠ°Π»ΡΠ½ΠΎ, ΠΆΠΌΡΠΌ Skip.
ΠΠ°ΠΊΠΎΠ½Π΅Ρ, ΠΌΡ ΠΏΠ΅ΡΠ΅Ρ
ΠΎΠ΄ΠΈΠΌ ΠΊ Π½Π°ΡΡΡΠΎΠΉΠΊΠ°ΠΌ ΠΏΡΠΎΠ΅ΠΊΡΠ°.
ΠΠΎΠ±Π°Π²ΠΈΠΌ ΡΠ°Π³ΠΈ ΡΠ±ΠΎΡΠΊΠΈ, Π΄Π»Ρ ΡΡΠΎΠ³ΠΎ ΠΆΠΌΡΠΌ: Build steps -> Add build step.
Π’ΡΡ Π²ΡΠ±Π΅ΡΠ΅ΠΌ:
- Runner type -> Command Line
- Run -> Custom Script
Π’Π°ΠΊ ΠΊΠ°ΠΊ ΠΌΡ Π±ΡΠ΄Π΅ΠΌ ΠΏΡΠΎΠ²ΠΎΠ΄ΠΈΡΡ Π°Π½Π°Π»ΠΈΠ· Π²ΠΎ Π²ΡΠ΅ΠΌΡ ΠΊΠΎΠΌΠΏΠΈΠ»ΡΡΠΈΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠ°, ΡΠ±ΠΎΡΠΊΠ° ΠΈ Π°Π½Π°Π»ΠΈΠ· Π΄ΠΎΠ»ΠΆΠ½Ρ Π±ΡΡΡ ΠΎΠ΄Π½ΠΈΠΌ ΡΠ°Π³ΠΎΠΌ, ΠΏΠΎΡΡΠΎΠΌΡ Π·Π°ΠΏΠΎΠ»Π½ΠΈΠΌ ΠΏΠΎΠ»Π΅ Custom Script:
ΠΠ° ΠΎΡΠ΄Π΅Π»ΡΠ½ΡΡ
ΡΠ°Π³Π°Ρ
ΠΌΡ ΠΎΡΡΠ°Π½ΠΎΠ²ΠΈΠΌΡΡ ΠΏΠΎΠ·ΠΆΠ΅. ΠΠ°ΠΆΠ½ΠΎ, ΡΡΠΎΠ±Ρ Π·Π°Π³ΡΡΠ·ΠΊΠ° Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ°, ΡΠ±ΠΎΡΠΊΠ° ΠΏΡΠΎΠ΅ΠΊΡΠ°, Π΅Π³ΠΎ Π°Π½Π°Π»ΠΈΠ·, Π²ΡΠ²ΠΎΠ΄ ΠΎΡΡΡΡΠ° ΠΈ Π΅Π³ΠΎ ΡΠΎΡΠΌΠ°ΡΠΈΡΠΎΠ²Π°Π½ΠΈΠ΅ Π·Π°Π½ΡΠ»ΠΎ Π²ΡΠ΅Π³ΠΎ ΠΎΠ΄ΠΈΠ½Π½Π°Π΄ΡΠ°ΡΡ ΡΡΡΠΎΠΊ ΠΊΠΎΠ΄Π°.
ΠΠΎΡΠ»Π΅Π΄Π½Π΅Π΅, ΡΡΠΎ Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ ΡΠ΄Π΅Π»Π°ΡΡ, β ΡΡΡΠ°Π½ΠΎΠ²ΠΈΡΡ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ, ΠΊΠΎΡΠΎΡΡΠΌΠΈ Ρ ΠΎΠ±ΠΎΠ·Π½Π°ΡΠΈΠ» Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΏΡΡΠΈ Π΄Π»Ρ ΡΠ»ΡΡΡΠ΅Π½ΠΈΡ ΠΈΡ ΡΠΈΡΠ°Π±Π΅Π»ΡΠ½ΠΎΡΡΠΈ. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ ΠΏΠ΅ΡΠ΅ΠΉΠ΄ΡΠΌ: Parameters -> Add new parameter ΠΈ Π΄ΠΎΠ±Π°Π²ΠΈΠΌ ΡΡΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΡΠ΅:
ΠΡΡΠ°ΡΡΡΡ Π½Π°ΠΆΠ°ΡΡ Π½Π° ΠΊΠ½ΠΎΠΏΠΊΡ Run Π² ΠΏΡΠ°Π²ΠΎΠΌ Π²Π΅ΡΡ
Π½Π΅ΠΌ ΡΠ³Π»Ρ. ΠΠΎΠΊΠ° ΠΈΠ΄ΡΡ ΡΠ±ΠΎΡΠΊΠ° ΠΈ Π°Π½Π°Π»ΠΈΠ· ΠΏΡΠΎΠ΅ΠΊΡΠ° ΡΠ°ΡΡΠΊΠ°ΠΆΡ Π²Π°ΠΌ ΠΎ ΡΠΊΡΠΈΠΏΡΠ΅.
ΠΠ΅ΠΏΠΎΡΡΠ΅Π΄ΡΡΠ²Π΅Π½Π½ΠΎ ΡΠΊΡΠΈΠΏΡ
ΠΠ»Ρ Π½Π°ΡΠ°Π»Π° Π½Π°ΠΌ Π½ΡΠΆΠ½ΠΎ Π²ΡΠΊΠ°ΡΠ°ΡΡ ΡΠ²Π΅ΠΆΠΈΠΉ Π΄ΠΈΡΡΡΠΈΠ±ΡΡΠΈΠ² PVS-Studio. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ ΠΌΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌ ΠΏΠ°ΠΊΠ΅ΡΠ½ΡΠΉ ΠΌΠ΅Π½Π΅Π΄ΠΆΠ΅Ρ Π‘hocolatey. ΠΠ»Ρ ΡΠ΅Ρ
, ΠΊΡΠΎ Ρ
ΠΎΡΠ΅Ρ ΡΠ·Π½Π°ΡΡ ΠΎΠ± ΡΡΠΎΠΌ ΠΏΠΎΠΏΠΎΠ΄ΡΠΎΠ±Π½Π΅Π΅, Π΅ΡΡΡ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠ°Ρ
choco install pvs-studio -y
ΠΠ°Π»Π΅Π΅ Π·Π°ΠΏΡΡΡΠΈΠΌ ΡΡΠΈΠ»ΠΈΡΡ ΠΎΡΡΠ»Π΅ΠΆΠΈΠ²Π°Π½ΠΈΡ ΡΠ±ΠΎΡΠΊΠΈ ΠΏΡΠΎΠ΅ΠΊΡΠ° CLMonitor.
%CLmon% monitor β-attach
ΠΠΎΡΠΎΠΌ ΠΏΡΠΎΠΈΠ·Π²Π΅Π΄ΡΠΌ ΡΠ±ΠΎΡΠΊΡ ΠΏΡΠΎΠ΅ΠΊΡΠ°, Π² ΠΊΠ°ΡΠ΅ΡΡΠ²Π΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΉ ΠΎΠΊΡΡΠΆΠ΅Π½ΠΈΡ MSB Π²ΡΡΡΡΠΏΠ°Π΅Ρ ΠΏΡΡΡ ΠΊ Π½ΡΠΆΠ½ΠΎΠΉ ΠΌΠ½Π΅ Π΄Π»Ρ ΡΠ±ΠΎΡΠΊΠΈ Π²Π΅ΡΡΠΈΠΈ MSBuild
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
ΠΠ²Π΅Π΄ΡΠΌ Π»ΠΎΠ³ΠΈΠ½ ΠΈ ΠΊΠ»ΡΡ Π»ΠΈΡΠ΅Π½Π·ΠΈΠΈ PVS-Studio:
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
ΠΠΎΡΠ»Π΅ Π·Π°Π²Π΅ΡΡΠ΅Π½ΠΈΡ ΡΠ±ΠΎΡΠΊΠΈ Π΅ΡΡ ΡΠ°Π· Π·Π°ΠΏΡΡΡΠΈΠΌ CLMonitor Π΄Π»Ρ Π³Π΅Π½Π΅ΡΠ°ΡΠΈΠΈ ΠΏΡΠ΅ΠΏΡΠΎΡΠ΅ΡΡΠΈΡΠΎΠ²Π°Π½Π½ΡΡ ΡΠ°ΠΉΠ»ΠΎΠ² ΠΈ ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ Π°Π½Π°Π»ΠΈΠ·Π°:
%CLmon% analyze -l "c:ptest.plog"
ΠΠΎΡΠ»Π΅ Π²ΠΎΡΠΏΠΎΠ»ΡΠ·ΡΠ΅ΠΌΡΡ Π΅ΡΡ ΠΎΠ΄Π½ΠΎΠΉ ΡΡΠΈΠ»ΠΈΡΠΎΠΉ ΠΈΠ· Π½Π°ΡΠ΅Π³ΠΎ Π΄ΠΈΡΡΡΠΈΠ±ΡΡΠΈΠ²Π°. PlogConverter ΠΏΡΠ΅ΠΎΠ±ΡΠ°Π·ΡΠ΅Ρ ΠΎΡΡΡΡ ΠΈΠ· ΡΡΠ°Π½Π΄Π°ΡΡΠ½ΠΎΠ³ΠΎ Π² ΡΠΏΠ΅ΡΠΈΡΠΈΡΠ½ΡΠΉ Π΄Π»Ρ TeamCity ΡΠΎΡΠΌΠ°Ρ. ΠΠ»Π°Π³ΠΎΠ΄Π°ΡΡ ΡΡΠΎΠΌΡ ΠΌΡ ΡΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠΎΡΠΌΠΎΡΡΠ΅ΡΡ Π΅Π³ΠΎ ΠΏΡΡΠΌΠΎ Π² ΠΎΠΊΠ½Π΅ ΡΠ±ΠΎΡΠΊΠΈ.
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
ΠΠΎΡΠ»Π΅Π΄Π½ΠΈΠΌ Π΄Π΅ΠΉΡΡΠ²ΠΈΠ΅ΠΌ Π²ΡΠ²Π΅Π΄Π΅ΠΌ ΡΠΎΡΠΌΠ°ΡΠΈΡΠΎΠ²Π°Π½Π½ΡΠΉ ΠΎΡΡΡΡ Π² stdout, Π³Π΄Π΅ Π΅Π³ΠΎ ΠΏΠΎΠ΄Ρ Π²Π°ΡΠΈΡ ΠΏΠ°ΡΡΠ΅Ρ TeamCity.
type "C:tempptest.plog_TeamCity.txt"
ΠΠΎΠ»Π½ΡΠΉ ΠΊΠΎΠ΄ ΡΠΊΡΠΈΠΏΡΠ°:
choco install pvs-studio -y
%CLmon% monitor --attach
set platform=x64
%MSB% %ProjPath% /t:clean
%MSB% %ProjPath% /t:rebuild /p:configuration=release
%MSB% %ProjPath% /t:g2
%MSB% %ProjPath% /t:PublishPortable
%PVS-Studio_cmd% credentials --username %PVS_Name% --serialNumber %PVS_Key%
%CLmon% analyze -l "c:ptest.plog"
%PlogConverter% "c:ptest.plog" --renderTypes=TeamCity -o "C:temp"
type "C:tempptest.plog_TeamCity.txt"
Π’Π΅ΠΌ Π²ΡΠ΅ΠΌΠ΅Π½Π΅ΠΌ, ΡΠ±ΠΎΡΠΊΠ° ΠΈ Π°Π½Π°Π»ΠΈΠ· ΠΏΡΠΎΠ΅ΠΊΡΠ° ΡΡΠΏΠ΅ΡΠ½ΠΎ Π·Π°Π²Π΅ΡΡΠΈΠ»ΠΈΡΡ, ΠΌΡ ΠΌΠΎΠΆΠ΅ΠΌ ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ Π½Π° Π²ΠΊΠ»Π°Π΄ΠΊΡ Projects ΠΈ ΡΠ±Π΅Π΄ΠΈΡΡΡΡ Π² ΡΡΠΎΠΌ.
Π’Π΅ΠΏΠ΅ΡΡ ΠΊΠ»ΠΈΠΊΠ½Π΅ΠΌ Π½Π° Inspections Total, ΡΡΠΎΠ± ΠΏΠ΅ΡΠ΅ΠΉΡΠΈ ΠΊ ΠΏΡΠΎΡΠΌΠΎΡΡΡ ΠΎΡΡΡΡΠ° Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ°:
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΡ ΡΠ³ΡΡΠΏΠΏΠΈΡΠΎΠ²Π°Π½Ρ ΠΏΠΎ Π½ΠΎΠΌΠ΅ΡΠ°ΠΌ Π΄ΠΈΠ°Π³Π½ΠΎΡΡΠΈΡΠ΅ΡΠΊΠΈΡ
ΠΏΡΠ°Π²ΠΈΠ». ΠΠ»Ρ ΠΎΡΡΡΠ΅ΡΡΠ²Π»Π΅Π½ΠΈΡ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΠΈ ΠΏΠΎ ΠΊΠΎΠ΄Ρ Π½ΡΠΆΠ½ΠΎ ΠΊΠ»ΠΈΠΊΠ½ΡΡΡ Π½Π° Π½ΠΎΠΌΠ΅Ρ ΡΡΡΠΎΠΊΠΈ Ρ ΠΏΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ΠΌ. ΠΠ°ΠΆΠ°ΡΠΈΠ΅ Π½Π° Π·Π½Π°ΠΊ Π²ΠΎΠΏΡΠΎΡΠ° Π² ΠΏΡΠ°Π²ΠΎΠΌ Π²Π΅ΡΡ
Π½Π΅ΠΌ ΡΠ³Π»Ρ ΠΎΡΠΊΡΠΎΠ΅Ρ Π²Π°ΠΌ Π½ΠΎΠ²ΡΡ Π²ΠΊΠ»Π°Π΄ΠΊΡ Ρ Π΄ΠΎΠΊΡΠΌΠ΅Π½ΡΠ°ΡΠΈΠ΅ΠΉ. Π’Π°ΠΊΠΆΠ΅ ΠΌΠΎΠΆΠ½ΠΎ ΠΎΡΡΡΠ΅ΡΡΠ²ΠΈΡΡ Π½Π°Π²ΠΈΠ³Π°ΡΠΈΡ ΠΏΠΎ ΠΊΠΎΠ΄Ρ, Π½Π°ΠΆΠ°Π² Π½Π° Π½ΠΎΠΌΠ΅Ρ ΡΡΡΠΎΠΊΠΈ Ρ ΠΏΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ΠΌ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ°. ΠΠ°Π²ΠΈΠ³Π°ΡΠΈΡ Ρ ΡΠ΄Π°Π»ΡΠ½Π½ΠΎΠ³ΠΎ ΠΊΠΎΠΌΠΏΡΡΡΠ΅ΡΠ° Π²ΠΎΠ·ΠΌΠΎΠΆΠ½Π° ΠΏΡΠΈ ΠΏΡΠΈΠΌΠ΅Π½Π΅Π½ΠΈΠΈ SourceTreeRoot ΠΌΠ°ΡΠΊΠ΅ΡΠ°. Π’ΠΎΡ, ΠΊΠΎΠΌΡ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ΅Π½ Π΄Π°Π½Π½ΡΠΉ ΡΠ΅ΠΆΠΈΠΌ ΡΠ°Π±ΠΎΡΡ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ°, ΠΌΠΎΠΆΠ΅Ρ ΠΎΠ·Π½Π°ΠΊΠΎΠΌΠΈΡΡΡΡ Ρ ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²ΡΡΡΠΈΠΌ ΡΠ°Π·Π΄Π΅Π»ΠΎΠΌ
ΠΡΠΎΡΠΌΠΎΡΡ ΡΠ΅Π·ΡΠ»ΡΡΠ°ΡΠΎΠ² ΡΠ°Π±ΠΎΡΡ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ°
ΠΠΎΡΠ»Π΅ ΡΠΎΠ³ΠΎ, ΠΊΠ°ΠΊ ΠΌΡ Π·Π°ΠΊΠΎΠ½ΡΠΈΠ»ΠΈ Ρ ΡΠ°Π·Π²ΡΡΡΡΠ²Π°Π½ΠΈΠ΅ΠΌ ΠΈ Π½Π°ΡΡΡΠΎΠΉΠΊΠΎΠΉ ΡΠ±ΠΎΡΠΊΠΈ, ΠΏΡΠ΅Π΄Π»Π°Π³Π°Ρ ΠΏΠΎΡΠΌΠΎΡΡΠ΅ΡΡ Π½Π° Π½Π΅ΠΊΠΎΡΠΎΡΡΠ΅ ΠΈΠ½ΡΠ΅ΡΠ΅ΡΠ½ΡΠ΅ ΠΏΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΡ, ΠΎΠ±Π½Π°ΡΡΠΆΠ΅Π½Π½ΡΠ΅ Π² ΠΈΡΡΠ»Π΅Π΄ΡΠ΅ΠΌΠΎΠΌ ΠΏΡΠΎΠ΅ΠΊΡΠ΅.
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ N1
Object* CreateObjectFromJson(....)
{
Object* result = nullptr;
....
result = CreateObject(entry);
....
if (readContext.WasError())
{
throw std::runtime_error("Object has errors");
}
....
}
Object* CreateObject(const rct_object_entry& entry)
{
Object* result;
switch (entry.GetType())
{
case OBJECT_TYPE_RIDE:
result = new RideObject(entry);
break;
case OBJECT_TYPE_SMALL_SCENERY:
result = new SmallSceneryObject(entry);
break;
case OBJECT_TYPE_LARGE_SCENERY:
result = new LargeSceneryObject(entry);
break;
....
default:
throw std::runtime_error("Invalid object type");
}
return result;
}
ΠΠ½Π°Π»ΠΈΠ·Π°ΡΠΎΡ Π·Π°ΠΌΠ΅ΡΠΈΠ» ΠΎΡΠΈΠ±ΠΊΡ, Π·Π°ΠΊΠ»ΡΡΠ°ΡΡΡΡΡΡ Π² ΡΠΎΠΌ, ΡΡΠΎ ΠΏΠΎΡΠ»Π΅ Π΄ΠΈΠ½Π°ΠΌΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ Π²ΡΠ΄Π΅Π»Π΅Π½ΠΈΡ ΠΏΠ°ΠΌΡΡΠΈ Π² CreateObject, ΠΏΡΠΈ Π²ΠΎΠ·Π½ΠΈΠΊΠ½ΠΎΠ²Π΅Π½ΠΈΠΈ ΠΈΡΠΊΠ»ΡΡΠ΅Π½ΠΈΡ ΠΏΠ°ΠΌΡΡΡ Π½Π΅ ΠΎΡΠΈΡΠ°Π΅ΡΡΡ, ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²Π΅Π½Π½ΠΎ, Π²ΠΎΠ·Π½ΠΈΠΊΠ°Π΅Ρ ΡΡΠ΅ΡΠΊΠ° ΠΏΠ°ΠΌΡΡΠΈ.
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ N2
static uint64_t window_cheats_page_enabled_widgets[] =
{
MAIN_CHEAT_ENABLED_WIDGETS |
(1ULL << WIDX_NO_MONEY) |
(1ULL << WIDX_ADD_SET_MONEY_GROUP) |
(1ULL << WIDX_MONEY_SPINNER) |
(1ULL << WIDX_MONEY_SPINNER_INCREMENT) |
(1ULL << WIDX_MONEY_SPINNER_DECREMENT) |
(1ULL << WIDX_ADD_MONEY) |
(1ULL << WIDX_SET_MONEY) |
(1ULL << WIDX_CLEAR_LOAN) |
(1ULL << WIDX_DATE_SET) |
(1ULL << WIDX_MONTH_BOX) | // <=
(1ULL << WIDX_MONTH_UP) |
(1ULL << WIDX_MONTH_DOWN) |
(1ULL << WIDX_YEAR_BOX) |
(1ULL << WIDX_YEAR_UP) |
(1ULL << WIDX_YEAR_DOWN) |
(1ULL << WIDX_DAY_BOX) |
(1ULL << WIDX_DAY_UP) |
(1ULL << WIDX_DAY_DOWN) |
(1ULL << WIDX_MONTH_BOX) | // <=
(1ULL << WIDX_DATE_GROUP) |
(1ULL << WIDX_DATE_RESET),
....
};
ΠΠ°Π»ΠΎ ΠΊΡΠΎ, ΠΊΡΠΎΠΌΠ΅ ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΎΠ³ΠΎ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡΠ°, ΡΠΌΠΎΠ³ Π±Ρ ΠΏΡΠΎΠΉΡΠΈ Π΄Π°Π½Π½ΡΠΉ ΡΠ΅ΡΡ Π½Π° Π²Π½ΠΈΠΌΠ°ΡΠ΅Π»ΡΠ½ΠΎΡΡΡ. ΠΠ°Π½Π½ΡΠΉ ΠΏΡΠΈΠΌΠ΅Ρ ΠΊΠΎΠΏΠΈΠΏΠ°ΡΡΡ Ρ ΠΎΡΠΎΡ ΠΈΠΌΠ΅Π½Π½ΠΎ ΡΡΠΈΠΌ.
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΡ N3
struct RCT12SpriteBase
{
....
uint8_t flags;
....
};
struct rct1_peep : RCT12SpriteBase
{
....
uint8_t flags;
....
};
ΠΠΎΠ½Π΅ΡΠ½ΠΎ, ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°Π½ΠΈΠ΅ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΉ Ρ ΠΎΠ΄Π½ΠΈΠΌ ΠΈΠΌΠ΅Π½Π΅ΠΌ Π² Π±Π°Π·ΠΎΠ²ΠΎΠΌ ΠΊΠ»Π°ΡΡΠ΅ ΠΈ Π² Π½Π°ΡΠ»Π΅Π΄Π½ΠΈΠΊΠ΅ Π΄Π°Π»Π΅ΠΊΠΎ Π½Π΅ Π²ΡΠ΅Π³Π΄Π° ΡΠ²Π»ΡΠ΅ΡΡΡ ΠΎΡΠΈΠ±ΠΊΠΎΠΉ. ΠΠ΄Π½Π°ΠΊΠΎ ΡΠ΅Ρ Π½ΠΎΠ»ΠΎΠ³ΠΈΡ Π½Π°ΡΠ»Π΅Π΄ΠΎΠ²Π°Π½ΠΈΡ ΡΠ°ΠΌΠ° ΠΏΠΎ ΡΠ΅Π±Π΅ ΠΏΡΠ΅Π΄ΠΏΠΎΠ»Π°Π³Π°Π΅Ρ Π½Π°Π»ΠΈΡΠΈΠ΅ Π²ΡΠ΅Ρ ΠΏΠΎΠ»Π΅ΠΉ ΡΠΎΠ΄ΠΈΡΠ΅Π»ΡΡΠΊΠΎΠ³ΠΎ ΠΊΠ»Π°ΡΡΠ° Π² Π΄ΠΎΡΠ΅ΡΠ½Π΅ΠΌ. ΠΠ±ΡΡΠ²ΠΈΠ² ΠΆΠ΅ Π² Π½Π°ΡΠ»Π΅Π΄Π½ΠΈΠΊΠ΅ ΠΏΠΎΠ»Ρ Ρ ΡΠ°ΠΊΠΈΠΌ ΠΆΠ΅ ΠΈΠΌΠ΅Π½Π΅ΠΌ, ΠΌΡ Π²Π½ΠΎΡΠΈΠΌ ΠΏΡΡΠ°Π½ΠΈΡΡ.
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ N4
void vehicle_visual_observation_tower(...., int32_t imageDirection, ....)
{
if ((imageDirection / 8) && (imageDirection / 8) != 3)
{
....
}
....
}
ΠΠ°Π²Π°ΠΉΡΠ΅ ΡΠ°Π·Π±Π΅ΡΡΠΌΡΡ ΠΏΠΎΠΏΠΎΠ΄ΡΠΎΠ±Π½Π΅Π΅. ΠΡΡΠ°ΠΆΠ΅Π½ΠΈΠ΅ imageDirection / 8 Π±ΡΠ΄Π΅Ρ false Π² ΡΠΎΠΌ ΡΠ»ΡΡΠ°Π΅, Π΅ΡΠ»ΠΈ imageDirection Π½Π°Ρ ΠΎΠ΄ΠΈΡΡΡ Π² Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π΅ ΠΎΡ -7 Π΄ΠΎ 7. ΠΡΠΎΡΠ°Ρ ΡΠ°ΡΡΡ: (imageDirection / 8) != 3 ΠΏΡΠΎΠ²Π΅ΡΡΠ΅Ρ imageDirection Π½Π° Π½Π°Ρ ΠΎΠΆΠ΄Π΅Π½ΠΈΠ΅ Π²Π½Π΅ Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½Π°: ΠΎΡ -31 Π΄ΠΎ -24 ΠΈ ΠΎΡ 24 Π΄ΠΎ 31 ΡΠΎΠΎΡΠ²Π΅ΡΡΡΠ²Π΅Π½Π½ΠΎ. ΠΠ½Π΅ ΠΊΠ°ΠΆΠ΅ΡΡΡ Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΡΡΡΠ°Π½Π½ΡΠΌ ΠΏΡΠΎΠ²Π΅ΡΡΡΡ ΡΠΈΡΠ»Π° Π½Π° Π²Ρ ΠΎΠΆΠ΄Π΅Π½ΠΈΠ΅ Π² ΠΎΠΏΡΠ΅Π΄Π΅Π»ΡΠ½Π½ΡΠΉ Π΄ΠΈΠ°ΠΏΠ°Π·ΠΎΠ½ ΡΠ°ΠΊΠΈΠΌ ΡΠΏΠΎΡΠΎΠ±ΠΎΠΌ ΠΈ, Π΄Π°ΠΆΠ΅ Π΅ΡΠ»ΠΈ Π² Π΄Π°Π½Π½ΠΎΠΌ ΡΡΠ°Π³ΠΌΠ΅Π½ΡΠ΅ ΠΊΠΎΠ΄Π° Π½Π΅Ρ ΠΎΡΠΈΠ±ΠΊΠΈ, Ρ Π±Ρ ΡΠ΅ΠΊΠΎΠΌΠ΅Π½Π΄ΠΎΠ²Π°Π» ΠΏΠ΅ΡΠ΅ΠΏΠΈΡΠ°ΡΡ Π΄Π°Π½Π½ΡΠ΅ ΡΡΠ»ΠΎΠ²ΠΈΡ Π½Π° Π±ΠΎΠ»Π΅Π΅ ΡΠ²Π½ΡΠ΅. ΠΡΠΎ ΡΡΡΠ΅ΡΡΠ²Π΅Π½Π½ΠΎ ΡΠΏΡΠΎΡΡΠΈΠ»ΠΎ Π±Ρ ΠΆΠΈΠ·Π½Ρ Π»ΡΠ΄ΡΠΌ, ΠΊΠΎΡΠΎΡΡΠ΅ Π±ΡΠ΄ΡΡ ΡΠΈΡΠ°ΡΡ ΠΈ ΠΏΠΎΠ΄Π΄Π΅ΡΠΆΠΈΠ²Π°ΡΡ ΡΡΠΎΡ ΠΊΠΎΠ΄.
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ N5
void process_mouse_over(....)
{
....
switch (window->widgets[widgetId].type)
{
case WWT_VIEWPORT:
ebx = 0;
edi = cursorId; // <=
// Window event WE_UNKNOWN_0E was called here,
// but no windows actually implemented a handler and
// it's not known what it was for
cursorId = edi; // <=
if ((ebx & 0xFF) != 0)
{
set_cursor(cursorId);
return;
}
break;
....
}
....
}
ΠΠ°Π½Π½ΡΠΉ ΡΡΠ°Π³ΠΌΠ΅Π½Ρ ΠΊΠΎΠ΄Π°, ΡΠΊΠΎΡΠ΅Π΅ Π²ΡΠ΅Π³ΠΎ, Π±ΡΠ» ΠΏΠΎΠ»ΡΡΠ΅Π½ ΠΏΡΡΠ΅ΠΌ Π΄Π΅ΠΊΠΎΠΌΠΏΠΈΠ»ΡΡΠΈΠΈ. ΠΠ°ΡΠ΅ΠΌ, ΡΡΠ΄Ρ ΠΏΠΎ ΠΎΡΡΠ°Π²Π»Π΅Π½Π½ΠΎΠΌΡ ΠΊΠΎΠΌΠΌΠ΅Π½ΡΠ°ΡΠΈΡ, Π±ΡΠ»Π° ΡΠ΄Π°Π»Π΅Π½Π° ΡΠ°ΡΡΡ Π½Π΅ΡΠ°Π±ΠΎΡΠ΅Π³ΠΎ ΠΊΠΎΠ΄Π°. ΠΠ΄Π½Π°ΠΊΠΎ ΠΎΡΡΠ°Π»Π°ΡΡ ΠΏΠ°ΡΠ° ΠΎΠΏΠ΅ΡΠ°ΡΠΈΠΉ Π½Π°Π΄ cursorId, ΠΊΠΎΡΠΎΡΡΠ΅ ΡΠ°ΠΊΠΆΠ΅ Π½Π΅ Π½Π΅ΡΡΡ ΠΎΡΠΎΠ±ΠΎΠ³ΠΎ ΡΠΌΡΡΠ»Π°.
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ N6
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Add new player.
player = AddPlayer("", "");
if (player) // <=
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
}
newPlayers.push_back(player->Id); // <=
}
....
}
ΠΠ°Π½Π½ΡΠΉ ΠΊΠΎΠ΄ ΠΏΠΎΠΏΡΠ°Π²ΠΈΡΡ Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΠΏΡΠΎΡΡΠΎ, Π½ΡΠΆΠ½ΠΎ ΠΈΠ»ΠΈ ΡΡΠ΅ΡΠΈΠΉ ΡΠ°Π· ΠΏΡΠΎΠ²Π΅ΡΡΡΡ player Π½Π° Π½ΡΠ»Π΅Π²ΠΎΠΉ ΡΠΊΠ°Π·Π°ΡΠ΅Π»Ρ, Π»ΠΈΠ±ΠΎ Π²Π½Π΅ΡΡΠΈ Π΅Π³ΠΎ Π² ΡΠ΅Π»ΠΎ ΡΡΠ»ΠΎΠ²Π½ΠΎΠ³ΠΎ ΠΎΠΏΠ΅ΡΠ°ΡΠΎΡΠ°. Π― Π±Ρ ΠΏΡΠ΅Π΄Π»ΠΎΠΆΠΈΠ» Π²ΡΠΎΡΠΎΠΉ Π²Π°ΡΠΈΠ°Π½Ρ:
void Network::ProcessPlayerList()
{
....
auto* player = GetPlayerByID(pendingPlayer.Id);
if (player == nullptr)
{
// Add new player.
player = AddPlayer("", "");
if (player)
{
*player = pendingPlayer;
if (player->Flags & NETWORK_PLAYER_FLAG_ISSERVER)
{
_serverConnection->Player = player;
}
newPlayers.push_back(player->Id);
}
}
....
}
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ N7
std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
....
}
else
{
....
entry.name = (name == nullptr ? "" : json_string_value(name));
....
}
....
}
ΠΠΎΠΆΠ½ΠΎ ΠΎΠ΄Π½ΠΈΠΌ ΠΌΠ°Ρ ΠΎΠΌ ΠΈΠ·Π±Π°Π²ΠΈΡΡΡΡ ΠΎΡ ΡΡΡΠ΄Π½ΠΎΡΠΈΡΠ°Π΅ΠΌΠΎΠΉ ΡΡΡΠΎΠΊΠΈ ΠΊΠΎΠ΄Π° ΠΈ ΡΠ΅ΡΠΈΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ Ρ ΠΏΡΠΎΠ²Π΅ΡΠΊΠΎΠΉ Π½Π° nullptr. ΠΡΠ΅Π΄Π»Π°Π³Π°Ρ ΠΈΠ·ΠΌΠ΅Π½ΠΈΡΡ ΠΊΠΎΠ΄ ΡΠ»Π΅Π΄ΡΡΡΠΈΠΌ ΠΎΠ±ΡΠ°Π·ΠΎΠΌ:
std::optional<ServerListEntry> ServerListEntry::FromJson(...)
{
auto name = json_object_get(server, "name");
.....
if (name == nullptr || version == nullptr)
{
name = ""
....
}
else
{
....
entry.name = json_string_value(name);
....
}
....
}
ΠΡΠ΅Π΄ΡΠΏΡΠ΅ΠΆΠ΄Π΅Π½ΠΈΠ΅ N8
void CustomListView::MouseUp(....)
{
....
if (!ColumnHeaderPressedCurrentState)
{
ColumnHeaderPressed = std::nullopt;
ColumnHeaderPressedCurrentState = false;
Invalidate();
}
}
ΠΠΎΠ΄ Π²ΡΠ³Π»ΡΠ΄ΠΈΡ Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΡΡΡΠ°Π½Π½ΠΎ. ΠΠ½Π΅ ΠΊΠ°ΠΆΠ΅ΡΡΡ, ΠΈΠΌΠ΅Π»Π° ΠΌΠ΅ΡΡΠΎ Π±ΡΡΡ ΠΎΠΏΠ΅ΡΠ°ΡΠΊΠ° Π»ΠΈΠ±ΠΎ Π² ΡΡΠ»ΠΎΠ²ΠΈΠΈ, Π»ΠΈΠ±ΠΎ ΠΏΡΠΈ ΠΏΠΎΠ²ΡΠΎΡΠ½ΠΎΠΌ ΠΏΡΠΈΡΠ²ΠΎΠ΅Π½ΠΈΠΈ ΠΏΠ΅ΡΠ΅ΠΌΠ΅Π½Π½ΠΎΠΉ ColumnHeaderPressedCurrentState Π·Π½Π°ΡΠ΅Π½ΠΈΡ false.
ΠΡΠ²ΠΎΠ΄
ΠΠ°ΠΊ ΠΌΡ Π²ΠΈΠ΄ΠΈΠΌ, ΠΈΠ½ΡΠ΅Π³ΡΠΈΡΠΎΠ²Π°ΡΡ ΡΡΠ°ΡΠΈΡΠ΅ΡΠΊΠΈΠΉ Π°Π½Π°Π»ΠΈΠ·Π°ΡΠΎΡ PVS-Studio Π² ΡΠ²ΠΎΠΉ ΠΏΡΠΎΠ΅ΠΊΡ Π½Π° TeamCity Π΄ΠΎΠ²ΠΎΠ»ΡΠ½ΠΎ ΠΏΡΠΎΡΡΠΎ. ΠΠ»Ρ ΡΡΠΎΠ³ΠΎ Π΄ΠΎΡΡΠ°ΡΠΎΡΠ½ΠΎ Π½Π°ΠΏΠΈΡΠ°ΡΡ Π²ΡΠ΅Π³ΠΎ ΠΎΠ΄ΠΈΠ½ ΠΌΠ°Π»Π΅Π½ΡΠΊΠΈΠΉ ΡΠ°ΠΉΠ» ΠΊΠΎΠ½ΡΠΈΠ³ΡΡΠ°ΡΠΈΠΈ. ΠΡΠΎΠ²Π΅ΡΠΊΠ° ΠΊΠΎΠ΄Π° ΠΆΠ΅ ΠΏΠΎΠ·Π²ΠΎΠ»ΠΈΡ Π²ΡΡΠ²Π»ΡΡΡ ΠΏΡΠΎΠ±Π»Π΅ΠΌΡ ΡΡΠ°Π·Ρ ΠΏΠΎΡΠ»Π΅ ΡΠ±ΠΎΡΠΊΠΈ, ΡΡΠΎ ΠΏΠΎΠΌΠΎΠΆΠ΅Ρ ΡΡΡΡΠ°Π½ΡΡΡ ΠΈΡ ΡΠΎΠ³Π΄Π°, ΠΊΠΎΠ³Π΄Π° ΡΠ»ΠΎΠΆΠ½ΠΎΡΡΡ ΠΈ ΡΡΠΎΠΈΠΌΠΎΡΡΡ ΠΏΡΠ°Π²ΠΎΠΊ Π΅ΡΡ ΠΌΠ°Π»Ρ.
ΠΡΠ»ΠΈ Ρ
ΠΎΡΠΈΡΠ΅ ΠΏΠΎΠ΄Π΅Π»ΠΈΡΡΡΡ ΡΡΠΎΠΉ ΡΡΠ°ΡΡΠ΅ΠΉ Ρ Π°Π½Π³Π»ΠΎΡΠ·ΡΡΠ½ΠΎΠΉ Π°ΡΠ΄ΠΈΡΠΎΡΠΈΠ΅ΠΉ, ΡΠΎ ΠΏΡΠΎΡΡ ΠΈΡΠΏΠΎΠ»ΡΠ·ΠΎΠ²Π°ΡΡ ΡΡΡΠ»ΠΊΡ Π½Π° ΠΏΠ΅ΡΠ΅Π²ΠΎΠ΄: Vladislav Stolyarov.
ΠΡΡΠΎΡΠ½ΠΈΠΊ: habr.com