
אנו ממשיכים לשפר את השימושיות של PVS-Studio. המנתח שלנו זמין כעת ב-Chocolatey, מנהל חבילות עבור Windowsאנו מאמינים שזה יקל על פריסת PVS-Studio, במיוחד בשירותי ענן. כדי להתחיל, בואו נבחן את קוד המקור של Chocolatey. Azure DevOps ישמש כמערכת CI.
להלן רשימה של מאמרים אחרים שלנו בנושא אינטגרציה עם מערכות ענן:
אני ממליץ לך לשים לב למאמר הראשון על אינטגרציה עם Azure DevOps, מכיוון שבמקרה זה כמה נקודות מושמטות כדי לא להיות משוכפלות.
אז, הגיבורים של המאמר הזה:
— כלי לניתוח קוד סטטי שנועד לאתר שגיאות ופגיעויות פוטנציאליות בתוכניות שנכתבו ב-C, C++, C# ו-Java. עובד על מערכות 64 סיביות. Windows, Linux и macOS, ויכול לנתח קוד המכוון לפלטפורמות ARM של 32 סיביות, 64 סיביות ומוטמעות. אם אתם מנסים ניתוח קוד סטטי עבור הפרויקטים שלכם בפעם הראשונה, אנו ממליצים להכיר את כיצד לצפות במהירות באזהרות ה-PVS-Studio המעניינות ביותר ולהעריך את היכולות של כלי זה.
- מערך שירותי ענן המכסים במשותף את כל תהליך הפיתוח. פלטפורמה זו כוללת כלים כגון Azure Pipelines, Azure Boards, Azure Artifacts, Azure Repos, Azure Test Plans, המאפשרים להאיץ את תהליך יצירת התוכנה ולשפר את איכותה.
– מנהל חבילות עבור Windows קוד פתוח. מטרת הפרויקט היא להפוך את כל מחזור חיי התוכנה לאוטומטי, החל מהתקנה ועד שדרוגים והסרה, במערכות הפעלה. Windows.
על השימוש בשוקולדי
אתה יכול לראות כיצד להתקין את מנהל החבילות עצמו כאן . תיעוד מלא להתקנת הנתח זמין בכתובת עיין בסעיף התקנה באמצעות פרק מנהל החבילות של שוקולד. אחזור בקצרה על כמה נקודות משם.
פקודה להתקנת הגרסה העדכנית ביותר של הנתח:
choco install pvs-studioפקודה להתקנת גרסה ספציפית של חבילת PVS-Studio:
choco install pvs-studio --version=7.05.35617.2075כברירת מחדל, רק הליבה של הנתח, רכיב הליבה, מותקנת. ניתן להעביר את כל הדגלים האחרים (Standalone, JavaCore, IDEA, MSVS2010, MSVS2012, MSVS2013, MSVS2015, MSVS2017, MSVS2019) באמצעות פרמטרי --package.
דוגמה לפקודה שתתקין מנתח עם תוסף עבור Visual Studio 2019:
choco install pvs-studio --package-parameters="'/MSVS2019'"כעת בואו נסתכל על דוגמה לשימוש נוח בנתח תחת Azure DevOps.
התאמה
הרשו לי להזכיר לכם שישנו סעיף נפרד בנושאים כגון רישום חשבון, יצירת Build Pipeline וסנכרון החשבון שלכם עם פרויקט שנמצא במאגר GitHub. . ההגדרה שלנו תתחיל מיד בכתיבת קובץ תצורה.
ראשית, בואו נגדיר טריגר הפעלה, המציין שאנו מפעילים רק עבור שינויים ב אב ענף:
trigger:
- masterבשלב הבא, עלינו לבחור מכונה וירטואלית. בשלב זה, זו תהיה סוכן שמתארח על ידי מיקרוסופט עם Windows Server 2019 ו-Visual Studio 2019:
pool:
vmImage: 'windows-latest'נעבור לגוף קובץ התצורה (בלוק צעדים). למרות העובדה שלא ניתן להתקין תוכנה שרירותית במכונה וירטואלית, לא הוספתי קונטיינר Docker. אנו יכולים להוסיף את Chocolatey כהרחבה עבור Azure DevOps. כדי לעשות זאת, בוא נלך אל . נְקִישָׁה השג אותו בחינם. לאחר מכן, אם אתה כבר מורשה, פשוט בחר את החשבון שלך, ואם לא, אז עשה את אותו הדבר לאחר ההרשאה.

כאן אתה צריך לבחור היכן נוסיף את התוסף וללחוץ על הכפתור התקן.

לאחר התקנה מוצלחת, לחץ המשך לארגון:

כעת תוכל לראות את התבנית עבור משימת השוקולד בחלון משימות בעת עריכת קובץ תצורה azure-pipelines.yml:

לחץ על שוקולדי וראה רשימה של שדות:

כאן אנחנו צריכים לבחור להתקין בשטח עם הקבוצות. IN שם קובץ Nuspec ציינו את שם החבילה הנדרשת – pvs-studio. אם לא תציין את הגרסה, תותקן העדכנית, מה שמתאים לנו לחלוטין. בוא נלחץ על הכפתור להוסיף ונראה את המשימה שנוצרה בקובץ התצורה.
steps:
- task: ChocolateyCommand@0
inputs:
command: 'install'
installPackageId: 'pvs-studio'לאחר מכן, נעבור לחלק העיקרי של הקובץ שלנו:
- task: CmdLine@2
inputs:
script: כעת עלינו ליצור קובץ עם רישיון המנתח. כאן PVSNAME и PVSKEY - שמות של משתנים שאת ערכיהם אנו מציינים בהגדרות. הם יאחסנו את פרטי הכניסה ואת מפתח הרישיון של PVS-Studio. כדי להגדיר את הערכים שלהם, פתח את התפריט משתנים->משתנה חדש. בואו ניצור משתנים PVSNAME לכניסה ו PVSKEY עבור מפתח הנתח. אל תשכח לסמן את התיבה שמור את הערך הזה בסוד עבור PVSKEY. קוד פקודה:
сall "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe" credentials
–u $(PVSNAME) –n $(PVSKEY)בואו נבנה את הפרויקט באמצעות קובץ bat שנמצא במאגר:
сall build.batבואו ניצור תיקיה שבה יאוחסנו קבצים עם תוצאות הנתח:
сall mkdir PVSTestResultsבואו נתחיל לנתח את הפרויקט:
сall "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe"
–t .srcchocolatey.sln –o .PVSTestResultsChoco.plog אנו ממירים את הדוח שלנו לפורמט html באמצעות כלי השירות PlogСonverter:
сall "C:Program Files (x86)PVS-StudioPlogConverter.exe"
–t html –o PVSTestResults .PVSTestResultsChoco.plogכעת עליך ליצור משימה כדי שתוכל להעלות את הדוח.
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: PVSTestResults
artifactName: PVSTestResults
condition: always()קובץ התצורה המלא נראה כך:
trigger:
- master
pool:
vmImage: 'windows-latest'
steps:
- task: ChocolateyCommand@0
inputs:
command: 'install'
installPackageId: 'pvs-studio'
- task: CmdLine@2
inputs:
script: |
call "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe"
credentials –u $(PVSNAME) –n $(PVSKEY)
call build.bat
call mkdir PVSTestResults
call "C:Program Files (x86)PVS-StudioPVS-Studio_Cmd.exe"
–t .srcchocolatey.sln –o .PVSTestResultsChoco.plog
call "C:Program Files (x86)PVS-StudioPlogConverter.exe"
–t html –o .PVSTestResults .PVSTestResultsChoco.plog
- task: PublishBuildArtifacts@1
inputs:
pathToPublish: PVSTestResults
artifactName: PVSTestResults
condition: always()בוא נקליק שמור->שמור->הפעל כדי להפעיל את המשימה. בוא נוריד את הדוח על ידי מעבר ללשונית המשימות.
פרויקט Chocolatey מכיל רק 37615 שורות של קוד C#. בואו נסתכל על כמה מהשגיאות שנמצאו.
תוצאות מבחן
אזהרה N1
אזהרת מנתח: המשתנה 'ספק' מוקצה לעצמו. CrytpoHashProviderSpecs.cs 38
public abstract class CrytpoHashProviderSpecsBase : TinySpec
{
....
protected CryptoHashProvider Provider;
....
public override void Context()
{
Provider = Provider = new CryptoHashProvider(FileSystem.Object);
}
}המנתח זיהה הקצאה של המשתנה לעצמו, וזה לא הגיוני. סביר להניח שבמקום אחד מהמשתנים האלה צריך להיות אחד אחר. ובכן, או שזו טעות הקלדה, וניתן פשוט להסיר את המשימה הנוספת.
אזהרה N2
אזהרת מנתח: [CWE-480] האופרטור '&' מעריך את שני האופרנדים. אולי במקום זאת יש להשתמש באופרטור '&&' של קצר חשמלי. Platform.cs 64
public static PlatformType get_platform()
{
switch (Environment.OSVersion.Platform)
{
case PlatformID.MacOSX:
{
....
}
case PlatformID.Unix:
if(file_system.directory_exists("/Applications")
& file_system.directory_exists("/System")
& file_system.directory_exists("/Users")
& file_system.directory_exists("/Volumes"))
{
return PlatformType.Mac;
}
else
return PlatformType.Linux;
default:
return PlatformType.Windows;
}
}הבדל מפעיל & מהמפעיל && הוא שאם הצד השמאלי של הביטוי הוא שקר, אז הצד הימני עדיין יחושב, מה שבמקרה זה מרמז על קריאות שיטה מיותרות system.directory_exists.
בפרגמנט הנחשב, מדובר בפגם קל. כן, ניתן לבצע אופטימיזציה של מצב זה על ידי החלפת האופרטור & באופרטור &&, אך מנקודת מבט מעשית, זה לא משפיע על שום דבר. עם זאת, במקרים אחרים, בלבול בין & ו-&& עלול לגרום לבעיות חמורות כאשר הצד הימני של הביטוי מטופל בערכים שגויים/לא חוקיים. לדוגמה, באוסף השגיאות שלנו, , יש את המקרה הזה:
if ((k < nct) & (s[k] != 0.0))גם אם המדד k שגוי, הוא ישמש לגישה לרכיב מערך. כתוצאה מכך, ייושק חריג IndexOutOfRangeException.
אזהרות N3, N4
אזהרת מנתח: [CWE-571] הביטוי 'shortPrompt' הוא תמיד נכון. InteractivePrompt.cs 101
אזהרת מנתח: [CWE-571] הביטוי 'shortPrompt' הוא תמיד נכון. InteractivePrompt.cs 105
public static string
prompt_for_confirmation(.... bool shortPrompt = false, ....)
{
....
if (shortPrompt)
{
var choicePrompt = choice.is_equal_to(defaultChoice) //1
?
shortPrompt //2
?
"[[{0}]{1}]".format_with(choice.Substring(0, 1).ToUpperInvariant(), //3
choice.Substring(1,choice.Length - 1))
:
"[{0}]".format_with(choice.ToUpperInvariant()) //0
:
shortPrompt //4
?
"[{0}]{1}".format_with(choice.Substring(0,1).ToUpperInvariant(), //5
choice.Substring(1,choice.Length - 1))
:
choice; //0
....
}
....
}במקרה זה, יש היגיון מוזר מאחורי פעולת האופרטור השלישוני. בואו נסתכל מקרוב: אם מתקיים התנאי שסימנתי במספר 1, אז נעבור לתנאי 2 שהוא תמיד נָכוֹן, כלומר יבוצע שורה 3. אם תנאי 1 יתברר כשקרי, אז נלך לשורה המסומנת במספר 4, התנאי שבו הוא גם תמיד נָכוֹן, כלומר תבוצע שורה 5. לפיכך, התנאים המסומנים בהערה 0 לעולם לא יתקיימו, מה שאולי אינו בדיוק היגיון הפעולה שהמתכנת ציפה לו.
אזהרה N5
אזהרת מנתח: [CWE-783] אולי המפעיל '?:' פועל בצורה שונה ממה שהיה צפוי. העדיפות שלו נמוכה יותר מהעדיפות של מפעילים אחרים במצבו. Options.cs 1019
private static string GetArgumentName (...., string description)
{
string[] nameStart;
if (maxIndex == 1)
{
nameStart = new string[]{"{0:", "{"};
}
else
{
nameStart = new string[]{"{" + index + ":"};
}
for (int i = 0; i < nameStart.Length; ++i)
{
int start, j = 0;
do
{
start = description.IndexOf (nameStart [i], j);
}
while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false);
....
return maxIndex == 1 ? "VALUE" : "VALUE" + (index + 1);
}
}האבחון עבד עבור הקו:
while (start >= 0 && j != 0 ? description [j++ - 1] == '{' : false)מאז המשתנה j כמה שורות למעלה מאותחל לאפס, האופרטור השלישוני יחזיר את הערך שקר. בגלל מצב זה, גוף הלולאה יבוצע פעם אחת בלבד. נראה לי שקטע הקוד הזה לא עובד בכלל כמו שהמתכנת התכוון.
אזהרה N6
אזהרת מנתח: [CWE-571] הביטוי 'installedPackageVersions.Count != 1' הוא תמיד נכון. NugetService.cs 1405
private void remove_nuget_cache_for_package(....)
{
if (!config.AllVersions && installedPackageVersions.Count > 1)
{
const string allVersionsChoice = "All versions";
if (installedPackageVersions.Count != 1)
{
choices.Add(allVersionsChoice);
}
....
}
....
}יש כאן מצב מקונן מוזר: installedPackageVersions.Count != 1שתמיד יהיה נָכוֹן. לעתים קרובות אזהרה כזו מעידה על שגיאה לוגית בקוד, ובמקרים אחרים היא פשוט מעידה על בדיקה מיותרת.
אזהרה N7
אזהרת מנתח: יש תתי-ביטויים זהים 'commandArguments.contains("-apikey")' משמאל ומימין ל-'||' מַפעִיל. ArgumentsUtility.cs 42
public static bool arguments_contain_sensitive_information(string
commandArguments)
{
return commandArguments.contains("-install-arguments-sensitive")
|| commandArguments.contains("-package-parameters-sensitive")
|| commandArguments.contains("apikey ")
|| commandArguments.contains("config ")
|| commandArguments.contains("push ")
|| commandArguments.contains("-p ")
|| commandArguments.contains("-p=")
|| commandArguments.contains("-password")
|| commandArguments.contains("-cp ")
|| commandArguments.contains("-cp=")
|| commandArguments.contains("-certpassword")
|| commandArguments.contains("-k ")
|| commandArguments.contains("-k=")
|| commandArguments.contains("-key ")
|| commandArguments.contains("-key=")
|| commandArguments.contains("-apikey")
|| commandArguments.contains("-api-key")
|| commandArguments.contains("-apikey")
|| commandArguments.contains("-api-key");
}המתכנת שכתב את קטע הקוד הזה העתיק והדביק את שתי השורות האחרונות ושכח לערוך אותן. בשל כך, משתמשי Chocolatey לא הצליחו להחיל את הפרמטר apikey עוד כמה דרכים. בדומה לפרמטרים לעיל, אני יכול להציע את האפשרויות הבאות:
commandArguments.contains("-apikey=");
commandArguments.contains("-api-key=");לשגיאות העתק-הדבק יש סיכוי גבוה להופיע במוקדם או במאוחר בכל פרויקט עם כמות גדולה של קוד מקור, ואחד הכלים הטובים ביותר להילחם בהם הוא ניתוח סטטי.
נ.ב. וכמו תמיד, השגיאה הזו נוטה להופיע בסוף מצב מרובה שורות :). ראה פרסום"".
אזהרה N8
אזהרת מנתח: [CWE-476] נעשה שימוש באובייקט 'installedPackage' לפני שהוא אומת כנגד null. קווי בדיקה: 910, 917. NugetService.cs 910
public virtual ConcurrentDictionary<string, PackageResult> get_outdated(....)
{
....
var pinnedPackageResult = outdatedPackages.GetOrAdd(
packageName,
new PackageResult(installedPackage,
_fileSystem.combine_paths(
ApplicationParameters.PackagesLocation,
installedPackage.Id)));
....
if ( installedPackage != null
&& !string.IsNullOrWhiteSpace(installedPackage.Version.SpecialVersion)
&& !config.UpgradeCommand.ExcludePrerelease)
{
....
}
....
}טעות קלאסית: חפץ ראשון חבילה מותקנת נמצא בשימוש ולאחר מכן נבדק ריק. אבחון זה מספר לנו על אחת משתי בעיות בתוכנית: או חבילה מותקנת אף פעם לא שווה ריק, וזה בספק, ואז הסימון מיותר, או שאנחנו עלולים לקבל שגיאה רצינית בקוד - ניסיון לגשת להפניה null.
מסקנה
אז עשינו עוד צעד קטן - עכשיו השימוש ב-PVS-Studio הפך אפילו לקל ונוח יותר. אני גם רוצה לומר ש- Chocolatey הוא מנהל חבילות טוב עם מספר קטן של שגיאות בקוד, שיכולות להיות אפילו פחות בעת שימוש ב- PVS-Studio.
אנחנו מזמינים אותך ותנסה את PVS-Studio. שימוש קבוע בנתח סטטי ישפר את האיכות והאמינות של הקוד שהצוות שלך מפתח ויעזור למנוע הרבה .
נ.ב.
לפני הפרסום שלחנו את המאמר למפתחי Chocolatey והם קיבלו אותה יפה. לא מצאנו שום דבר קריטי, אבל הם, למשל, אהבו את הבאג שמצאנו שקשור למפתח "api-key".
אם ברצונך לשתף מאמר זה עם קהל דובר אנגלית, אנא השתמש בקישור התרגום: ולדיסלב סטוליארוב. .
מקור: www.habr.com
