Static Analysis and C++ - Neil Macintosh - CppCon 2015
Static Analysis and C++ - Neil Macintosh - CppCon 2015
Neil MacIntosh
[email protected]
thing.cpp(15) : warning CXXXX: Overflow using expression '(void *)(&thing)'
struct Thing { Buffer accessed is thing
int someInt; Buffer is of length 4 bytes [size of variable]
int anotherInt; Accessing 8 bytes starting at byte offset 0
};
// validate parameters
if (thing == nullptr)
return Result::InvalidParameter;
• Lots of free, open source and commercial offerings for static analysis
of C++ source
void StateMachine::OnStop() {
// ... see if it's ok to stop ..
m_current = States::Started;
return Result::Success;
}
void StateMachine::OnStop() {
// ... see if it's ok to stop ..
m_current = States::Started;
void Initialize() {
if (g_flags & ENABLE_COOL_STUFF) {
// ... enable the cool functionality ...
}
}
Warning here will annoy users as creating “dead code” at compile-time based
on bit-wise expressions is a common (and useful) configuration technique.
So we are silent here.
CppCon 2015 Static Analysis and C++ 11
// bitflags that control system features
enum SystemLevels {
ENABLE_COOL_STUFF,
ENABLE_OTHER_STUFF,
ENABLE_STANDARD_STUFF
};
void Initialize() {
if (g_flags & ENABLE_COOL_STUFF) {
// ... enable the cool functionality ...
}
} config.cpp(11) : warning C6313: Incorrect operator: zero-valued flag cannot
be tested with bitwise-and. Use an equality test to check for zero-valued
flags.
void Initialize() {
if (g_flags & ENABLE_COOL_STUFF) {
// ... enable the cool functionality ...
}
} config.cpp(11) : warning C6313: Incorrect operator: ENABLE_COOL_STUFF has
a value of zero. Testing it with bit-wise AND will always result in zero. You may
have meant to check for equality instead.
if (recLength < 3)
return -1;
buffer[0] = rec[0];
buffer[1] = rec[1];
buffer[2] = rec[2];
return payloadLength + 3;
}
CppCon 2015 Static Analysis and C++ 18
// return number of bytes read\written from socket or < 0 on error
int OS::Socket::Read(byte* buf, size_t bufSize);
int OS::Socket::Write(byte* buf, size_t bufSize);
...
int rc = OS::Socket::Read(readBuffer.data(), readBuffer.size());
if (rc <= 0) return -1;
rc = MakePacket(readBuffer.size(), readBuffer.data(),
writeBuffer.size(), writeBuffer.data());
if (rc <= 0) return -1;
rc = OS::Socket::Write(writeBuffer.data(), rc);
if (rc <= 0) return -1;
...
buffer[0] = rec[0];
buffer[1] = rec[1];
buffer[2] = rec[2];
return payloadLength + 3;
}
CppCon 2015 Static Analysis and C++ 21
// return number of bytes read\written from socket or < 0 on error
int OS::Socket::Read(byte* buf, size_t bufSize);
int OS::Socket::Write(byte* buf, size_t bufSize);
...
int rc = OS::Socket::Read(readBuffer.data(), readBuffer.size());
if (rc <= 0) return -1;
rc = MakePacket(readBuffer.size(), readBuffer.data(),
writeBuffer.size(), writeBuffer.data());
if (rc <= 0) return -1;
rc = OS::Socket::Write(writeBuffer.data(), rc);
if (rc <= 0) return -1;
...
rc = MakePacket(readBuffer.size(), readBuffer.data(),
writeBuffer.size(), writeBuffer.data());
if (rc <= 0) return -1;
rc = OS::Socket::Write(writeBuffer.data(), rc);
if (rc <= 0) return -1;
CppCon 2015 Static Analysis and C++ 23
...
Pros and cons of annotations
• Suddenly, we can find subtle defects that previously eluded us
• Intentions are clearer and we can reduce false positives
if (recLength < 3)
return -1;
buffer[0] = rec[0];
buffer[1] = rec[1];
buffer[2] = rec[2]; Can still catch error the same way.
if (memcpy_s(buffer.data() + 3, buffer.length() - 3, rec + 3, payloadLength) != 0)
return false;
return payloadLength + 3;
}
CppCon 2015 Static Analysis and C++ 26
bool MakePacket(array_view<byte> rec, array_buffer<byte> buffer)
{
if (rec.length() < 3) This is range-checked at runtime. We can also warn
statically that it may fail.
return false;
rec = rec.first(payloadLength + 3)
buffer.set_used(rec.length());
return true;
}
rc = OS::Socket::Write(packet.data(), packet.used_length());
if (rc <= 0) return -1;
...
CppCon 2015 Static Analysis and C++ 28
Good and bad of types
• Suddenly, we can find subtle defects that previously eluded us
• Intentions are clearer and we can reduce false positives
• Now we can use the type checker to do some of the work for us!
• SOME DEFECTS ARE NO LONGER POSSIBLE – THEY DON’T COMPILE
• They are not viral – you can preserve legacy code and ABIs
• They are not source code
• They are not are a form of language extension
• You don’t have to interpret them, they are precisely defined
• bounds + types: less than 600 lines of C++ against our framework
• Resources:
• http://microsoft.github.io/CodeAnalysis
• https://github.com/sarif-standard/
• https://msdn.microsoft.com/en-us/library/d3bbz7tz.aspx (Code Analysis in VS)
• http://clang.llvm.org/extra/clang-tidy/
CppCon 2015 Static Analysis and C++ 36