This is an archive of the discontinued LLVM Phabricator instance.

[analyzer] ExprEngine: Escape pointers in bitwise operations
ClosedPublic

Authored by Charusso on Jun 24 2019, 8:38 AM.

Diff Detail

Repository
rL LLVM

Event Timeline

Charusso created this revision.Jun 24 2019, 8:38 AM
  • Make the test fail.
Charusso updated this revision to Diff 206227.Jun 24 2019, 8:41 AM
  • Make the test pass.
Charusso added a comment.EditedJun 24 2019, 9:43 AM

These bitwise operations only affects the part of the pointer which we do not analyze. My previous approach to solve this problem caused one false positive, so we should be that strict. Just in case for the future developments, copy-pasted here:

// If we have a bitwise operation (e.g. checking low-bits) of a pointer    
// we would like to keep the pointer to work with it later. Otherwise it  
// would be an Unknown value after evaluation and we would lost tracking.  
if (B->isBitwiseOp()) {                                                    
  bool IsLhsPtr = false, IsRhsPtr = false;                                
  const MemRegion *LeftMR = nullptr, *RightMR = nullptr;                  
                                                                           
  if ((LeftMR = LeftV.getAsRegion()))                                      
    IsLhsPtr = LeftMR->getSymbolicBase();                                  
  if ((RightMR = RightV.getAsRegion()))                                    
    IsRhsPtr = RightMR->getSymbolicBase();                                
                                                                           
  // If only one of the operands is a pointer we should keep it.          
  if (IsLhsPtr ^ IsRhsPtr) {                                              
    SVal KeepV = IsLhsPtr ? LeftV : RightV;                                
    state = state->BindExpr(B, LCtx, KeepV);                              
    Bldr.generateNode(B, *it, state);                                      
    continue;                                                              
  }                                                                        
                                                                           
  // If both of the operands are pointers we would like to keep the one    
  // which dominates. Treat the lower-level pointer as a helper to set up  
  // some low-bits of the higher-level pointer and keep the latter.        
  if (IsLhsPtr && IsRhsPtr) {                                              
    bool IsLhsDominates = false, IsRhsDominates = false;                  
                                                                           
    if (const MemRegion *LeftBase = LeftMR->getBaseRegion())              
      if (const MemSpaceRegion *LeftMS = LeftBase->getMemorySpace())      
        IsLhsDominates = isa<HeapSpaceRegion>(LeftMS);                    
                                                                           
    if (const MemRegion *RightBase = RightMR->getBaseRegion())            
      if (const MemSpaceRegion *RightMS = RightBase->getMemorySpace())    
        IsRhsDominates = isa<HeapSpaceRegion>(RightMS);                    
                                                                           
    if (IsLhsDominates ^ IsRhsDominates) {                                
      SVal KeepDominatorV = IsLhsDominates ? LeftV : RightV;              
      state = state->BindExpr(B, LCtx, KeepDominatorV);                    
      Bldr.generateNode(B, *it, state);                                    
      continue;                                                            
    }                                                                      
  }                                                                        
}
Charusso marked an inline comment as done.Jun 24 2019, 10:58 AM
Charusso added inline comments.
clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
123 ↗(On Diff #206227)

I have seen we are producing tons of Unknowns and I am still not sure where they go, but even if I remove that condition these Unknowns will not shown up in the ExplodedGraph. I believe that should be the correct behavior.

NoQ added a comment.Jun 24 2019, 3:19 PM

Nice!~ I'm glad this is getting sorted out.

clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
103 ↗(On Diff #206227)

How about the following test case in which not Bar but &Bar gets bitwise-operated upon?

C **test() {
  C *Bar = new C;
  C **Baz = &Bar;
  Baz = reinterpret_cast<C **>(reinterpret_cast<uintptr_t>(Baz) | 0x1);
  Baz = reinterpret_cast<C **>(reinterpret_cast<uintptr_t>(Baz) & ~static_cast<uintptr_t>(0x1));
  delete *Baz;
}

The difference is that in this case the escaping region doesn't have a symbolic base. And i believe that symbolic regions aren't special here in any way.

I suggest doing an escape when the resulting value is unknown after evalBinOp but regardless of any other conditions that you mentioned. Simply because there's a loss of information.

clang/test/Analysis/symbol-escape.cpp
2 ↗(On Diff #206227)

Pls include core as well, just in case, because running path-sensitive analysis without core checkers is unsupported.

5–6 ↗(On Diff #206227)

I think something went wrong while uploading the patch. This diff should add this whole test file, not update it.

7 ↗(On Diff #206227)

Relying on everybody's system headers is super flaky, don't do this in tests.

Please define stuff that you use directly like other tests do:

typedef unsigned __INTPTR_TYPE__ uintptr_t;
15 ↗(On Diff #206227)

Bar is reduced to one bit here. It's a legit leak. I think you meant to swap Foo and Bar.

20 ↗(On Diff #206227)

In any case, passing a pointer to delete that wasn't obtained from new is undefined behavior. In order to produce a test that's also a correct code, i think we should either undo our bitwise operations, or perform an escape in a different manner (say, return the pointer to the caller).

Charusso updated this revision to Diff 206332.Jun 24 2019, 4:48 PM
Charusso marked 11 inline comments as done.
  • Fix.
Charusso marked an inline comment as done.Jun 24 2019, 4:49 PM
Charusso added inline comments.
clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
103 ↗(On Diff #206227)

That is a great idea! I wanted to make it more generic. Thanks you!

clang/test/Analysis/symbol-escape.cpp
26 ↗(On Diff #206332)

I have made it fail on master.

5–6 ↗(On Diff #206227)

This is a test-driven-development-driven patch, first make the test fail, then make it pass.

7 ↗(On Diff #206227)

It is a predefined header in Modules so Windows will not break.

Please note that only one of the test files (malloc.c) use that definition, most of them using that I have used in D62926, so I believe this header is the correct one as it is in four tests and Windows-approved by D62926.

15 ↗(On Diff #206227)

I assumed that we are doing our low-bits magic, where we have two identical objects. The SymRegion sets up the HeapSymRegion's property, where no leak happen. We just could change the first low-bit and set up some crazy flag, like isChecked(), so we do not lost the tracking. Also we have four bits to change, so there is no edge case going on.

20 ↗(On Diff #206227)

Whoops! I really wanted to make the test look great.

NoQ added inline comments.Jun 24 2019, 5:09 PM
clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
123 ↗(On Diff #206227)

Unknowns don't show up in the Environment, but they do show up in the Store.

clang/test/Analysis/symbol-escape.cpp
12 ↗(On Diff #206332)

Let's make fancier names, like, dunno, test_escape_into_bitwise_ops and test_indirect_escape_into_bitwise_ops.

5–6 ↗(On Diff #206227)

Sounds nice in your local git but for phabricator it's definitely better to upload the exact thing that you're going to commit. It is assumed that the tests would fail without the patch and will pass with the patch, and the information on how exactly did the tests fail previously would look nice in the description. Sometimes some (but usually not all) of the tests are added just because they were discovered to be nice tests related to the patch but not because they failed before the patch, which is also fine and should ideally be mentioned in the description.

7 ↗(On Diff #206227)

Mmm, ok, if it's an internal file then i guess it might be non-flaky. But even then, if it's something more complicated than the definition of uintptr_t, then your test will be affected by the contents of this file, which may change. I'd slightly prefer to reduce the amount of such stuff in our tests simply for keeping them minimal.

15 ↗(On Diff #206227)

I guess just flip a single bit like i did in my test?

Charusso updated this revision to Diff 206341.Jun 24 2019, 5:33 PM
Charusso marked 8 inline comments as done.
Charusso added inline comments.
clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
123 ↗(On Diff #206227)

Oh, I really wanted to see them in the Environment. Thanks for the clarification!

clang/test/Analysis/symbol-escape.cpp
5–6 ↗(On Diff #206227)

Lesson learned, thanks for the idea!

7 ↗(On Diff #206227)

Hm, I hope that malloc.c one works.

NoQ accepted this revision.Jun 24 2019, 5:36 PM

Great, thanks!

clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp
104 ↗(On Diff #206341)

Pls put the comment inside else.

This revision is now accepted and ready to land.Jun 24 2019, 5:36 PM
Charusso updated this revision to Diff 206344.Jun 24 2019, 5:43 PM
Charusso marked an inline comment as done.
  • Better comment.
This revision was automatically updated to reflect the committed changes.
Herald added a project: Restricted Project. · View Herald TranscriptJun 24 2019, 5:44 PM

Thanks for the review ! That was the largest-work three lines of code of the history because I was blind.