如何用C和C ++拍攝自己的腳。Haiku OS Cookbook

關於PVS-Studio靜態分析器和Haiku OS代碼如何相遇的故事可以追溯到2015年。對於這兩個項目的團隊來說,這是一個令人興奮的實驗和有用的經驗。爲什麼要做實驗?那一刻,我們沒有Linux的分析儀,我們不會再用一年半了。無論如何,我們團隊的愛好者的努力得到了回報:我們結識了Haiku開發人員並提高了代碼質量,擴大了我們的錯誤基礎,開發人員製造了罕見的錯誤並改進了分析器。現在,您可以輕鬆快速地檢查Haiku代碼中的錯誤。

圖片1

介紹

滿足我們故事的主要特徵 - 帶有開源代碼的Haiku和用於C,C ++,C#和Java 的PVS-Studio靜態分析器。當我們在4.5年前深入研究項目分析時,我們只需處理已編譯的可執行分析器文件。用於解析編譯器參數,運行預處理器,並行分析等的所有基礎結構都來自實用程序Compiler Monitoring UI,用C#編寫。該實用程序部分移植到Mono平臺,以便在Linux中運行。Haiku項目是使用各種操作系統下的交叉編譯器構建的,Windows除外。再一次,我想提一下與Haiku大樓相關的便利性和文檔完整性。另外,我要感謝Haiku開發人員在構建項目方面提供的幫助。

現在執行分析要簡單得多。以下是用於構建和分析項目的所有命令的列表:

cd /opt
git clone https://review.haiku-os.org/buildtools
git clone https://review.haiku-os.org/haiku
cd ./haiku
mkdir generated.x86_64; cd generated.x86_64
../configure --distro-compatibility official -j12 \
  --build-cross-tools x86_64 ../../buildtools
cd ../../buildtools/jam
make all
cd /opt/haiku/generated.x86_64
pvs-studio-analyzer trace -- /opt/buildtools/jam/bin.linuxx86/jam \
  -q -j1 @nightly-anyboot
pvs-studio-analyzer analyze -l /mnt/svn/PVS-Studio.lic -r /opt/haiku \
   -C x86_64-unknown-haiku-gcc -o /opt/haiku/haiku.log -j12

順便說一句,項目分析是在Docker容器中實現的。最近我們準備了關於這個主題的新文檔:在Docker中運行PVS-Studio。這可以使一些公司很容易爲他們的項目應用靜態分析技術。

未初始化的變量

使用V614未初始化的變量'rval'。fetch.c 1727

int
auto_fetch(int argc, char *argv[])
{
  volatile int  argpos;
  int    rval;                  // <=
  argpos = 0;

  if (sigsetjmp(toplevel, 1)) {
    if (connected)
      disconnect(0, NULL);
    if (rval > 0)               // <=
      rval = argpos + 1;
    return (rval);
  }
  ....
}
該RVAL變量尚未初始化的聲明,所以它與空

值將導致不確定的結果比較。如果情況失敗,則rval變量的不確定值 可以成爲auto_fetch函數的返回值。

使用V614未初始化指針'res'。commands.c 2873

    struct addrinfo {
     int ai_flags;
     int ai_family;
     int ai_socktype;
     int ai_protocol;
     socklen_t ai_addrlen;
     char *ai_canonname;
     struct sockaddr *ai_addr;
     struct addrinfo *ai_next;
    };

static int
sourceroute(struct addrinfo *ai, char *arg, char **cpp,
            int *lenp, int *protop, int *optp)
{
  static char buf[1024 + ALIGNBYTES];
  char *cp, *cp2, *lsrp, *ep;
  struct sockaddr_in *_sin;
#ifdef INET6
  struct sockaddr_in6 *sin6;
  struct ip6_rthdr *rth;
#endif
  struct addrinfo hints, *res;     // <=
  int error;
  char c;

  if (cpp == NULL || lenp == NULL)
    return -1;
  if (*cpp != NULL) {
    switch (res->ai_family) {      // <=
    case AF_INET:
      if (*lenp < 7)
        return -1;
      break;
      ....
    }
  }
  ....
}

下面是使用未初始化變量的類似情況,除了res是在此處發生的未初始化指針。

V506指向局部變量“normalized”的指針存儲在此變量的範圍之外。這樣的指針將變爲無效。TextView.cpp 5596

void
BTextView::_ApplyStyleRange(...., const BFont font, ....)
{
if (font != NULL) {
BFont normalized =
font;
_NormalizeFont(&normalized);
font = &normalized;
}
....
fStyles->SetStyleRange(fromOffset, toOffset, fText->Length(), mode,
font, color);
}

程序員可能需要使用中間變量來規範化對象。但現在字體指針包含指向規範化對象的指針,該指針將在退出創建臨時對象的作用域後刪除。

V603對象已創建但未使用。如果你想調用構造函數,應該使用'this->

BUnicodeChar :: BUnicodeChar(....)'。UnicodeChar.cpp 27

int8
BUnicodeChar::Type(uint32 c)
{
  BUnicodeChar();
  return u_charType(c);
}

C ++程序員中一個非常常見的錯誤是使用構造函數的調用來初始化/取消類字段。在這種情況下,不會對類字段進行修改,但會創建此類的新未命名對象,然後立即銷燬。不幸的是,項目中有很多這樣的地方:

  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 37
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 49
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 58
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 67
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 77
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 89
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 103
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 115
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 126
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 142
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 152
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 163
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 186
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 196
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 206
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 214
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 222
  • V603對象已創建但未使用。如果你想調用構造函數,應該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 230

V670未初始化的類成員'fPatternHandler'用於初始化'fInternal'成員。請記住,成員是按照其在類中的聲明順序初始化的。Painter.cpp 184

Painter::Painter()
  :
  fInternal(fPatternHandler),
  ....
  fPatternHandler(),
  ....
{
  ....
};

class Painter {
  ....
private:
  mutable PainterAggInterface fInternal; // line 336

  bool fSubpixelPrecise : 1;
  bool fValidClipping : 1;
  bool fDrawingText : 1;
  bool fAttached : 1;
  bool fIdentityTransform : 1;

  Transformable fTransform;
  float fPenSize;
  const BRegion* fClippingRegion;
  drawing_mode fDrawingMode;
  source_alpha fAlphaSrcMode;
  alpha_function fAlphaFncMode;
  cap_mode fLineCapMode;
  join_mode fLineJoinMode;
  float fMiterLimit;

  PatternHandler fPatternHandler;        // line 355
  mutable AGGTextRenderer fTextRenderer;
};

另一個錯誤初始化的例子。類字段按其在類本身中的聲明順序初始化。在此示例中,fInternal字段將是第一個使用未初始化的fPatternHandler值進行初始化的字段。

可疑#define
V523'then '語句相當於'else'語句。subr_gtaskqueue.c 191

#define  TQ_LOCK(tq)              \
  do {                \
    if ((tq)->tq_spin)          \
      mtx_lock_spin(&(tq)->tq_mutex);      \
    else              \
      mtx_lock(&(tq)->tq_mutex);      \
  } while (0)
#define  TQ_ASSERT_LOCKED(tq)  mtx_assert(&(tq)->tq_mutex, MA_OWNED)

#define  TQ_UNLOCK(tq)              \
  do {                \
    if ((tq)->tq_spin)          \
      mtx_unlock_spin(&(tq)->tq_mutex);    \
    else              \
      mtx_unlock(&(tq)->tq_mutex);      \
  } while (0)

void
grouptask_block(struct grouptask *grouptask)
{
  ....
  TQ_LOCK(queue);
  gtask->ta_flags |= TASK_NOENQUEUE;
  gtaskqueue_drain_locked(queue, gtask);
  TQ_UNLOCK(queue);
}

在查看預處理器結果之前,此代碼段看起來並不可疑:

void
grouptask_block(struct grouptask *grouptask)
{
  ....
  do { if ((queue)->tq_spin) mtx_lock(&(queue)->tq_mutex);
       else mtx_lock(&(queue)->tq_mutex); } while (0);
  gtask->ta_flags |= 0x4;
  gtaskqueue_drain_locked(queue, gtask);
   do { if ((queue)->tq_spin) mtx_unlock(&(queue)->tq_mutex);
        else mtx_unlock(&(queue)->tq_mutex); } while (0);
}

分析儀是真的正確 - 如果和其他分支是相同的。但是mtx_lock_spin和mtx_unlock_spin在哪裏起作用?宏TQ_LOCK,TQ_UNLOCK和grouptask_block函數在一個文件中聲明幾乎彼此相鄰,但是在這裏某處發生了替換。

搜索文件僅導致mutex.h具有以下內容:

/* on FreeBSD these are different functions */
#define mtx_lock_spin(x)   mtx_lock(x)
#define mtx_unlock_spin(x) mtx_unlock(x)

項目開發人員應檢查這種替換是否正確。我在Linux中檢查了這個項目,這樣的替換對我來說似乎很可疑。

自由功能的錯誤
V575空指針傳遞給'free'函數。檢查第一個參數。setmime.cpp 727

void
MimeType::_PurgeProperties()
{
  fShort.Truncate(0);
  fLong.Truncate(0);
  fPrefApp.Truncate(0);
  fPrefAppSig.Truncate(0);
  fSniffRule.Truncate(0);

  delete fSmallIcon;
  fSmallIcon = NULL;

  delete fBigIcon;
  fBigIcon = NULL;

  fVectorIcon = NULL;            // <=
  free(fVectorIcon);             // <=

  fExtensions.clear();
  fAttributes.clear();
}

您可以在free函數中傳遞空指針,但這種用法肯定是可疑的。因此,分析儀找到了混合的代碼行。首先,代碼作者必須通過fVectorIcon指針釋放內存,然後才分配NULL。

V575空指針傳遞給'free'函數。檢查第一個參數。driver_settings.cpp 461

static settings_handle *
load_driver_settings_from_file(int file, const char *driverName)
{
  ....
  handle = new_settings(text, driverName);
  if (handle != NULL) {
    // everything went fine!
    return handle;
  }

  free(handle);           // <=
  ....
}

這是顯式將空指針傳遞給自由函數的另一個示例。該行可以刪除,因爲該函數在成功獲得指針後退出。

V575空指針傳遞給'free'函數。檢查第一個參數。PackageFileHeapWriter.cpp 166

void* _GetBuffer()
{
  ....
  void* buffer = malloc(fBufferSize);
  if (buffer == NULL && !fBuffers.AddItem(buffer)) {
    free(buffer);
    throw std::bad_alloc();
  }
  return buffer;
}

有人在這裏犯了錯誤。必須使用||運算符而不是&&。只有在這種情況下,如果內存分配(使用malloc函數)失敗,則拋出std :: bad_alloc()異常。

刪除操作符出錯
V611使用'new T []'運算符分配內存,但使用'delete'運算符釋放。考慮檢查此代碼。使用'delete [] fMsg;'可能更好。Err.cpp 65

class Err {
public:
 ....
private:
 char *fMsg;
 ssize_t fPos;
};

void
Err::Unset() {
 delete fMsg;                                   // <=
 fMsg = __null;
 fPos = -1;
}

void
Err::SetMsg(const char *msg) {
 if (fMsg) {
  delete fMsg;                                  // <=
  fMsg = __null;
 }
 if (msg) {
  fMsg = new(std::nothrow) char[strlen(msg)+1]; // <=
  if (fMsg)
   strcpy(fMsg, msg);
 }
}

所述fMsg指針用於爲字符數組分配內存。該刪除操作被用來釋放所述存儲器,而不是刪除[] 。

V611內存使用'new'運算符分配,但是使用'free'函數釋放。考慮檢查'wrapperPool'變量後面的操作邏輯。vm_page.cpp 3080

status_t
vm_page_write_modified_page_range(....)
{
  ....
  PageWriteWrapper* wrapperPool
    = new(malloc_flags(allocationFlags)) PageWriteWrapper[maxPages + 1];
  PageWriteWrapper** wrappers
    = new(malloc_flags(allocationFlags)) PageWriteWrapper*[maxPages];
  if (wrapperPool == NULL || wrappers == NULL) {
    free(wrapperPool);                              // <=
    free(wrappers);                                 // <=
    wrapperPool = stackWrappersPool;
    wrappers = stackWrappers;
    maxPages = 1;
  }
  ....
}

這裏malloc_flags是一個調用malloc的函數。然後placement-new在這裏構造對象。由於PageWriteWrapper類以下列方式實現:

class PageWriteWrapper {
public:
 PageWriteWrapper();
 ~PageWriteWrapper();
 void SetTo(vm_page* page);
 bool Done(status_t result);

private:
 vm_page* fPage;
 struct VMCache* fCache;
 bool fIsActive;
};

PageWriteWrapper::PageWriteWrapper()
 :
 fIsActive(false)
{
}

PageWriteWrapper::~PageWriteWrapper()
{
 if (fIsActive)
  panic("page write wrapper going out of scope but isn't completed");
}

由於使用free函數釋放內存,因此不會調用此類的對象析構函數。

V611使用'new T []'運算符分配內存,但使用'delete'運算符釋放。考慮檢查此代碼。使用'delete [] fOutBuffer;'可能更好。檢查線:26,45。PCL6Rasterizer.h 26

class PCL6Rasterizer : public Rasterizer
{
public:
  ....
  ~PCL6Rasterizer()
  {
    delete fOutBuffer;
    fOutBuffer = NULL;
  }
  ....
  virtual void InitializeBuffer()
  {
    fOutBuffer = new uchar[fOutBufferSize];
  }
private:
  uchar* fOutBuffer;
  int    fOutBufferSize;
};

使用delete運算符而不是delete []是一個常見錯誤。編寫類時最容易出錯,因爲析構函數的代碼通常遠離內存位置。這裏,程序員錯誤地釋放了析構函數中fOutBuffer指針存儲的內存。

V772爲void指針調用'delete'操作符將導致未定義的行爲。Hashtable.cpp 207

void
Hashtable::MakeEmpty(int8 keyMode,int8 valueMode)
{
  ....
  for (entry = fTable[index]; entry; entry = next) {
    switch (keyMode) {
      case HASH_EMPTY_DELETE:
        // TODO: destructors are not called!
        delete (void*)entry->key;
        break;
      case HASH_EMPTY_FREE:
        free((void*)entry->key);
        break;
    }
    switch (valueMode) {
      case HASH_EMPTY_DELETE:
        // TODO: destructors are not called!
        delete entry->value;
        break;
      case HASH_EMPTY_FREE:
        free(entry->value);
        break;
    }
    next = entry->next;
    delete entry;
  }
  ....
}

除了刪除 / 刪除[]和free之間的錯誤選擇之外,還可以在嘗試通過指向void類型(void *)的指針清除內存時遇到未定義的行爲。

沒有返回值的函數
V591非空函數應返回一個值。Referenceable.h 228

BReference& operator=(const BReference<const Type>& other)
{
  fReference = other.fReference;
}

重載賦值運算符缺少返回值。在這種情況下,操作員將返回一個隨機值,這可能導致奇怪的錯誤。

以下是此類其他代碼片段中的類似問題:

  • V591非空函數應返回一個值。Referenceable.h 233
  • V591非空函數應返回一個值。Referenceable.h 239

V591非空函數應返回一個值。main.c 1010

void errx(int, const char *, ...) ;

char *
getoptionvalue(const char *name)
{
  struct option *c;

  if (name == NULL)
    errx(1, "getoptionvalue() invoked with NULL name");
  c = getoption(name);
  if (c != NULL)
    return (c->value);
  errx(1, "getoptionvalue() invoked with unknown option '%s'", name);
  /* NOTREACHED */
}

用戶的評論NOTREACHED在這裏沒有任何意義。您需要將函數註釋爲noreturn才能正確編寫此類場景的代碼。爲此,有noreturn屬性:標準和特定於編譯器。首先,編譯器會考慮這些屬性,以便使用警告正確生成代碼或通知某些類型的錯誤。各種靜態分析工具還考慮了屬性以提高分析質量。

處理異常
V596對象已創建但未使用。'throw'關鍵字可能會丟失:拋出

ParseException(FOO); Response.cpp 659

size_t
Response::ExtractNumber(BDataIO& stream)
{
  BString string = ExtractString(stream);

  const char* end;
  size_t number = strtoul(string.String(), (char**)&end, 10);
  if (end == NULL || end[0] != '\0')
    ParseException("Invalid number!");

  return number;
}

關鍵字throw在這裏被意外遺忘了。因此,在退出作用域時,將簡單地銷燬此類的對象時,不會生成ParseException異常。之後,該功能將繼續工作,就像沒有發生任何事情一樣,就像輸入了正確的數字一樣。

V1022指針拋出異常。考慮改爲按價值拋出它。gensyscallinfos.cpp 316

int
main(int argc, char** argv)
{
  try {
    return Main().Run(argc, argv);
  } catch (Exception& exception) {                                         // <=
    fprintf(stderr, "%s\n", exception.what());
    return 1;
  }
}

int Run(int argc, char** argv)
{
  ....
  _ParseSyscalls(argv[1]);
  ....
}

void _ParseSyscalls(const char* filename)
{
  ifstream file(filename, ifstream::in);
  if (!file.is_open())
    throw new IOException(string("Failed to open '") + filename + "'.");   // <=
  ....
}

分析器檢測到指針拋出的IOException異常。拋出指針會導致異常不會被捕獲。因此異常最終被引用捕獲。此外,指針的使用迫使捕獲方調用delete操作符來銷燬尚未完成的創建對象。

其他幾個代碼片段有問題:

  • V1022指針拋出異常。考慮改爲按價值拋出它。gensyscallinfos.cpp 347
  • V1022指針拋出異常。考慮改爲按價值拋出它。gensyscallinfos.cpp 413

正式的安全

V597編譯器可以刪除'memset'函數調用,該調用用於刷新'f_key'對象。memset_s()函數應該用於擦除私有數據。dst_api.c 1018

#ifndef SAFE_FREE
#define SAFE_FREE(a) \
do{if(a != NULL){memset(a,0, sizeof(*a)); free(a); a=NULL;}} while (0)
....
#endif

DST_KEY *
dst_free_key(DST_KEY *f_key)
{
  if (f_key == NULL)
    return (f_key);
  if (f_key->dk_func && f_key->dk_func->destroy)
    f_key->dk_KEY_struct =
      f_key->dk_func->destroy(f_key->dk_KEY_struct);
  else {
    EREPORT(("dst_free_key(): Unknown key alg %d\n",
       f_key->dk_alg));
  }
  if (f_key->dk_KEY_struct) {
    free(f_key->dk_KEY_struct);
    f_key->dk_KEY_struct = NULL;
  }
  if (f_key->dk_key_name)
    SAFE_FREE(f_key->dk_key_name);
  SAFE_FREE(f_key);
  return (NULL);
}

分析儀檢測到可疑代碼,用於安全的私人數據清除。不幸的是,擴展到memset,自由調用和NULL賦值的SAFE_FREE宏不會使代碼更安全,因爲在使用O2進行優化時,編譯器都會刪除它。

順便說一句,除了CWE-14:編譯器刪除代碼以清除緩衝區之外別無其他。

以下是未實際清除緩衝區的位置列表:

  • V597編譯器可以刪除'memset'函數調用,該調用用於刷新'encoded_block'緩衝區。memset_s()函數應該用於擦除私有數據。dst_api.c446
  • V597編譯器可以刪除'memset'函數調用,該調用用於刷新'key_st'對象。memset_s()函數應該用於擦除私有數據。dst_api.c685
  • V597編譯器可以刪除'memset'函數調用,該調用用於刷新'in_buff'緩衝區。memset_s()函數應該用於擦除私有數據。dst_api.c916
  • V597編譯器可以刪除'memset'函數調用,該調用用於刷新'ce'對象。memset_s()函數應該用於擦除私有數據。fs_cache.c1078

與無符號變量的比較

V547表達式'剩餘<0'始終爲false。無符號類型值永遠不會<0。DwarfFile.cpp 1947

status_t
DwarfFile::_UnwindCallFrame(....)
{
  ....
  uint64 remaining = lengthOffset + length - dataReader.Offset();
  if (remaining < 0)
    return B_BAD_DATA;
  ....
}

分析器找到了無符號變量與負值的明確比較。也許,應該只將剩餘變量與null 進行比較,或者實現溢出檢查。

V547表達式'sleep((unsigned)secs)<0'始終爲false。無符號類型值永遠不會<0。misc.cpp 56

status_t
snooze(bigtime_t amount)
{
  if (amount <= 0)
    return B_OK;

  int64 secs = amount / 1000000LL;
  int64 usecs = amount % 1000000LL;
  if (secs > 0) {
    if (sleep((unsigned)secs) < 0)     // <=
      return errno;
  }

  if (usecs > 0) {
    if (usleep((useconds_t)usecs) < 0)
      return errno;
  }

  return B_OK;
}

爲了得到錯誤的要點,讓我們解決的簽名睡眠和usleep功能:

  • extern unsigned int sleep (unsigned int __seconds);
  • extern int usleep(__useconds_t __useconds);

我們可以看到,sleep函數返回無符號值,並且它在代碼中的用法不正確。

危險的指針
V774釋放內存後使用'device'指針。xhci.cpp 1572

void
XHCI::FreeDevice(Device *device)
{
  uint8 slot = fPortSlots[device->HubPort()];
  TRACE("FreeDevice() port %d slot %d\n", device->HubPort(), slot);

  // Delete the device first, so it cleans up its pipes and tells us
  // what we need to destroy before we tear down our internal state.
  delete device;

  DisableSlot(slot);
  fDcba->baseAddress[slot] = 0;
  fPortSlots[device->HubPort()] = 0;            // <=
  delete_area(fDevices[slot].trb_area);
  delete_area(fDevices[slot].input_ctx_area);
  delete_area(fDevices[slot].device_ctx_area);

  memset(&fDevices[slot], 0, sizeof(xhci_device));
  fDevices[slot].state = XHCI_STATE_DISABLED;
}

甲設備對象 由釋放刪除操作符。FreeDevice函數非常符合邏輯。但是,出於某種原因,爲了釋放其他資源,已經刪除的對象得到了解決。

這樣的代碼非常危險,可以在其他幾個地方遇到:

  • V774釋放內存後使用'self'指針。TranslatorRoster.cpp 884
  • V774釋放內存後使用'string'指針。RemoteView.cpp 1269
  • V774釋放內存後使用'bs'指針。mkntfs.c4291
  • V774釋放內存後使用'bs'指針。mkntfs.c 4308
  • V774重新分配內存後使用'al'指針。inode.c 1155

    V522可能會發生空指針“數據”的解除引用。空指針被傳遞到'malo_hal_send_helper'函數中。檢查第三個參數。檢查行:350,394。if_malohal.c 350

    static int
    malo_hal_fwload_helper(struct malo_hal mh, char helper)
    {
    ....
    / tell the card we're done and... /
    error = malo_hal_send_helper(mh, 0, NULL, 0, MALO_NOWAIT); // <= NULL
    ....
    }

    static int
    malo_hal_send_helper(struct malo_hal mh, int bsize,
    const void
    data, size_t dsize, int waitfor)
    {
    mh->mh_cmdbuf[0] = htole16(MALO_HOSTCMD_CODE_DNLD);
    mh->mh_cmdbuf[1] = htole16(bsize);
    memcpy(&mh->mh_cmdbuf[4], data , dsize); // <= data
    ....
    }

過程間分析揭示了NULL傳遞給函數的情況,並且具有這樣值的數據指針最終在memcpy函數中被解引用。

V773退出該函數時未釋放'inputFileFile'指針。內存泄漏是可能的。command_recompress.cpp 119

int
command_recompress(int argc, const char* const* argv)
{
  ....
  BFile* inputFileFile = new BFile;
  error = inputFileFile->SetTo(inputPackageFileName, O_RDONLY);
  if (error != B_OK) {
    fprintf(stderr, "Error: Failed to open input file \"%s\": %s\n",
      inputPackageFileName, strerror(error));
    return 1;
  }
  inputFile = inputFileFile;
  ....
}

PVS-Studio可以檢測內存泄漏。在此示例中,如果發生錯誤,則不會釋放內存。有人可能會認爲如果出現錯誤,你不應該爲內存釋放煩惱,因爲程序仍然會結束。但並非總是如此。許多程序要求正確處理錯誤並繼續工作。

V595'fReply '指針在針對nullptr進行驗證之前使用。檢查行:49,52。ReplyBuilder.cpp 49

RPC::CallbackReply*
ReplyBuilder::Reply()
{
  fReply->Stream().InsertUInt(fStatusPosition, _HaikuErrorToNFS4(fStatus));
  fReply->Stream().InsertUInt(fOpCountPosition, fOpCount);

  if (fReply == NULL || fReply->Stream().Error() == B_OK)
    return fReply;
  else
    return NULL;
}

在檢查它們之前取消引用指針是一個非常常見的錯誤。在V595診斷幾乎總是優先於一個項目的警告數量。此代碼片段包含fReply指針的危險用法。

V595'mq '指針在針對nullptr進行驗證之前使用。檢查線:782,786。oce_queue.c 782

static void
oce_mq_free(struct oce_mq *mq)
{
  POCE_SOFTC sc = (POCE_SOFTC) mq->parent;
  struct oce_mbx mbx;
  struct mbx_destroy_common_mq *fwcmd;

  if (!mq)
    return;
  ....
}

一個類似的例子。該毫克指針被解除引用幾行早於它的檢查無效。項目中有很多類似的地方。在某些片段中,指針的使用和檢查彼此相距很遠,因此在本文中您將只找到幾個這樣的示例。歡迎開發人員查看完整分析器報告中的其他示例。

V645'strncat '函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp 101

static void
dump_acpi_namespace(acpi_ns_device_info *device, char *root, int indenting)
{
  char output[320];
  char tabs[255] = "";
  ....
  strlcat(tabs, "|--- ", sizeof(tabs));
  ....
  while (....) {
    uint32 type = device->acpi->get_object_type(result);
    snprintf(output, sizeof(output), "%s%s", tabs, result + depth);
    switch(type) {
      case ACPI_TYPE_INTEGER:
        strncat(output, "     INTEGER", sizeof(output));
        break;
      case ACPI_TYPE_STRING:
        strncat(output, "     STRING", sizeof(output));
        break;
      ....
    }
    ....
  }
  ....
}

strlcat和strncat函數之間的區別對於不熟悉這些函數描述的人來說並不是很明顯。的strlcat提供函數希望整個緩衝區作爲而第三個參數的大小strncat函數功能-的自由空間的在緩衝器的大小,這需要在調用之前評估所需值。但開發人員經常忘記或不知道它。將整個緩衝區大小傳遞給strncat函數可能會導致緩衝區溢出,因爲該函數會將此值視爲要複製的可接受字符數。該strlcat提供功能沒有這樣的問題。但是你必須傳遞字符串,以terminal null結尾,以便它正常工作。

以下是包含字符串的危險地點的完整列表:

  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp104
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp107
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp110
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp113
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp118
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp119
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp120
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp123
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp126
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp129
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp132
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp135
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp138
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp141
  • V645'strncat'函數調用可能導致'output'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。NamespaceDump.cpp144
  • V645'strncat'函數調用可能導致'features_string'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。VirtioDevice.cpp283
  • V645'strncat'函數調用可能導致'features_string'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。VirtioDevice.cpp284
  • V645'strncat'函數調用可能導致'features_string'緩衝區溢出。邊界不應包含緩衝區的大小,而應包含它可以容納的多個字符。VirtioDevice.cpp
    285

V792'SetDecoratorSettings '函數位於運算符'|'的右側無論左操作數的值如何,都將被調用。也許,最好使用'||'。DesktopListener.cpp 324

class DesktopListener : public DoublyLinkedListLinkImpl<DesktopListener> {
public:
 ....
 virtual bool SetDecoratorSettings(Window* window,
         const BMessage& settings) = 0;
 ....
};

bool
DesktopObservable::SetDecoratorSettings(Window* window,
  const BMessage& settings)
{
  if (fWeAreInvoking)
    return false;
  InvokeGuard invokeGuard(fWeAreInvoking);

  bool changed = false;
  for (DesktopListener* listener = fDesktopListenerList.First();
    listener != NULL; listener = fDesktopListenerList.GetNext(listener))
    changed = changed | listener->SetDecoratorSettings(window, settings);

  return changed;
}

最有可能的是,'|' 和'||' 經營者感到困惑。此錯誤導致不必要的SetDecoratorSettings函數調用。

V627考慮檢查表達式。sizeof()的參數是擴展爲數字的宏。device.c 72

#define PCI_line_size 0x0c /* (1 byte) cache line size in 32 bit words */

static status_t
wb840_open(const char* name, uint32 flags, void** cookie)
{
  ....
  data->wb_cachesize = gPci->read_pci_config(data->pciInfo->bus,
    data->pciInfo->device, data->pciInfo->function, PCI_line_size,
    sizeof(PCI_line_size)) & 0xff;
  ....
}

將0x0c值傳遞給sizeof運算符看起來很可疑。也許,作者應該評估對象的大小,例如數據。

V562將bool類型值與值18進行比較是奇怪的:0x12 == IsProfessionalSpdif()。CEchoGals_mixer.cpp 533

typedef bool BOOL;

virtual BOOL IsProfessionalSpdif() { ... }

#define ECHOSTATUS_DSP_DEAD 0x12

ECHOSTATUS CEchoGals::ProcessMixerFunction(....)
{
  ....
  if ( ECHOSTATUS_DSP_DEAD == IsProfessionalSpdif() ) // <=
  {
    Status = ECHOSTATUS_DSP_DEAD;
  }
  else
  {
    pMixerFunction->Data.bProfSpdif = IsProfessionalSpdif();
  }
  ....
}

該IsProfessionalSpdif函數返回布爾類型值。在這樣做時,函數的結果與條件中的數字0x12進行比較。

結論

去年秋天我們錯過了第一個Haiku測試版的發佈,因爲我們正在忙着發佈PVS-Studio for Java。編程錯誤的本質仍然是如果你不搜索它們並且不注意代碼質量它們就不會消失。項目開發人員使用Coverity Scan,但最後一次運行是在兩年前。這必須讓Haiku用戶感到不安。儘管2014年使用Coverity對配置進行了分析,但它並沒有阻止我們在2015年撰寫兩篇關於錯誤審覈的長篇文章(第1 部分,第2部分)

對於那些直到最後閱讀這篇文章的人來說,Haiku的另一個錯誤評論很快就會出現。在發佈此錯誤評論之前,將向開發人員發送完整的分析器報告,因此在您閱讀此錯誤時可能會修復一些錯誤。爲了在文章之間傳遞時間,我建議爲您的項目下載並嘗試PVS-Studio。

作者:Svyatoslav Razmyslov
原文鏈接:https://www.viva64.com/en/b/0644/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章