| // 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? |