Skip to content

Commit 13b3629

Browse files
authored
[analyzer] Suppress NewDeleteLeaks FP in protobuf code (#162124)
Code automatically generated by protobuf can include a pattern where it allocates memory with `new` and then passes it to a function named `GetOwnedMessageInternal` which takes ownership of the allocated memory. This caused large amounts of false positives on a system where the protobuf header was included as a system header and therefore the analyzer assumed that `GetOwnedMessageInternal` won't escape memory. As we already individually recognize a dozen functions that can be declared in system headers but can escape memory, this commit just adds `GetOwnedMessageInternal` to that list. On a longer term perhaps we should distinguish the standard library headers (where the analyzer can assume that it recognizes all the functions that can free/escape memory) and other system headers (where the analyzer shouldn't make this assumption).
1 parent 70b7874 commit 13b3629

File tree

3 files changed

+60
-0
lines changed

3 files changed

+60
-0
lines changed

clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3812,6 +3812,15 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly(
38123812
return true;
38133813
}
38143814

3815+
// Protobuf function declared in `generated_message_util.h` that takes
3816+
// ownership of the second argument. As the first and third arguments are
3817+
// allocation arenas and won't be tracked by this checker, there is no reason
3818+
// to set `EscapingSymbol`. (Also, this is an implementation detail of
3819+
// Protobuf, so it's better to be a bit more permissive.)
3820+
if (FName == "GetOwnedMessageInternal") {
3821+
return true;
3822+
}
3823+
38153824
// Handle cases where we know a buffer's /address/ can escape.
38163825
// Note that the above checks handle some special cases where we know that
38173826
// even though the address escapes, it's still our responsibility to free the
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Like the compiler, the static analyzer treats some functions differently if
2+
// they come from a system header -- for example, it is assumed that system
3+
// functions do not arbitrarily free() their parameters, and that some bugs
4+
// found in system headers cannot be fixed by the user and should be
5+
// suppressed.
6+
#pragma clang system_header
7+
8+
class Arena;
9+
class MessageLite {
10+
int SomeArbitraryField;
11+
};
12+
13+
// Originally declared in generated_message_util.h
14+
MessageLite *GetOwnedMessageInternal(Arena *, MessageLite *, Arena *);
15+
16+
// Not a real protobuf function -- just introduced to validate that this file
17+
// is handled as a system header.
18+
void SomeOtherFunction(MessageLite *);

clang/test/Analysis/NewDeleteLeaks.cpp

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
// RUN: unix.DynamicMemoryModeling:AddNoOwnershipChangeNotes=true
1414

1515
#include "Inputs/system-header-simulator-for-malloc.h"
16+
// For the tests in namespace protobuf_leak:
17+
#include "Inputs/system-header-simulator-for-protobuf.h"
1618

1719
//===----------------------------------------------------------------------===//
1820
// Report for which we expect NoOwnershipChangeVisitor to add a new note.
@@ -218,3 +220,34 @@ void caller() {
218220
(void)n;
219221
} // no-warning: No potential memory leak here, because that's been already reported.
220222
} // namespace symbol_reaper_lifetime
223+
224+
// Check that we do not report false positives in automatically generated
225+
// protobuf code that passes dynamically allocated memory to a certain function
226+
// named GetOwnedMessageInternal.
227+
namespace protobuf_leak {
228+
Arena *some_arena, *some_submessage_arena;
229+
230+
MessageLite *protobuf_leak() {
231+
MessageLite *p = new MessageLite(); // Real protobuf code instantiates a
232+
// subclass of MessageLite, but that's
233+
// not relevant for the bug.
234+
MessageLite *q = GetOwnedMessageInternal(some_arena, p, some_submessage_arena);
235+
return q;
236+
// No leak at end of function -- the pointer escapes in GetOwnedMessageInternal.
237+
}
238+
239+
void validate_system_header() {
240+
// The case protobuf_leak would also pass if GetOwnedMessageInternal wasn't
241+
// declared in a system header. This test verifies that another function
242+
// declared in the same header behaves differently (doesn't escape memory) to
243+
// demonstrate that GetOwnedMessageInternal is indeed explicitly recognized
244+
// by the analyzer.
245+
246+
// expected-note@+1 {{Memory is allocated}}
247+
MessageLite *p = new MessageLite();
248+
SomeOtherFunction(p);
249+
// expected-warning@+2 {{Potential leak of memory pointed to by 'p'}}
250+
// expected-note@+1 {{Potential leak of memory pointed to by 'p'}}
251+
}
252+
253+
} // namespace protobuf_leak

0 commit comments

Comments
 (0)