[cfe-dev] Static Analyzer: LiveVariables and SymbolReaper

Anna Zaks via cfe-dev cfe-dev at lists.llvm.org
Sun Oct 29 21:56:23 PDT 2017



Sent from my iPhone

> On Oct 29, 2017, at 5:19 PM, Alexander Shaposhnikov <alexander.v.shaposhnikov at gmail.com> wrote:
> 
> ohh, typo above: i meant 
>      bool isLive(const Stmt *S, const VarDecl *D)
> (instead of LiveVariables::isLive(const Stmt *Loc, const Stmt *S))
> 
>> On Sun, Oct 29, 2017 at 5:08 PM, Alexander Shaposhnikov <alexander.v.shaposhnikov at gmail.com> wrote:
>> Hi, 
>> (long read, sorry in advance)
>> i'm looking into the internals of SymbolReaper and LiveVariables and have 
>> several questions on my mind. Since I have not built a consistent understanding of what's going on there yet + assume I might be missing smth, I'd like to start with some very basic example to show how the (potential) issue manifests itself there. Any explanations / suggestions / comments would be greatly appreciated.
>> 
>> Example s.c:
>> typedef struct _IO_FILE FILE;
>> extern FILE *fopen(const char *path, const char *mode);
>> 
>> void g() {};
>> void h() {};
>> 
>> void f(int c) {
>>   FILE *p = fopen("foo.c", "r");
>>   g();
>>   h();
>>   return;
>> }
>> clang -cc1 -analyze -analyzer-checker=debug.ViewCFG s.c
>> says that there 3 blocks: B2(entry) -> B1 -> B0(exit), B2 and B0 are essentially empty and all the interesting things are inside B1. 
>> Now let's try to run clang -c --analyze -Xanalyzer -analyzer-checker=alpha.unix.Stream s.c
>> 
>> s.c:8:9: warning: Value stored to 'p' during its initialization is never read
>>   FILE *p = fopen("foo.c", "r");
>>         ^   ~~~~~~~~~~~~~~~~~~~
>> s.c:9:3: warning: Opened File never closed. Potential Resource leakv
>>   g();
>>   ^~~
>> 2 warnings generated.
>> 
>> What looks suspicious here - the location of the warning
>> "s.c:9:3: warning: Opened File never closed. Potential Resource leak" 
>> - it points to g() instead of the end of function or return statement.
>> 

The analyzer is trying to report the leak at the earliest point. It uses live variable analysis for that:
https://en.m.wikipedia.org/wiki/Live_variable_analysis


>> Now let's run the analyzer under lldb and set the breakpoint at 
>> LiveVariables::isLive(const Stmt *Loc, const Stmt *S)
>> 
>>    178 	bool LiveVariables::isLive(const Stmt *Loc, const Stmt *S) {
>> 
>> (lldb) p D->dump()
>> VarDecl 0x11286c178 <s.c:8:3, col:31> col:9 p 'FILE *' cinit
>> `-CallExpr 0x11286c350 <col:13, col:31> 'FILE *'
>>   |-ImplicitCastExpr 0x11286c338 <col:13> 'FILE *(*)(const char *, const char *)' <FunctionToPointerDecay>
>>   | `-DeclRefExpr 0x11286c1d8 <col:13> 'FILE *(const char *, const char *)' Function 0x11286bce0 'fopen' 'FILE *(const char *, const char *)'
>>   |-ImplicitCastExpr 0x11286c3a0 <col:19> 'const char *' <BitCast>
>>   | `-ImplicitCastExpr 0x11286c388 <col:19> 'char *' <ArrayToPointerDecay>
>>   |   `-StringLiteral 0x11286c238 <col:19> 'char [6]' lvalue "foo.c"
>>   `-ImplicitCastExpr 0x11286c3d0 <col:28> 'const char *' <BitCast>
>>     `-ImplicitCastExpr 0x11286c3b8 <col:28> 'char *' <ArrayToPointerDecay>
>>       `-StringLiteral 0x11286c2a8 <col:28> 'char [2]' lvalue "r"
>> 
>> (lldb) p getImpl(impl).stmtsToLiveness[S].isLive(D)
>> (bool) $0 = false
>> 
>> (lldb) p S->dump()
>> CallExpr 0x11286c470 'void'
>> `-ImplicitCastExpr 0x11286c458 'void (*)()' <FunctionToPointerDecay>
>>   `-DeclRefExpr 0x11286c400 'void ()' Function 0x11286be18 'g' 'void ()'
>> 
>> (lldb) fr sel 7
>> frame #7: 0x0000000106ed4397 clang-6.0`clang::ento::ExprEngine::ProcessStmt(this=0x00007fff5fbf97c0, S=const clang::CFGStmt @ 0x00007fff5fbf8f50, Pred=0x0000000114004a30) at ExprEngine.cpp:497
>> 
>> Here getImpl(impl).stmtsToLiveness[S].isLive(D) returns false and
>> Static Analyzer thinks that the variable "p" is dead and will remove the binding (from the store). Since this variable is marked as dead, 
>> the corresponding checkers are called (in ExprEngine::removeDead getCheckerManager().runCheckersForDeadSymbols ...) and the stream checker reports a leak. 
>> In fact, if i'm not mistaken, at this point the variable "p" is not dead yet and the leak should be reported much later at the end of the function.
>> 
>> The problem does not appear to be specific to StreamChecker, after playing with several other simple examples, it looks like  LiveVariables::isLive returns false in most cases
>> (and so does SymbolReaper::isLive(const VarRegion *VR, bool includeStoreBindings) ).
>> This results in loosing information about variables or emitting diagnostics at the wrong locations, for example, see FIXMEs at 
>> llvm/tools/clang/test/Analysis/unions.cpp +96,  
>> llvm/tools/clang/test/Analysis/self-assign.cpp +41, 
>> llvm/tools/clang/test/Analysis/pr22954.c in "int f19(int i) {...}", llvm/tools/clang/test/Analysis/pr22954.c in "int f31() {...}" etc.
>> 
>> So the question here - if CSA works as expected for this example 
>> (in particular, I'm wondering if LiveVariables::computeLiveness 
>> does the right thing or, alternatively, if it's used properly). 
>> 
>> P.S. After playing a little bit more with different toy code snippets and dumping the full state
>> of LiveVariables (everything: stmtsToLiveness, blocksEndToLiveness etc) it looks like in most cases it's almost empty. 
>> P.P.S. The following experiment might be of some interest: if one switches the method
>> SymbolReaper::isLive to 
>> (VarContext == CurrentContext)  || VarContext->isParentOf(CurrentContext) || isAlwaysAlive(....) 
>> the behavior for some very simple tests (i was looking at the toy tests with 3-4 blocks in CFG) becomes more predictable (and a bunch of FIXMEs get corrected), 
>> but this, of course, would not be the correct implementation in the general case.  
>> 
>> Kind regards,
>> Alexander Shaposhnikov
> 
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-dev/attachments/20171029/c421a2b8/attachment.html>


More information about the cfe-dev mailing list