溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

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

發(fā)布時間:2020-07-03 20:12:53 來源:網(wǎng)絡(luò) 閱讀:483 作者:wx5d2844b385cdf 欄目:編程語言

關(guān)于PVS-Studio靜態(tài)分析器和Haiku OS代碼如何相遇的故事可以追溯到2015年。對于這兩個項目的團隊來說,這是一個令人興奮的實驗和有用的經(jīng)驗。為什么要做實驗?那一刻,我們沒有Linux的分析儀,我們不會再用一年半了。無論如何,我們團隊的愛好者的努力得到了回報:我們結(jié)識了Haiku開發(fā)人員并提高了代碼質(zhì)量,擴大了我們的錯誤基礎(chǔ),開發(fā)人員制造了罕見的錯誤并改進了分析器?,F(xiàn)在,您可以輕松快速地檢查Haiku代碼中的錯誤。

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

介紹

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

現(xiàn)在執(zhí)行分析要簡單得多。以下是用于構(gòu)建和分析項目的所有命令的列表:

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容器中實現(xiàn)的。最近我們準備了關(guān)于這個主題的新文檔:在Docker中運行PVS-Studio。這可以使一些公司很容易為他們的項目應(yīng)用靜態(tài)分析技術(shù)。

未初始化的變量

使用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變量尚未初始化的聲明,所以它與空

值將導(dǎo)致不確定的結(jié)果比較。如果情況失敗,則rval變量的不確定值 可以成為auto_fetch函數(shù)的返回值。

使用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是在此處發(fā)生的未初始化指針。

V506指向局部變量“normalized”的指針存儲在此變量的范圍之外。這樣的指針將變?yōu)闊o效。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);
}

程序員可能需要使用中間變量來規(guī)范化對象。但現(xiàn)在字體指針包含指向規(guī)范化對象的指針,該指針將在退出創(chuàng)建臨時對象的作用域后刪除。

V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this->

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

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

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

  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 37
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 49
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 58
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 67
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 77
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 89
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 103
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 115
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 126
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 142
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 152
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 163
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 186
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 196
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 206
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 214
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'this-> BUnicodeChar ::BUnicodeChar(....)'。UnicodeChar.cpp 222
  • V603對象已創(chuàng)建但未使用。如果你想調(diào)用構(gòu)造函數(shù),應(yīng)該使用'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 '語句相當(dāng)于'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);
}

在查看預(yù)處理器結(jié)果之前,此代碼段看起來并不可疑:

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函數(shù)在一個文件中聲明幾乎彼此相鄰,但是在這里某處發(fā)生了替換。

搜索文件僅導(dǎo)致mutex.h具有以下內(nèi)容:

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

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

自由功能的錯誤
V575空指針傳遞給'free'函數(shù)。檢查第一個參數(shù)。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函數(shù)中傳遞空指針,但這種用法肯定是可疑的。因此,分析儀找到了混合的代碼行。首先,代碼作者必須通過fVectorIcon指針釋放內(nèi)存,然后才分配NULL。

V575空指針傳遞給'free'函數(shù)。檢查第一個參數(shù)。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);           // <=
  ....
}

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

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

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

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

刪除操作符出錯
V611使用'new T []'運算符分配內(nèi)存,但使用'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指針用于為字符數(shù)組分配內(nèi)存。該刪除操作被用來釋放所述存儲器,而不是刪除[] 。

V611內(nèi)存使用'new'運算符分配,但是使用'free'函數(shù)釋放。考慮檢查'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是一個調(diào)用malloc的函數(shù)。然后placement-new在這里構(gòu)造對象。由于PageWriteWrapper類以下列方式實現(xiàn):

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函數(shù)釋放內(nèi)存,因此不會調(diào)用此類的對象析構(gòu)函數(shù)。

V611使用'new T []'運算符分配內(nèi)存,但使用'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 []是一個常見錯誤。編寫類時最容易出錯,因為析構(gòu)函數(shù)的代碼通常遠離內(nèi)存位置。這里,程序員錯誤地釋放了析構(gòu)函數(shù)中fOutBuffer指針存儲的內(nèi)存。

V772為void指針調(diào)用'delete'操作符將導(dǎo)致未定義的行為。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 *)的指針清除內(nèi)存時遇到未定義的行為。

沒有返回值的函數(shù)
V591非空函數(shù)應(yīng)返回一個值。Referenceable.h 228

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

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

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

  • V591非空函數(shù)應(yīng)返回一個值。Referenceable.h 233
  • V591非空函數(shù)應(yīng)返回一個值。Referenceable.h 239

V591非空函數(shù)應(yīng)返回一個值。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在這里沒有任何意義。您需要將函數(shù)注釋為noreturn才能正確編寫此類場景的代碼。為此,有noreturn屬性:標準和特定于編譯器。首先,編譯器會考慮這些屬性,以便使用警告正確生成代碼或通知某些類型的錯誤。各種靜態(tài)分析工具還考慮了屬性以提高分析質(zhì)量。

處理異常
V596對象已創(chuàng)建但未使用。'throw'關(guān)鍵字可能會丟失:拋出

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;
}

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

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異常。拋出指針會導(dǎo)致異常不會被捕獲。因此異常最終被引用捕獲。此外,指針的使用迫使捕獲方調(diào)用delete操作符來銷毀尚未完成的創(chuàng)建對象。

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

  • V1022指針拋出異常??紤]改為按價值拋出它。gensyscallinfos.cpp 347
  • V1022指針拋出異常??紤]改為按價值拋出它。gensyscallinfos.cpp 413

正式的安全

V597編譯器可以刪除'memset'函數(shù)調(diào)用,該調(diào)用用于刷新'f_key'對象。memset_s()函數(shù)應(yīng)該用于擦除私有數(shù)據(jù)。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);
}

分析儀檢測到可疑代碼,用于安全的私人數(shù)據(jù)清除。不幸的是,擴展到memset,自由調(diào)用和NULL賦值的SAFE_FREE宏不會使代碼更安全,因為在使用O2進行優(yōu)化時,編譯器都會刪除它。

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

以下是未實際清除緩沖區(qū)的位置列表:

  • V597編譯器可以刪除'memset'函數(shù)調(diào)用,該調(diào)用用于刷新'encoded_block'緩沖區(qū)。memset_s()函數(shù)應(yīng)該用于擦除私有數(shù)據(jù)。dst_api.c446
  • V597編譯器可以刪除'memset'函數(shù)調(diào)用,該調(diào)用用于刷新'key_st'對象。memset_s()函數(shù)應(yīng)該用于擦除私有數(shù)據(jù)。dst_api.c685
  • V597編譯器可以刪除'memset'函數(shù)調(diào)用,該調(diào)用用于刷新'in_buff'緩沖區(qū)。memset_s()函數(shù)應(yīng)該用于擦除私有數(shù)據(jù)。dst_api.c916
  • V597編譯器可以刪除'memset'函數(shù)調(diào)用,該調(diào)用用于刷新'ce'對象。memset_s()函數(shù)應(yīng)該用于擦除私有數(shù)據(jù)。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;
  ....
}

分析器找到了無符號變量與負值的明確比較。也許,應(yīng)該只將剩余變量與null 進行比較,或者實現(xiàn)溢出檢查。

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函數(shù)返回?zé)o符號值,并且它在代碼中的用法不正確。

危險的指針
V774釋放內(nèi)存后使用'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;
}

甲設(shè)備對象 由釋放刪除操作符。FreeDevice函數(shù)非常符合邏輯。但是,出于某種原因,為了釋放其他資源,已經(jīng)刪除的對象得到了解決。

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

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

    V522可能會發(fā)生空指針“數(shù)據(jù)”的解除引用??罩羔槺粋鬟f到'malo_hal_send_helper'函數(shù)中。檢查第三個參數(shù)。檢查行: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傳遞給函數(shù)的情況,并且具有這樣值的數(shù)據(jù)指針最終在memcpy函數(shù)中被解引用。

V773退出該函數(shù)時未釋放'inputFileFile'指針。內(nèi)存泄漏是可能的。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可以檢測內(nèi)存泄漏。在此示例中,如果發(fā)生錯誤,則不會釋放內(nèi)存。有人可能會認為如果出現(xiàn)錯誤,你不應(yīng)該為內(nèi)存釋放煩惱,因為程序仍然會結(jié)束。但并非總是如此。許多程序要求正確處理錯誤并繼續(xù)工作。

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診斷幾乎總是優(yōu)先于一個項目的警告數(shù)量。此代碼片段包含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;
  ....
}

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

V645'strncat '函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。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函數(shù)之間的區(qū)別對于不熟悉這些函數(shù)描述的人來說并不是很明顯。的strlcat提供函數(shù)希望整個緩沖區(qū)作為而第三個參數(shù)的大小strncat函數(shù)功能-的自由空間的在緩沖器的大小,這需要在調(diào)用之前評估所需值。但開發(fā)人員經(jīng)常忘記或不知道它。將整個緩沖區(qū)大小傳遞給strncat函數(shù)可能會導(dǎo)致緩沖區(qū)溢出,因為該函數(shù)會將此值視為要復(fù)制的可接受字符數(shù)。該strlcat提供功能沒有這樣的問題。但是你必須傳遞字符串,以terminal null結(jié)尾,以便它正常工作。

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

  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp104
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp107
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp110
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp113
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp118
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp119
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp120
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp123
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp126
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp129
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp132
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp135
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp138
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp141
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'output'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。NamespaceDump.cpp144
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'features_string'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。VirtioDevice.cpp283
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'features_string'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。VirtioDevice.cpp284
  • V645'strncat'函數(shù)調(diào)用可能導(dǎo)致'features_string'緩沖區(qū)溢出。邊界不應(yīng)包含緩沖區(qū)的大小,而應(yīng)包含它可以容納的多個字符。VirtioDevice.cpp
    285

V792'SetDecoratorSettings '函數(shù)位于運算符'|'的右側(cè)無論左操作數(shù)的值如何,都將被調(diào)用。也許,最好使用'||'。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;
}

最有可能的是,'|' 和'||' 經(jīng)營者感到困惑。此錯誤導(dǎo)致不必要的SetDecoratorSettings函數(shù)調(diào)用。

V627考慮檢查表達式。sizeof()的參數(shù)是擴展為數(shù)字的宏。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運算符看起來很可疑。也許,作者應(yīng)該評估對象的大小,例如數(shù)據(jù)。

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函數(shù)返回布爾類型值。在這樣做時,函數(shù)的結(jié)果與條件中的數(shù)字0x12進行比較。

結(jié)論

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

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

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

向AI問一下細節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI