llvm-project/clang/test/Analysis/fuchsia_handle.cpp
Yu Shan 3ce78f54ed [analyzer] Ignore annotations if func is inlined.
When we annotating a function header so that it could be used by other
TU, we also need to make sure the function is parsed correctly within
the same TU. So if we can find the function's implementation,
ignore the annotations, otherwise, false positive would occur.
Move the escape by value case to post call and do not escape the handle
if the function is inlined and we have analyzed the handle.

Differential Revision: https://reviews.llvm.org/D91902
2020-12-07 11:28:11 -08:00

488 lines
13 KiB
C++

// RUN: %clang_analyze_cc1 -analyzer-checker=core,fuchsia.HandleChecker -analyzer-output=text \
// RUN: -verify %s
typedef __typeof__(sizeof(int)) size_t;
typedef int zx_status_t;
typedef __typeof__(sizeof(int)) zx_handle_t;
typedef unsigned int uint32_t;
#define NULL ((void *)0)
#define ZX_HANDLE_INVALID 0
#if defined(__clang__)
#define ZX_HANDLE_ACQUIRE __attribute__((acquire_handle("Fuchsia")))
#define ZX_HANDLE_RELEASE __attribute__((release_handle("Fuchsia")))
#define ZX_HANDLE_USE __attribute__((use_handle("Fuchsia")))
#else
#define ZX_HANDLE_ACQUIRE
#define ZX_HANDLE_RELEASE
#define ZX_HANDLE_USE
#endif
zx_status_t zx_channel_create(
uint32_t options,
zx_handle_t *out0 ZX_HANDLE_ACQUIRE,
zx_handle_t *out1 ZX_HANDLE_ACQUIRE);
zx_status_t zx_handle_close(
zx_handle_t handle ZX_HANDLE_RELEASE);
ZX_HANDLE_ACQUIRE
zx_handle_t return_handle();
void escape1(zx_handle_t *in);
void escape2(zx_handle_t in);
void (*escape3)(zx_handle_t) = escape2;
void use1(const zx_handle_t *in ZX_HANDLE_USE);
void use2(zx_handle_t in ZX_HANDLE_USE);
void moreArgs(zx_handle_t, int, ...);
void lessArgs(zx_handle_t, int a = 5);
// To test if argument indexes are OK for operator calls.
struct MyType {
ZX_HANDLE_ACQUIRE
zx_handle_t operator+(zx_handle_t ZX_HANDLE_RELEASE replace);
};
void checkInvalidHandle01() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
if (sa == ZX_HANDLE_INVALID)
;
// Will we ever see a warning like below?
// We eagerly replace the symbol with a constant and lose info...
use2(sa); // TODOexpected-warning {{Use of an invalid handle}}
zx_handle_close(sb);
zx_handle_close(sa);
}
void checkInvalidHandle2() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
if (sb != ZX_HANDLE_INVALID)
zx_handle_close(sb);
if (sa != ZX_HANDLE_INVALID)
zx_handle_close(sa);
}
void handleDieBeforeErrorSymbol01() {
zx_handle_t sa, sb;
zx_status_t status = zx_channel_create(0, &sa, &sb);
if (status < 0)
return;
__builtin_trap();
}
void handleDieBeforeErrorSymbol02() {
zx_handle_t sa, sb;
zx_status_t status = zx_channel_create(0, &sa, &sb);
// FIXME: There appears to be non-determinism in choosing
// which handle to report.
// expected-note-re@-3 {{Handle allocated through {{(2nd|3rd)}} parameter}}
if (status == 0) { // expected-note {{Assuming 'status' is equal to 0}}
// expected-note@-1 {{Taking true branch}}
return; // expected-warning {{Potential leak of handle}}
// expected-note@-1 {{Potential leak of handle}}
}
__builtin_trap();
}
void checkNoCrash01() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
moreArgs(sa, 1, 2, 3, 4, 5);
lessArgs(sa);
zx_handle_close(sa);
zx_handle_close(sb);
}
void checkNoLeak01() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
zx_handle_close(sa);
zx_handle_close(sb);
}
void checkNoLeak02() {
zx_handle_t ay[2];
zx_channel_create(0, &ay[0], &ay[1]);
zx_handle_close(ay[0]);
zx_handle_close(ay[1]);
}
void checkNoLeak03() {
zx_handle_t ay[2];
zx_channel_create(0, &ay[0], &ay[1]);
for (int i = 0; i < 2; i++)
zx_handle_close(ay[i]);
}
zx_handle_t checkNoLeak04() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
zx_handle_close(sa);
return sb; // no warning
}
zx_handle_t checkNoLeak05(zx_handle_t *out1) {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
*out1 = sa;
return sb; // no warning
}
void checkNoLeak06() {
zx_handle_t sa, sb;
if (zx_channel_create(0, &sa, &sb))
return;
zx_handle_close(sa);
zx_handle_close(sb);
}
void checkLeak01(int tag) {
zx_handle_t sa, sb;
if (zx_channel_create(0, &sa, &sb)) // expected-note {{Handle allocated through 2nd parameter}}
return; // expected-note@-1 {{Assuming the condition is false}}
// expected-note@-2 {{Taking false branch}}
use1(&sa);
if (tag) // expected-note {{Assuming 'tag' is 0}}
zx_handle_close(sa);
// expected-note@-2 {{Taking false branch}}
use2(sb); // expected-warning {{Potential leak of handle}}
// expected-note@-1 {{Potential leak of handle}}
zx_handle_close(sb);
}
void checkLeakFromReturn01(int tag) {
zx_handle_t sa = return_handle(); // expected-note {{Function 'return_handle' returns an open handle}}
(void)sa;
} // expected-note {{Potential leak of handle}}
// expected-warning@-1 {{Potential leak of handle}}
void checkReportLeakOnOnePath(int tag) {
zx_handle_t sa, sb;
if (zx_channel_create(0, &sa, &sb)) // expected-note {{Handle allocated through 2nd parameter}}
return; // expected-note@-1 {{Assuming the condition is false}}
// expected-note@-2 {{Taking false branch}}
zx_handle_close(sb);
switch (tag) { // expected-note {{Control jumps to the 'default' case at line}}
case 0:
use2(sa);
return;
case 1:
use2(sa);
return;
case 2:
use2(sa);
return;
case 3:
use2(sa);
return;
case 4:
use2(sa);
return;
default:
use2(sa);
return; // expected-warning {{Potential leak of handle}}
// expected-note@-1 {{Potential leak of handle}}
}
}
void checkDoubleRelease01(int tag) {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
// expected-note@-1 {{Handle allocated through 2nd parameter}}
if (tag) // expected-note {{Assuming 'tag' is not equal to 0}}
zx_handle_close(sa); // expected-note {{Handle released through 1st parameter}}
// expected-note@-2 {{Taking true branch}}
zx_handle_close(sa); // expected-warning {{Releasing a previously released handle}}
// expected-note@-1 {{Releasing a previously released handle}}
zx_handle_close(sb);
}
void checkUseAfterFree01(int tag) {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
// expected-note@-1 {{Handle allocated through 2nd parameter}}
// expected-note@-2 {{Handle allocated through 3rd parameter}}
// expected-note@+2 {{Taking true branch}}
// expected-note@+1 {{Taking false branch}}
if (tag) {
// expected-note@-1 {{Assuming 'tag' is not equal to 0}}
zx_handle_close(sa); // expected-note {{Handle released through 1st parameter}}
use1(&sa); // expected-warning {{Using a previously released handle}}
// expected-note@-1 {{Using a previously released handle}}
}
// expected-note@-6 {{Assuming 'tag' is 0}}
zx_handle_close(sb); // expected-note {{Handle released through 1st parameter}}
use2(sb); // expected-warning {{Using a previously released handle}}
// expected-note@-1 {{Using a previously released handle}}
}
void checkMemberOperatorIndices() {
zx_handle_t sa, sb, sc;
zx_channel_create(0, &sa, &sb);
zx_handle_close(sb);
MyType t;
sc = t + sa;
zx_handle_close(sc);
}
struct HandleStruct {
zx_handle_t h;
};
void close_handle_struct(HandleStruct hs ZX_HANDLE_RELEASE);
void use_handle_struct(HandleStruct hs ZX_HANDLE_USE);
void checkHandleInStructureUseAfterFree() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb); // expected-note {{Handle allocated through 3rd parameter}}
HandleStruct hs;
hs.h = sb;
use_handle_struct(hs);
close_handle_struct(hs); // expected-note {{Handle released through 1st parameter}}
zx_handle_close(sa);
use2(sb); // expected-warning {{Using a previously released handle}}
// expected-note@-1 {{Using a previously released handle}}
}
void checkHandleInStructureUseAfterFree2() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb); // expected-note {{Handle allocated through 3rd parameter}}
HandleStruct hs;
hs.h = sb;
use_handle_struct(hs);
zx_handle_close(sb); // expected-note {{Handle released through 1st parameter}}
zx_handle_close(sa);
use_handle_struct(hs); // expected-warning {{Using a previously released handle}}
// expected-note@-1 {{Using a previously released handle}}
}
void checkHandleInStructureLeak() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb); // expected-note {{Handle allocated through 3rd parameter}}
HandleStruct hs;
hs.h = sb;
zx_handle_close(sa); // expected-warning {{Potential leak of handle}}
// expected-note@-1 {{Potential leak of handle}}
}
struct HandlePtrStruct {
zx_handle_t *h;
};
void close_handle_struct(HandlePtrStruct hs ZX_HANDLE_RELEASE);
void use_handle_struct(HandlePtrStruct hs ZX_HANDLE_USE);
void checkHandlePtrInStructureUseAfterFree() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
HandlePtrStruct hs;
hs.h = &sb;
use_handle_struct(hs);
close_handle_struct(hs); // expected-note {{Handle released through 1st parameter}}
zx_handle_close(sa);
use2(sb); // expected-warning {{Using a previously released handle}}
// expected-note@-1 {{Using a previously released handle}}
}
void checkHandlePtrInStructureUseAfterFree2() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb);
HandlePtrStruct hs;
hs.h = &sb;
use_handle_struct(hs);
zx_handle_close(sb); // expected-note {{Handle released through 1st parameter}}
zx_handle_close(sa);
use_handle_struct(hs); // expected-warning {{Using a previously released handle}}
// expected-note@-1 {{Using a previously released handle}}
}
void checkHandlePtrInStructureLeak() {
zx_handle_t sa, sb;
zx_channel_create(0, &sa, &sb); // expected-note {{Handle allocated through 3rd parameter}}
HandlePtrStruct hs;
hs.h = &sb;
zx_handle_close(sa); // expected-warning {{Potential leak of handle}}
// expected-note@-1 {{Potential leak of handle}}
}
// Assume this function's declaration that has the release annotation is in one
// header file while its implementation is in another file. We have to annotate
// the declaration because it might be used outside the TU.
// We also want to make sure it is okay to call the function within the same TU.
zx_status_t test_release_handle(zx_handle_t handle ZX_HANDLE_RELEASE) {
return zx_handle_close(handle);
}
void checkReleaseImplementedFunc() {
zx_handle_t a, b;
zx_channel_create(0, &a, &b);
zx_handle_close(a);
test_release_handle(b);
}
void use_handle(zx_handle_t handle) {
// Do nothing.
}
void test_call_by_value() {
zx_handle_t a, b;
zx_channel_create(0, &a, &b);
zx_handle_close(a);
use_handle(b);
zx_handle_close(b);
}
void test_call_by_value_leak() {
zx_handle_t a, b;
zx_channel_create(0, &a, &b); // expected-note {{Handle allocated through 3rd parameter}}
zx_handle_close(a);
// Here we are passing handle b as integer value to a function that could be
// analyzed by the analyzer, thus the handle should not be considered escaped.
// After the function 'use_handle', handle b is still tracked and should be
// reported leaked.
use_handle(b);
} // expected-warning {{Potential leak of handle}}
// expected-note@-1 {{Potential leak of handle}}
// RAII
template <typename T>
struct HandleWrapper {
~HandleWrapper() { close(); }
void close() {
if (handle != ZX_HANDLE_INVALID)
zx_handle_close(handle);
}
T *get_handle_address() { return &handle; }
private:
T handle;
};
void doNotWarnOnRAII() {
HandleWrapper<zx_handle_t> w1;
zx_handle_t sb;
if (zx_channel_create(0, w1.get_handle_address(), &sb))
return;
zx_handle_close(sb);
}
template <typename T>
struct HandleWrapperUnkonwDtor {
~HandleWrapperUnkonwDtor();
void close() {
if (handle != ZX_HANDLE_INVALID)
zx_handle_close(handle);
}
T *get_handle_address() { return &handle; }
private:
T handle;
};
void doNotWarnOnUnknownDtor() {
HandleWrapperUnkonwDtor<zx_handle_t> w1;
zx_handle_t sb;
if (zx_channel_create(0, w1.get_handle_address(), &sb))
return;
zx_handle_close(sb);
}
// Various escaping scenarios
zx_handle_t *get_handle_address();
void escape_store_to_escaped_region01() {
zx_handle_t sb;
if (zx_channel_create(0, get_handle_address(), &sb))
return;
zx_handle_close(sb);
}
struct object {
zx_handle_t *get_handle_address();
};
void escape_store_to_escaped_region02(object &o) {
zx_handle_t sb;
// Same as above.
if (zx_channel_create(0, o.get_handle_address(), &sb))
return;
zx_handle_close(sb);
}
void escape_store_to_escaped_region03(object o) {
zx_handle_t sb;
// Should we consider the pointee of get_handle_address escaped?
// Maybe we only should it consider escaped if o escapes?
if (zx_channel_create(0, o.get_handle_address(), &sb))
return;
zx_handle_close(sb);
}
void escape_through_call(int tag) {
zx_handle_t sa, sb;
if (zx_channel_create(0, &sa, &sb))
return;
escape1(&sa);
if (tag)
escape2(sb);
else
escape3(sb);
}
struct have_handle {
zx_handle_t h;
zx_handle_t *hp;
};
void escape_through_store01(have_handle *handle) {
zx_handle_t sa;
if (zx_channel_create(0, &sa, handle->hp))
return;
handle->h = sa;
}
have_handle global;
void escape_through_store02() {
zx_handle_t sa;
if (zx_channel_create(0, &sa, global.hp))
return;
global.h = sa;
}
have_handle escape_through_store03() {
zx_handle_t sa, sb;
if (zx_channel_create(0, &sa, &sb))
return {0, nullptr};
zx_handle_close(sb);
return {sa, nullptr};
}
void escape_structs(have_handle *);
void escape_transitively01() {
zx_handle_t sa, sb;
if (zx_channel_create(0, &sa, &sb))
return;
have_handle hs[2];
hs[1] = {sa, &sb};
escape_structs(hs);
}
void escape_top_level_pointees(zx_handle_t *h) {
zx_handle_t h2;
if (zx_channel_create(0, h, &h2))
return;
zx_handle_close(h2);
} // *h should be escaped here. Right?