[PATCH][Review request] unix.Malloc checker improvement: +handling new/delete, +memory.MismatchedFree, +memory.MismatchedDelete, +improved display names for allocators/deallocators

Anton Yartsev anton.yartsev at gmail.com
Thu Mar 14 08:38:26 PDT 2013


On 12.03.2013 22:06, Anna Zaks wrote:
> Thanks Anton! The patch looks good overall. Have you evaluated it on 
> some real C++ codebases?
Not yet. Failed to launch scan-build with my Perl 5.16.2.

>
> Below are some comments.
>
> ---
> +  // The return value from operator new is already bound and we don't 
> want to
> +  // break this binding so we call MallocUpdateRefState instead of 
> MallocMemAux.
> +  State = MallocUpdateRefState(C, NE, State, NE->isArray() ? 
> AF_CXXNewArray
> +     : AF_CXXNew);
> Why is this different from handling malloc? MallocMemAux does break 
> the binding formed by default handling of malloc and forms a new 
> binding with more semantic information. (I am fine with addressing 
> this after the initial commit/commits.)
In case of 'new' the memory can be initialized to a non-default value 
(e.g. int *p = new int(3); ). The existing logic of MallocMemAux() 
breaks the binding and information about the initialization value is 
lost. This causes several tests to fail.
Changed the comment to be more informative.

>
> ---
>  def MallocOptimistic : Checker<"MallocWithAnnotations">,
> -  HelpText<"Check for memory leaks, double free, and use-after-free 
> problems. Assumes that all user-defined functions which might free a 
> pointer are annotated.">,
> +  HelpText<"Check for memory leaks, double free, and use-after-free 
> problems. Traces memory managed by malloc()/free(). Assumes that all 
> user-defined functions which might free a pointer are annotated.">,
>
> Shouldn't the MallocWithAnnotations only check the functions which are 
> explicitly annotated? I think it might be better to change the code 
> rather than the comment.
Currently MallocWithAnnotations is designed as extended unix.Malloc and 
it is not a single line change to make it distinct. Can we address this 
after primary commits?

>
> ---
> +  unsigned K : 2; // Kind enum, but stored as a bitfield.
> +  unsigned Family : 30; // Rest of 32-bit word, currently just an 
> allocation
> +                        // family.
>
> We usually add the comment on a line preceding the declaration, like this:
> +  // Kind enum, but stored as a bitfield.
> +  unsigned K : 2;
> +  // Rest of 32-bit word, currently just an allocation family.
> +  unsigned Family : 30;
>
> ---
> +  // Check if an expected deallocation function matches the real one.
> +  if (RsBase &&
> +      RsBase->getAllocationFamily() != AF_None &&
> +      RsBase->getAllocationFamily() != getAllocationFamily(C, 
> ParentExpr) ) {
> Is it possible to have AF_None family here? Shouldn't " 
> RsBase->getAllocationFamily() != AF_None" be inside an assert?
It is possible. In the example below 
initWithCharactersNoCopy:length:freeWhenDone takes ownership of memory 
allocated by unknown means, RefState with AF_None is bound to the call 
and after, when free() is processed, we have 
RsBase->getAllocationFamily() == AF_None.

void test(unichar *characters) {
   NSString *string = [[NSString alloc] 
initWithCharactersNoCopy:characters length:12 freeWhenDone:1];
   if (!string) {free(characters);}
}

>
> ---
> +// Used to check correspondence between allocators and deallocators.
> +enum AllocationFamily {
> The comment should describe what family is. It is a central notion for 
> the checker and I do not think we explain it anywhere.
>
> ---
> The patch is very long. Is it possible to split it up into smaller 
> chunks 
> (http://llvm.org/docs/DeveloperPolicy.html#incremental-development)?
Committed the initial refactoring as r176949.

Let's start! Attached is the cplusplus.NewDelete checker separated from 
the patch.

>
> Thanks,
> Anna.
> On Mar 12, 2013, at 8:56 AM, Anton Yartsev <anton.yartsev at gmail.com 
> <mailto:anton.yartsev at gmail.com>> wrote:
>
>> On 12.03.2013 2:24, Jordan Rose wrote:
>>> Looking over this one last time...
>>>
>>>> -  os << "Argument to free() is offset by "
>>>> +  os << "Argument to ";
>>>> +  if (!printAllocDeallocName(os, C, DeallocExpr))
>>>> +    os << "deallocator";
>>>> +  os << " is offset by "
>>>> << offsetBytes
>>>> << " "
>>>> << ((abs(offsetBytes) > 1) ? "bytes" : "byte")
>>>> -     << " from the start of memory allocated by malloc()";
>>>> +     << " from the start of ";
>>>> +  if (AllocExpr) {
>>>> +    SmallString<100> TempBuf;
>>>> +    llvm::raw_svector_ostream TempOs(TempBuf);
>>>> +    if (printAllocDeallocName(TempOs, C, AllocExpr))
>>>> +        os << "memory allocated by " << TempOs.str();
>>>> +      else
>>>> +        os << "allocated memory";
>>>> +  } else
>>>> +    printExpectedAllocName(os, C, DeallocExpr);
>>>> +
>>>
>>> The way you've set it up, AllocExpr will never be NULL (which is 
>>> good, because otherwise you'd get "...from the start of malloc()" 
>>> rather than "from the start of memory allocated by malloc()").
>> Strange logic. Fixed.
>>
>>>
>>>
>>>> + at interface Wrapper : NSData
>>>> +- (id)initWithBytesNoCopy:(void *)bytes length:(NSUInteger)len;
>>>> + at end
>>>
>>> As I discovered with the rest of the ObjC patch, this isn't a great 
>>> test case because the analyzer doesn't consider it a system method. 
>>> However, I don't see you use it anywhere in the file anyway, so you 
>>> can probably just remove it.
>>>
>>>
>>>> +void testNew11(NSUInteger dataLength) {
>>>> +  int *data = new int;
>>>> +  NSData *nsdata = [NSData dataWithBytesNoCopy:data 
>>>> length:sizeof(int) freeWhenDone:1]; // expected-warning{{Memory 
>>>> allocated by 'new' should be deallocated by 'delete', not 
>>>> +dataWithBytesNoCopy:length:freeWhenDone:}}
>>>> +}
>>>
>>> Hm, that is rather unwieldy, but what bothers me more is that 
>>> +dataWithBytesNoCopy:length:freeWhenDone:/doesn't/ free the memory; 
>>> it just takes ownership of it. I guess it's okay to leave that as a 
>>> FIXME for now, but in the long run we should say something like 
>>> "+dataWithBytesNoCopy:length:freeWhenDone: cannot take ownership of 
>>> memory allocated by 'new'." (In the "hold" cases, most likely the 
>>> user wasn't intending to free
>>>
>>> But, this doesn't have to block the patch; you/we can fix it 
>>> post-commit.
>>>
>>>
>>>> +  delete x; // FIXME: Shoud detect pointer escape and keep silent. 
>>>> checkPointerEscape() is not currently invoked for delete.
>>>
>>> Pedantic note: the real issue here is that we don't model delete at 
>>> all (see ExprEngine::VisitCXXDeleteExpr). The correct model won't 
>>> explicitly invoke checkPointerEscape, but it will construct a 
>>> CallEvent for the deletion operator and then try to evaluate that 
>>> call—or at least invalidate the arguments like VisitCXXNewExpr does 
>>> for placement args—which will cause the argument region to get 
>>> invalidated and checkPointerEscape to be triggered.
>>>
>>> Jordan
>> Updated patch attached.
>> -- 
>> Anton
>> <MallocChecker_v11.patch>
>
-- 
Anton

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20130314/97338a10/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: cplusplus.NewDelete_v1.patch
Type: text/x-diff
Size: 22018 bytes
Desc: not available
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20130314/97338a10/attachment.patch>


More information about the cfe-commits mailing list