PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Ένα από τα πιο πρόσφατα σενάρια για τη χρήση του αναλυτή PVS-Studio είναι η ενσωμάτωσή του με συστήματα CI. Και παρόλο που η ανάλυση ενός έργου PVS-Studio από σχεδόν οποιοδήποτε σύστημα συνεχούς ενοποίησης μπορεί να ενσωματωθεί σε λίγες μόνο εντολές, συνεχίζουμε να κάνουμε αυτή τη διαδικασία ακόμα πιο βολική. Το PVS-Studio έχει πλέον υποστήριξη για τη μετατροπή της εξόδου του αναλυτή σε μορφή για TeamCity - TeamCity Inspections Type. Ας δούμε πώς λειτουργεί.

Πληροφορίες σχετικά με το λογισμικό που χρησιμοποιείται

PVS-Στούντιο — ένας στατικός αναλυτής κώδικα C, C++, C# και Java, σχεδιασμένος για να διευκολύνει την εύρεση και τη διόρθωση διαφόρων τύπων σφαλμάτων. Ο αναλυτής μπορεί να χρησιμοποιηθεί σε Windows, Linux και macOS. Σε αυτό το άρθρο θα χρησιμοποιήσουμε ενεργά όχι μόνο τον ίδιο τον αναλυτή, αλλά και ορισμένα βοηθητικά προγράμματα από τη διανομή του.

CLMonitor — είναι ένας διακομιστής παρακολούθησης που παρακολουθεί τις εκκινήσεις μεταγλωττιστών. Πρέπει να εκτελεστεί αμέσως πριν ξεκινήσετε την κατασκευή του έργου σας. Στη λειτουργία παρακολούθησης, ο διακομιστής θα παρεμποδίσει τις εκτελέσεις όλων των υποστηριζόμενων μεταγλωττιστών. Αξίζει να σημειωθεί ότι αυτό το βοηθητικό πρόγραμμα μπορεί να χρησιμοποιηθεί μόνο για την ανάλυση έργων C/C++.

PlogConverter – ένα βοηθητικό πρόγραμμα για τη μετατροπή αναφορών αναλυτών σε διαφορετικές μορφές.

Πληροφορίες για το υπό μελέτη έργο

Ας δοκιμάσουμε αυτήν τη λειτουργία σε ένα πρακτικό παράδειγμα - ας αναλύσουμε το έργο OpenRCT2.

OpenRCT2 - μια ανοιχτή υλοποίηση του παιχνιδιού RollerCoaster Tycoon 2 (RCT2), επεκτείνοντάς το με νέες λειτουργίες και διορθώνοντας σφάλματα. Το παιχνίδι περιστρέφεται γύρω από τη δημιουργία και τη συντήρηση ενός λούνα παρκ που περιέχει βόλτες, καταστήματα και εγκαταστάσεις. Ο παίκτης πρέπει να προσπαθήσει να αποκομίσει κέρδος και να διατηρήσει την καλή φήμη του πάρκου, κρατώντας τους επισκέπτες ευχαριστημένους. Το OpenRCT2 σάς επιτρέπει να παίζετε τόσο σε σενάρια όσο και σε sandbox. Τα σενάρια απαιτούν από τον παίκτη να ολοκληρώσει μια συγκεκριμένη εργασία εντός καθορισμένου χρόνου, ενώ το Sandbox επιτρέπει στον παίκτη να χτίσει ένα πιο ευέλικτο πάρκο χωρίς περιορισμούς ή οικονομικά.

προσαρμογή

Για να εξοικονομήσω χρόνο, μάλλον θα παραλείψω τη διαδικασία εγκατάστασης και θα ξεκινήσω από τη στιγμή που θα έχω τον διακομιστή TeamCity σε λειτουργία στον υπολογιστή μου. Πρέπει να πάμε στο: localhost:{port specified κατά τη διαδικασία εγκατάστασης} (στην περίπτωσή μου, localhost:9090) και να εισαγάγουμε δεδομένα εξουσιοδότησης. Μετά την είσοδο θα μας υποδεχτούν:

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Κάντε κλικ στο κουμπί Δημιουργία έργου. Στη συνέχεια, επιλέξτε Manually και συμπληρώστε τα πεδία.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Αφού πατήσετε το κουμπί Δημιουργία, μας υποδέχεται ένα παράθυρο με ρυθμίσεις.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Ας κάνουμε κλικ Δημιουργία διαμόρφωσης κατασκευής.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Συμπληρώστε τα πεδία και κάντε κλικ Δημιουργία. Βλέπουμε ένα παράθυρο που σας ζητά να επιλέξετε ένα σύστημα ελέγχου έκδοσης. Εφόσον οι πηγές βρίσκονται ήδη τοπικά, κάντε κλικ Μετάβαση.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Τέλος, προχωράμε στις ρυθμίσεις του έργου.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Ας προσθέσουμε βήματα συναρμολόγησης, για να κάνετε αυτό κάντε κλικ: Βήματα κατασκευής -> Προσθήκη βήματος κατασκευής.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Εδώ επιλέγουμε:

  • Τύπος Runner -> Γραμμή εντολών
  • Εκτέλεση -> Προσαρμοσμένη δέσμη ενεργειών

Δεδομένου ότι θα πραγματοποιήσουμε ανάλυση κατά τη σύνταξη του έργου, η συναρμολόγηση και η ανάλυση πρέπει να είναι ένα βήμα, επομένως συμπληρώστε το πεδίο Custom Script:

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Θα εξετάσουμε μεμονωμένα βήματα αργότερα. Είναι σημαντικό ότι η φόρτωση του αναλυτή, η συναρμολόγηση του έργου, η ανάλυσή του, η έξοδος της αναφοράς και η μορφοποίησή του χρειάζονται μόνο έντεκα γραμμές κώδικα.

Το τελευταίο πράγμα που πρέπει να κάνουμε είναι να ορίσουμε τις μεταβλητές περιβάλλοντος, τις οποίες έχω περιγράψει ορισμένους τρόπους βελτίωσης της αναγνωσιμότητάς τους. Για να το κάνουμε αυτό, ας προχωρήσουμε: Παράμετροι -> Προσθήκη νέας παραμέτρου και προσθέστε τρεις μεταβλητές:

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Το μόνο που έχετε να κάνετε είναι να πατήσετε το κουμπί τρέξιμο στην επάνω δεξιά γωνία. Ενώ το έργο συγκεντρώνεται και αναλύεται, θα σας πω για το σενάριο.

Απευθείας σενάριο

Πρώτα, πρέπει να κατεβάσουμε την πιο πρόσφατη διανομή 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"

Στο μεταξύ, η συναρμολόγηση και η ανάλυση του έργου έχει ολοκληρωθεί με επιτυχία, μπορούμε να μεταβούμε στην καρτέλα Έργα και βεβαιωθείτε για αυτό.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Τώρα ας κάνουμε κλικ Σύνολο επιθεωρήσεωνγια να μεταβείτε στην προβολή της αναφοράς του αναλυτή:

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Οι προειδοποιήσεις ομαδοποιούνται κατά αριθμούς διαγνωστικών κανόνων. Για να πλοηγηθείτε στον κωδικό, πρέπει να κάνετε κλικ στον αριθμό γραμμής με την προειδοποίηση. Κάνοντας κλικ στο ερωτηματικό στην επάνω δεξιά γωνία θα ανοίξει μια νέα καρτέλα με τεκμηρίωση. Μπορείτε επίσης να πλοηγηθείτε στον κωδικό κάνοντας κλικ στον αριθμό γραμμής με την προειδοποίηση του αναλυτή. Η πλοήγηση από έναν απομακρυσμένο υπολογιστή είναι δυνατή κατά τη χρήση SourceTreeRoot σημάδι. Όποιος ενδιαφέρεται για αυτόν τον τρόπο λειτουργίας του αναλυτή μπορεί να εξοικειωθεί με την αντίστοιχη ενότητα τεκμηρίωση.

Προβολή των αποτελεσμάτων του αναλυτή

Τώρα που ολοκληρώσαμε την ανάπτυξη και τη διαμόρφωση της κατασκευής, ας ρίξουμε μια ματιά σε μερικές ενδιαφέρουσες προειδοποιήσεις που βρέθηκαν στο έργο που εξετάζουμε.

Προειδοποίηση N1

V773 [CWE-401] Η εξαίρεση απορρίφθηκε χωρίς να απελευθερωθεί ο δείκτης 'αποτέλεσμα'. Πιθανή διαρροή μνήμης. libopenrct2 ObjectFactory.cpp 443

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

V501 Υπάρχουν πανομοιότυπες υπο-εκφράσεις '(1ULL << WIDX_MONTH_BOX)' στα αριστερά και στα δεξιά του '|' χειριστής. libopenrct2ui Cheats.cpp 487

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

V703 Είναι περίεργο το πεδίο "σημαία" στην παράγωγη κλάση "RCT12BannerElement" να αντικαθιστά το πεδίο στη βασική κλάση "RCT12TileElementBase". Έλεγχος γραμμών: RCT12.h:570, RCT12.h:259. libopenrct2 RCT12.h 570

struct RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};
struct rct1_peep : RCT12SpriteBase
{
  ....
  uint8_t flags;
  ....
};

Φυσικά, η χρήση μιας μεταβλητής με το ίδιο όνομα στη βασική κλάση και στην απόγονη δεν είναι πάντα σφάλμα. Ωστόσο, η ίδια η τεχνολογία κληρονομικότητας προϋποθέτει ότι όλα τα πεδία της γονικής τάξης είναι παρόντα στη θυγατρική τάξη. Δηλώνοντας πεδία με το ίδιο όνομα στον κληρονόμο, δημιουργούμε σύγχυση.

Προειδοποίηση N4

V793 Είναι περίεργο το αποτέλεσμα της δήλωσης 'imageDirection / 8' να είναι μέρος της συνθήκης. Ίσως, αυτή η δήλωση θα έπρεπε να συγκριθεί με κάτι άλλο. libopenrct2 ObservationTower.cpp 38

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

V587 Μια περιττή ακολουθία αναθέσεων αυτού του είδους: A = B; Β = Α;. Έλεγχος γραμμών: 1115, 1118. libopenrct2ui MouseInput.cpp 1118

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

V1004 [CWE-476] Ο δείκτης 'player' χρησιμοποιήθηκε με μη ασφαλή τρόπο αφού επαληθεύτηκε έναντι του nullptr. Έλεγχος γραμμών: 2085, 2094. libopenrct2 Network.cpp 2094

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

V547 [CWE-570] Η έκφραση 'name == nullptr' είναι πάντα ψευδής. libopenrct2 ServerList.cpp 102

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

V1048 [CWE-1164] Στη μεταβλητή 'ColumnHeaderPressedCurrentState' εκχωρήθηκε η ίδια τιμή. libopenrct2ui CustomListView.cpp 510

void CustomListView::MouseUp(....)
{
  ....
  if (!ColumnHeaderPressedCurrentState)
  {
    ColumnHeaderPressed = std::nullopt;
    ColumnHeaderPressedCurrentState = false;
    Invalidate();
  }
}

Ο κώδικας φαίνεται πολύ περίεργος. Μου φαίνεται ότι υπήρχε τυπογραφικό λάθος είτε στη συνθήκη είτε κατά την εκ νέου ανάθεση της μεταβλητής ColumnHeaderPressedCurrentState τιμές ψευδής.

Παραγωγή

Όπως μπορούμε να δούμε, η ενσωμάτωση του στατικού αναλυτή PVS-Studio στο έργο TeamCity είναι αρκετά απλή. Για να γίνει αυτό, αρκεί να γράψετε μόνο ένα μικρό αρχείο διαμόρφωσης. Ο έλεγχος του κώδικα θα σας επιτρέψει να εντοπίσετε προβλήματα αμέσως μετά τη συναρμολόγηση, κάτι που θα βοηθήσει στην εξάλειψή τους όταν η πολυπλοκότητα και το κόστος των αλλαγών είναι ακόμα χαμηλά.

PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2
Εάν θέλετε να μοιραστείτε αυτό το άρθρο με ένα αγγλόφωνο κοινό, χρησιμοποιήστε τον σύνδεσμο μετάφρασης: Vladislav Stolyarov. PVS-Studio και Continuous Integration: TeamCity. Ανάλυση του έργου Open RollerCoaster Tycoon 2.

Πηγή: www.habr.com

Προσθέστε ένα σχόλιο