509 lines
14 KiB
C++
509 lines
14 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")))
|
|
#define ZX_HANDLE_ACQUIRE_UNOWNED __attribute__((acquire_handle("FuchsiaUnowned")))
|
|
#else
|
|
#define ZX_HANDLE_ACQUIRE
|
|
#define ZX_HANDLE_RELEASE
|
|
#define ZX_HANDLE_USE
|
|
#define ZX_HANDLE_ACQUIRE_UNOWNED
|
|
#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_UNOWNED
|
|
zx_handle_t zx_process_self();
|
|
|
|
void zx_process_self_param(zx_handle_t *out ZX_HANDLE_ACQUIRE_UNOWNED);
|
|
|
|
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 checkUnownedHandle01() {
|
|
zx_handle_t h0;
|
|
h0 = zx_process_self(); // expected-note {{Function 'zx_process_self' returns an unowned handle}}
|
|
zx_handle_close(h0); // expected-warning {{Releasing an unowned handle}}
|
|
// expected-note@-1 {{Releasing an unowned handle}}
|
|
}
|
|
|
|
void checkUnownedHandle02() {
|
|
zx_handle_t h0;
|
|
zx_process_self_param(&h0); // expected-note {{Unowned handle allocated through 1st parameter}}
|
|
zx_handle_close(h0); // expected-warning {{Releasing an unowned handle}}
|
|
// expected-note@-1 {{Releasing an unowned handle}}
|
|
}
|
|
|
|
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?
|