Ένα από τα πιο πρόσφατα σενάρια για τη χρήση του αναλυτή PVS-Studio είναι η ενσωμάτωσή του με συστήματα CI. Και παρόλο που η ανάλυση ενός έργου PVS-Studio από σχεδόν οποιοδήποτε σύστημα συνεχούς ενοποίησης μπορεί να ενσωματωθεί σε λίγες μόνο εντολές, συνεχίζουμε να κάνουμε αυτή τη διαδικασία ακόμα πιο βολική. Το PVS-Studio έχει πλέον υποστήριξη για τη μετατροπή της εξόδου του αναλυτή σε μορφή για TeamCity - TeamCity Inspections Type. Ας δούμε πώς λειτουργεί.
Πληροφορίες σχετικά με το λογισμικό που χρησιμοποιείται
Πληροφορίες για το υπό μελέτη έργο
Ας δοκιμάσουμε αυτήν τη λειτουργία σε ένα πρακτικό παράδειγμα - ας αναλύσουμε το έργο OpenRCT2.
προσαρμογή
Για να εξοικονομήσω χρόνο, μάλλον θα παραλείψω τη διαδικασία εγκατάστασης και θα ξεκινήσω από τη στιγμή που θα έχω τον διακομιστή TeamCity σε λειτουργία στον υπολογιστή μου. Πρέπει να πάμε στο: localhost:{port specified κατά τη διαδικασία εγκατάστασης} (στην περίπτωσή μου, localhost:9090) και να εισαγάγουμε δεδομένα εξουσιοδότησης. Μετά την είσοδο θα μας υποδεχτούν:
Κάντε κλικ στο κουμπί Δημιουργία έργου. Στη συνέχεια, επιλέξτε Manually και συμπληρώστε τα πεδία.
Αφού πατήσετε το κουμπί Δημιουργία, μας υποδέχεται ένα παράθυρο με ρυθμίσεις.
Ας κάνουμε κλικ Δημιουργία διαμόρφωσης κατασκευής.
Συμπληρώστε τα πεδία και κάντε κλικ Δημιουργία. Βλέπουμε ένα παράθυρο που σας ζητά να επιλέξετε ένα σύστημα ελέγχου έκδοσης. Εφόσον οι πηγές βρίσκονται ήδη τοπικά, κάντε κλικ Μετάβαση.
Τέλος, προχωράμε στις ρυθμίσεις του έργου.
Ας προσθέσουμε βήματα συναρμολόγησης, για να κάνετε αυτό κάντε κλικ: Βήματα κατασκευής -> Προσθήκη βήματος κατασκευής.
Εδώ επιλέγουμε:
- Τύπος Runner -> Γραμμή εντολών
- Εκτέλεση -> Προσαρμοσμένη δέσμη ενεργειών
Δεδομένου ότι θα πραγματοποιήσουμε ανάλυση κατά τη σύνταξη του έργου, η συναρμολόγηση και η ανάλυση πρέπει να είναι ένα βήμα, επομένως συμπληρώστε το πεδίο Custom Script:
Θα εξετάσουμε μεμονωμένα βήματα αργότερα. Είναι σημαντικό ότι η φόρτωση του αναλυτή, η συναρμολόγηση του έργου, η ανάλυσή του, η έξοδος της αναφοράς και η μορφοποίησή του χρειάζονται μόνο έντεκα γραμμές κώδικα.
Το τελευταίο πράγμα που πρέπει να κάνουμε είναι να ορίσουμε τις μεταβλητές περιβάλλοντος, τις οποίες έχω περιγράψει ορισμένους τρόπους βελτίωσης της αναγνωσιμότητάς τους. Για να το κάνουμε αυτό, ας προχωρήσουμε: Παράμετροι -> Προσθήκη νέας παραμέτρου και προσθέστε τρεις μεταβλητές:
Το μόνο που έχετε να κάνετε είναι να πατήσετε το κουμπί τρέξιμο στην επάνω δεξιά γωνία. Ενώ το έργο συγκεντρώνεται και αναλύεται, θα σας πω για το σενάριο.
Απευθείας σενάριο
Πρώτα, πρέπει να κατεβάσουμε την πιο πρόσφατη διανομή PVS-Studio. Για αυτό χρησιμοποιούμε τον διαχειριστή πακέτων Chocolatey. Για όσους θέλουν να μάθουν περισσότερα για αυτό, υπάρχει ένα αντίστοιχο
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"
Στο μεταξύ, η συναρμολόγηση και η ανάλυση του έργου έχει ολοκληρωθεί με επιτυχία, μπορούμε να μεταβούμε στην καρτέλα Έργα και βεβαιωθείτε για αυτό.
Τώρα ας κάνουμε κλικ Σύνολο επιθεωρήσεωνγια να μεταβείτε στην προβολή της αναφοράς του αναλυτή:
Οι προειδοποιήσεις ομαδοποιούνται κατά αριθμούς διαγνωστικών κανόνων. Για να πλοηγηθείτε στον κωδικό, πρέπει να κάνετε κλικ στον αριθμό γραμμής με την προειδοποίηση. Κάνοντας κλικ στο ερωτηματικό στην επάνω δεξιά γωνία θα ανοίξει μια νέα καρτέλα με τεκμηρίωση. Μπορείτε επίσης να πλοηγηθείτε στον κωδικό κάνοντας κλικ στον αριθμό γραμμής με την προειδοποίηση του αναλυτή. Η πλοήγηση από έναν απομακρυσμένο υπολογιστή είναι δυνατή κατά τη χρήση 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 θα είναι ψευδής αν 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;
....
}
....
}
Αυτό το τμήμα κώδικα πιθανότατα λήφθηκε με αποσυμπίληση. Στη συνέχεια, κρίνοντας από το σχόλιο που άφησε, αφαιρέθηκε μέρος του μη λειτουργικού κώδικα. Ωστόσο, απομένουν ακόμη μερικές επεμβάσεις δρομέας, που επίσης δεν έχουν πολύ νόημα.
Προειδοποίηση 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); // <=
}
....
}
Αυτός ο κωδικός διορθώνεται αρκετά εύκολα, απλά πρέπει να τον ελέγξετε για τρίτη φορά παίχτης σε έναν μηδενικό δείκτη ή προσθέστε τον στο σώμα της πρότασης υπό όρους. Θα πρότεινα τη δεύτερη επιλογή:
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 τιμές ψευδής.
Παραγωγή
Όπως μπορούμε να δούμε, η ενσωμάτωση του στατικού αναλυτή PVS-Studio στο έργο TeamCity είναι αρκετά απλή. Για να γίνει αυτό, αρκεί να γράψετε μόνο ένα μικρό αρχείο διαμόρφωσης. Ο έλεγχος του κώδικα θα σας επιτρέψει να εντοπίσετε προβλήματα αμέσως μετά τη συναρμολόγηση, κάτι που θα βοηθήσει στην εξάλειψή τους όταν η πολυπλοκότητα και το κόστος των αλλαγών είναι ακόμα χαμηλά.
Εάν θέλετε να μοιραστείτε αυτό το άρθρο με ένα αγγλόφωνο κοινό, χρησιμοποιήστε τον σύνδεσμο μετάφρασης: Vladislav Stolyarov.
Πηγή: www.habr.com