【本文正在参加「盲盒」+码有奖征文活动】https://ost.51cto.com/posts/19288

Clang Static Analyzer (5) Clangsa

接着本系列之前的文章,补充下CodeChecker的部分用法。使用命令CodeChecker analyzers可以查看当前支持的静态分析检查器analyzers。如果安装了Cppcheck就会展示出来,clangsa和Clang-tidy是LLVM Clang编译工具链提供的静态分析检查器。

zhushangyuan@DESKTOP-RPE9R4O:~/CSA$ CodeChecker analyzers
 cppcheck
zhushangyuan@DESKTOP-RPE9R4O:~/CSA$ export PATH=~/openharmony/prebuilts/clang/ohos/linux-x86_64/llvm/bin:$PATH
zhushangyuan@DESKTOP-RPE9R4O:~/CSA$ CodeChecker analyzers
 clangsa
 cppcheck
 clang-tidy

在使用CodeChecker静态分析时,可以使用选项[--analyzers ANALYZER [ANALYZER ...]指定只使能部分分析器,这些ANALYZER可以是clangsacppcheckclang-tidy。如下:

CodeChecker analyze --analyzers clangsa compile_commands.json -o ./reports

系列文章中的前几篇中,已经到了Cppcheck和Clang-tidy这2个静态检查分析器,本文快速介绍下clangsa静态检查分析器。

1、clangsa介绍

前面系列文章介绍了Clang Static Analyzer,知道Clang 静态分析器CSA是一个源代码分析工具,可查找 C、C++ 和 Objective-C 程序的bugs。Clang Static Analyzer可以理解提供了两部分内容,一部分是CSA工具,比如scan-build、CodeChecker,还有一部分是clangsa,虽然是Clang Static Analyzer的缩写,但是指的是Clang Static Analyzer的静态检查分析器,定义了一些的代码缺陷检查规则。详细可以参考链接https://clang.llvm.org/docs/ClangStaticAnalyzer.html,除了支持的检查器,还有用户文档和开发文档,用于支持定制自己的检查器。

本文主要学习下clangsa支持的检查器Available Checkers

clangsacppcheckclang-tidy属于分析器analyzer,每个分析器定义了自己的一套检查器“checkers”。检查器还会通过分组进行管理。clangsa分析器的检查器分为三组,分别为:

  • Default Checkers 默认检查器,可以发现安全和API使用缺陷,死代码和其他逻辑错误。。请参阅下面的默认检查器列表。
  • Experimental Checkers 实验检查器也称为alpha checkers,处于开发过程中,默认关闭。可能会崩溃,也可能产生较多的误报。
  • Debug Checkers 调测检查器被分析器开发者用作调测目的

2、Default Checkers 默认检查器

默认检查器分为如下几组。只关注C、C++相关的检查器,osx、Fuchsia、nullability、WebKit可以自行按需了解。

  • core 核心语言特性,通用的检查器
  • cplusplus C++语言检查器
  • deadcode 死代码检查器
  • nullability Objective-C空指针传递和解引用检查器
  • optin 可移植、性能或编程风格相关的检查器
  • security 安全相关检查器
  • unix POSIX/Unix检查器
  • osx macOS检查器
  • Fuchsia Fuchsia操作系统相关的检查器。Fuchsia是Google开发的一款开源操作系统。
  • WebKit WebKit相关检查器。WebKit是开源Web浏览器引擎。

2.1 core检查器

core检查器关注语言的核心特性,包含通用目的的检查器,例如除零错误,空指针解引用,使用未初始化的值等。这些检查器需要一直开启,其他检查器依赖这些核心检查器。

  • core.CallAndMessage (C, C++, ObjC) 检查函数调用的逻辑错误,Objective-C消息表达错误,例如,未初始化参数,空函数指针等。适用于C, C++, ObjC等编程语言。
//C
void test() {
   void (*foo)(void);
   foo = 0;
   foo(); // warn: function pointer is null 函数指针为空
 }
 // C++
 class C {
 public:
   void f();
 };
 void test() {
   C *pc;
   pc->f(); // warn: object pointer is uninitialized 对象指针未初始化
 }
 // C++
 class C {
 public:
   void f();
 };
 void test() {
   C *pc = 0;
   pc->f(); // warn: object pointer is null 对象指针为空
 }
 // Objective-C
......
  • core.DivideZero (C, C++, ObjC) 检查是否存在除零错误。

    void test(int z) {
    if (z == 0)
      int x = 1 / z; // warn 除零错误
    }
    void test() {
    int x = 1;
    int y = x % 0; // warn 除零错误
    }
    
  • core.NullDereference (C, C++, ObjC) 检查空指针的解引用错误。SuppressAddressSpaces选项会屏蔽带地址空间的空指针解引用告警。可以使用选项-analyzer-config core.NullDereference:SuppressAddressSpaces=false来关注该SuppressAddressSpaces。默认是开启的。

  // C
  void test(int *p) {
  if (p)
    return;

  int x = p[0]; // warn
  } 

// C
void test(int *p) {
  if (!p)
    *p = 0; // warn
}

// C++
class C {
public:
  int x;
};

void test() {
  C *pc = 0;
  int k = pc->x; // warn
}

// Objective-C
......
  • core.StackAddressEscape (C) 检查堆栈越界,在一个函数内部声明的局部变量存储在栈空间上,不能把该变量的地址传递到函数外部。
char const *p;

void test() {
  char const str[] = "string";
  p = str; // warn StackAddressEscape告警
}

void* test() {
   return __builtin_alloca(12); // warn StackAddressEscape告警
}

void test() {
  static int *x;
  int y;
  x = &y; // warn StackAddressEscape告警
}
  • core.UndefinedBinaryOperatorResult (C) 检查二元表达式中的未定义的值。
void test() {
int x;
int y = x + 1; // warn: left operand is garbage
}
  • core.VLASize (C) 检查变长数组(Variable Length Arrays,VLA)的长度未定义或者零值。
void test() {
  int x;
  int vla1[x]; // warn: garbage as size
}

void test() {
  int x = 0;
  int vla2[x]; // warn: zero size
}
  • core.uninitialized.ArraySubscript (C) 检查未初始化的值用作数组下标。
void test() {
  int i, a[10];
  int x = a[i]; // warn: array subscript is undefined
}
  • core.uninitialized.Assign (C) 检查使用未初始化的值进行赋值。
void test() {
  int x;
  x |= 1; // warn: left expression is uninitialized 左表达式未初始化
}
  • core.uninitialized.Branch (C)

检查未初始化的值用于条件分支。

void test() {
  int x;
  if (x) // warn
    return;
}
  • core.uninitialized.CapturedBlockVariable (C) 检查代码块捕获未初始化的值。
void test() {
  int x;
  ^{ int y = x; }(); // warn
}
  • core.uninitialized.UndefReturn (C) 检查未初始化的值传递到外层函数。
int test() {
  int x;
  return x; // warn
}

2.2 cplusplus检查器

C++语言相关的检查器。

  • cplusplus.InnerPointer (C++)

    检查在re/deallocation之后使用的C++容器的内部指针。C++标准库中的许多容器方法会让指向容器元素的引用无效,包含实际引用,迭代器,原生指针(raw pointers)。使用无效的引用会引起未定义行为,这通常是C++中内存错误的源头,也是该检查致力于检查出来的代码缺陷。

    该检查器只是适合std::string对象,不能识别出复杂的用法,比如传递std::string_view这样的unowned指针。

void deref_after_assignment() {
  std::string s = "llvm";
  const char *c = s.data(); // note: pointer to inner buffer of 'std::string' obtained here
  s = "clang"; // note: inner buffer of 'std::string' reallocated by call to 'operator='
  consume(c); // warn: inner pointer of container used after re/deallocation
}

const char *return_temp(int x) {
  return std::to_string(x).c_str(); // warn: inner pointer of container used after re/deallocation
  // note: pointer to inner buffer of 'std::string' obtained here
  // note: inner buffer of 'std::string' deallocated by call to destructor
}
  • cplusplus.NewDelete (C++)

    检查重复释放double-free和释放后使用UAF等内存问题。追踪被new/delete管理的内存。

void f(int *p);
void testUseMiddleArgAfterDelete(int *p) {
 delete p;
 f(p); // warn: use after free 释放后使用
}

class SomeClass {
public:
 void f();
};

void test() {
 SomeClass *c = new SomeClass;
 delete c;
 c->f(); // warn: use after free 释放后使用
}

void test() {
 int *p = (int *)__builtin_alloca(sizeof(int));
 delete p; // warn: deleting memory allocated by alloca
           // 释放使用alloca申请的内存
}

void test() {
 int *p = new int;
 delete p;
 delete p; // warn: attempt to free released 重复释放内存
}

void test() {
 int i;
 delete &i; // warn: delete address of local 释放局部变量的栈内存
}

void test() {
 int *p = new int[1];
 delete[] (++p);
   // warn: argument to 'delete[]' is offset by 4 bytes
   // from the start of memory allocated by 'new[]'
   // 传递给'delete[]'的参数相对'new[]'申请的内存开始地址进行了4字节偏移
   // 释放的不是申请的内存。
}
  • cplusplus.NewDeleteLeaks (C++) 检查内存泄露memory leaks. 追踪new/delete管理的内存。
void test() {
  int *p = new int;
} // warn 申请未释放
  • cplusplus.PlacementNewChecker (C++) Check if default placement new is provided with pointers to sufficient storage capacity. 检查缺省的定位new(placement new)指针操作,有足够的存储能力。
#include <new> 

void f() {
  short s;
  long *lp = ::new (&s) long; // warn
}
  • cplusplus.SelfAssignment (C++) 检查C++的自赋值的copy 和 move赋值操作。

  • cplusplus.StringChecker (C++) 检查 std::string 操作。检查从std::string对象构造的cstring是否为空NULL。如果检查器不能推断出该指针为空,会假设其非空,用于满足构造。

该检查器可以检查SEI CERT C++编程规则STR51-CPP。不要从空指针创建std::string对象。

#include <string>

void f(const char *p) {
  if (!p) {
    std::string msg(p); // warn: The parameter must not be null
  }
}

2.3 deadcode检查器

  • deadcode.DeadStores (C) 检查存储在变量里的值后续未被使用。

    void test() {
    int x;
    x = 1; // warn
    }
    

    WarnForDeadNestedAssignments选项会使能检查器来检测嵌套的无用的赋值代码,可以使用选项-analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false来关闭该选项。WarnForDeadNestedAssignments`选项默认开启。

会对这样的代码进行告警,例如: if ((y = make_int())) { }

2.4 security检查器

  • security.FloatLoopCounter (C) 浮点数作为循环变量 (CERT: FLP30-C, FLP30-CPP).
void test() {
 for (float x = 0.1f; x <= 1.0f; x += 0.1f) {} // warn
}
  • security.insecureAPI.UncheckedReturn (C) Warn on uses of functions whose return values must be always checked.
void test() {
  setuid(1); // warn
}
  • security.insecureAPI.bcmp (C) Warn on uses of the ‘bcmp’ function.

    void test() {
    bcmp(ptr0, ptr1, n); // warn
    }
    
  • security.insecureAPI.bcopy (C) Warn on uses of the ‘bcopy’ function.

    void test() {
    bcopy(src, dst, n); // warn
    }
    
  • security.insecureAPI.bzero ( C ) Warn on uses of the ‘bzero’ function.

    void test() {
    bzero(ptr, n); // warn
    }
    
  • security.insecureAPI.getpw (C) Warn on uses of the ‘getpw’ function.

    void test() {
    char buff[1024];
    getpw(2, buff); // warn
    }
    
  • security.insecureAPI.gets (C) Warn on uses of the ‘gets’ function.

    void test() {
    char buff[1024];
    gets(buff); // warn
    }
    
  • security.insecureAPI.mkstemp (C) Warn when ‘mkstemp’ is passed fewer than 6 X’s in the format string.

    void test() {
    mkstemp("XX"); // warn
    }
    
  • security.insecureAPI.mktemp (C) Warn on uses of the mktemp function.

    void test() {
    char *x = mktemp("/tmp/zxcv"); // warn: insecure, use mkstemp
    }
    
  • security.insecureAPI.rand (C) Warn on uses of inferior random number generating functions (only if arc4random function is available): drand48, erand48, jrand48, lcong48, lrand48, mrand48, nrand48, random, rand_r.

    void test() {
    random(); // warn
    }
    
  • security.insecureAPI.strcpy (C) Warn on uses of the strcpy and strcat functions.

    void test() {
    char x[4];
    char *y = "abcd";
    
    strcpy(x, y); // warn
    }
    
  • security.insecureAPI.vfork (C) Warn on uses of the ‘vfork’ function.

    void test() {
    vfork(); // warn
    }
    
  • security.insecureAPI.DeprecatedOrUnsafeBufferHandling (C) Warn on occurrences of unsafe or deprecated buffer handling functions, which now have a secure variant: sprintf, vsprintf, scanf, wscanf, fscanf, fwscanf, vscanf, vwscanf, vfscanf, vfwscanf, sscanf, swscanf, vsscanf, vswscanf, swprintf, snprintf, vswprintf, vsnprintf, memcpy, memmove, strncpy, strncat, memset

    void test() {
    char buf [5];
    strncpy(buf, "a", 1); // warn
    }
    

2.5 unix检查器

  • unix.API (C) 检查UNIX/POSIX函数调用:open, pthread_once, calloc, malloc, realloc, alloca。
// Currently the check is performed for apple targets only.
void test(const char *path) {
  int fd = open(path, O_CREAT);
    // warn: call to 'open' requires a third argument when the
    // 'O_CREAT' flag is set
}

void f();

void test() {
  pthread_once_t pred = {0x30B1BCBA, {0}};
  pthread_once(&pred, f);
    // warn: call to 'pthread_once' uses the local variable
}

void test() {
  void *p = malloc(0); // warn: allocation size of 0 bytes
}

void test() {
  void *p = calloc(0, 42); // warn: allocation size of 0 bytes
}

void test() {
  void *p = malloc(1);
  p = realloc(p, 0); // warn: allocation size of 0 bytes
}

void test() {
  void *p = alloca(0); // warn: allocation size of 0 bytes
}

void test() {
  void *p = valloc(0); // warn: allocation size of 0 bytes
}
  • unix.Malloc (C) 检查内存泄露,重复释放、释放后使用等内存代码缺陷。追踪被malloc()/free()管理的内存。
void test() {
  int *p = malloc(1);
  free(p);
  free(p); // warn: attempt to free released memory
}

void test() {
  int *p = malloc(sizeof(int));
  free(p);
  *p = 1; // warn: use after free
}

void test() {
  int *p = malloc(1);
  if (p)
    return; // warn: memory is never released
}

void test() {
  int a[] = { 1 };
  free(a); // warn: argument is not allocated by malloc
}

void test() {
  int *p = malloc(sizeof(char));
  p = p - 1;
  free(p); // warn: argument to free() is offset by -4 bytes
}

- unix.MallocSizeof (C)
Check for dubious malloc arguments involving sizeof.

void test() {
  long *p = malloc(sizeof(short));
    // warn: result is converted to 'long *', which is
    // incompatible with operand type 'short'
  free(p);
}
- unix.MismatchedDeallocator (C, C++)
Check for mismatched deallocators.

// C, C++
void test() {
  int *p = (int *)malloc(sizeof(int));
  delete p; // warn
}

// C, C++
void __attribute((ownership_returns(malloc))) *user_malloc(size_t);

void test() {
  int *p = (int *)user_malloc(sizeof(int));
  delete p; // warn
}

// C, C++
void test() {
  int *p = new int;
  free(p); // warn
}

// C, C++
void test() {
  int *p = new int[1];
  realloc(p, sizeof(long)); // warn
}

// C, C++
template <typename T>
struct SimpleSmartPointer {
  T *ptr;

  explicit SimpleSmartPointer(T *p = 0) : ptr(p) {}
  ~SimpleSmartPointer() {
    delete ptr; // warn
  }
};

void test() {
  SimpleSmartPointer<int> a((int *)malloc(4));
}

// C++
void test() {
  int *p = (int *)operator new(0);
  delete[] p; // warn
}

// Objective-C, C++
void test(NSUInteger dataLength) {
  int *p = new int;
  NSData *d = [NSData dataWithBytesNoCopy:p
               length:sizeof(int) freeWhenDone:1];
    // warn +dataWithBytesNoCopy:length:freeWhenDone: cannot take
    // ownership of memory allocated by 'new'
}
  • unix.Vfork (C) 检查vfork是否合适使用。

    int test(int x) {
    pid_t pid = vfork(); // warn
    if (pid != 0)
      return 0;
    
    switch (x) {
    case 0:
      pid = 1;
      execl("", "", 0);
      _exit(1);
      break;
    case 1:
      x = 0; // warn: this assignment is prohibited
      break;
    case 2:
      foo(); // warn: this function call is prohibited
      break;
    default:
      return 0; // warn: return is prohibited
    }
    
    while(1);
    }
    
  • unix.cstring.BadSizeArg (C) 检查传递给C字符串函数的size参数的错误模式。使用-Wno-strncat-size编译器选项屏蔽其他strncat-相关的编译告警。

    void test() {
    char dest[3];
    strncat(dest, """""""""""""""""""""""""*", sizeof(dest));
      // warn: potential buffer overflow
    }
    
  • unix.cstring.NullArg (C) 检查空指针作为参数传递给C字符串函数:strlen, strnlen, strcpy, strncpy, strcat, strncat, strcmp, strncmp, strcasecmp, strncasecmp, wcslen, wcsnlen。

    int test() {
    return strlen(0); // warn
    }
    

3、Experimental Checkers 实验检查器

3.1 alpha.clone检查器

  • alpha.clone.CloneChecker (C, C++, ObjC) 检查类似的重复代码块。
void log();

int max(int a, int b) { // warn
  log();
  if (a > b)
    return a;
  return b;
}

int maxClone(int x, int y) { // similar code here
  log();
  if (x > y)
    return x;
  return y;
}

3.2 alpha.core检查器

简单看一个,其他更多检查器,可以自行访问官网文档https://clang.llvm.org/docs/analyzer/checkers.html#alpha-core。

  • alpha.core.C11Lock 类似alpha.unix.PthreadLock,检查互斥锁的mtx_t mutexeslocking/unlocking的上锁和解锁。
mtx_t mtx1;

void bad1(void)
{
  mtx_lock(&mtx1);
  mtx_lock(&mtx1); // warn: This lock has already been acquired
}

4、参考站点

本文作者:zhushangyuan_

想了解更多关于开源的内容,请访问:​

​51CTO 开源基础软件社区​

​https://ost.51cto.com/#bkwz​