C++: Models for side-effect-free functions

This commit adds a new model interface that describes the known side effects (or lack thereof) of a library function. Does it read memory, does it write memory, and do any of its parameters escape? Initially, we have models for just two Standard Library functions: `std::move` and `std::forward`, which neither read nor write memory, and do not escape their parameter.

IR construction has been updated to insert the correct side effect instruction (or no side effect instruction) based on the model.
This commit is contained in:
Dave Bartolomeo
2018-11-30 12:06:19 -08:00
parent af443569d9
commit 309b703e47
4 changed files with 141 additions and 3 deletions

View File

@@ -1,6 +1,7 @@
import cpp
private import semmle.code.cpp.ir.implementation.Opcode
private import semmle.code.cpp.ir.internal.OperandTag
private import semmle.code.cpp.models.interfaces.SideEffectFunction
private import InstructionTag
private import TranslatedElement
private import TranslatedExpr
@@ -37,9 +38,18 @@ abstract class TranslatedCall extends TranslatedExpr {
isGLValue = false
) or
(
hasSideEffect() and
tag = CallSideEffectTag() and
opcode instanceof Opcode::CallSideEffect and
resultType instanceof UnknownType and
(
if hasWriteSideEffect() then (
opcode instanceof Opcode::CallSideEffect and
resultType instanceof UnknownType
)
else (
opcode instanceof Opcode::CallReadSideEffect and
resultType instanceof VoidType
)
) and
isGLValue = false
)
}
@@ -68,9 +78,13 @@ abstract class TranslatedCall extends TranslatedExpr {
(
(
tag = CallTag() and
result = getInstruction(CallSideEffectTag())
if hasSideEffect() then
result = getInstruction(CallSideEffectTag())
else
result = getParent().getChildSuccessor(this)
) or
(
hasSideEffect() and
tag = CallSideEffectTag() and
result = getParent().getChildSuccessor(this)
)
@@ -183,6 +197,18 @@ abstract class TranslatedCall extends TranslatedExpr {
* Holds if the call has any arguments, not counting the `this` argument.
*/
abstract predicate hasArguments();
predicate hasReadSideEffect() {
any()
}
predicate hasWriteSideEffect() {
any()
}
private predicate hasSideEffect() {
hasReadSideEffect() or hasWriteSideEffect()
}
}
/**
@@ -280,6 +306,14 @@ class TranslatedFunctionCall extends TranslatedCallExpr, TranslatedDirectCall {
override Function getInstructionFunction(InstructionTag tag) {
tag = CallTargetTag() and result = funcCall.getTarget()
}
override predicate hasReadSideEffect() {
functionReadsMemory(funcCall.getTarget())
}
override predicate hasWriteSideEffect() {
functionWritesMemory(funcCall.getTarget())
}
}
/**

View File

@@ -1,3 +1,4 @@
private import implementations.IdentityFunction
private import implementations.Inet
private import implementations.Memcpy
private import implementations.Printf

View File

@@ -0,0 +1,35 @@
import semmle.code.cpp.Function
import semmle.code.cpp.models.interfaces.DataFlow
import semmle.code.cpp.models.interfaces.SideEffectFunction
/**
* The standard function templates `std::move` and `std::identity`
*/
class IdentityFunction extends DataFlowFunction, SideEffectFunction {
IdentityFunction() {
this.getNamespace().getParentNamespace() instanceof GlobalNamespace and
this.getNamespace().getName() = "std" and
(
this.getName() = "move" or
this.getName() = "forward"
)
}
override predicate readsMemory() {
none()
}
override predicate writesMemory() {
none()
}
override predicate parameterEscapes(int index) {
// Note that returning the value of the parameter does not count as escaping.
none()
}
override predicate hasDataFlow(FunctionInput input, FunctionOutput output) {
// These functions simply return the argument value.
input.isInParameter(0) and output.isOutReturnValue()
}
}

View File

@@ -0,0 +1,68 @@
/**
* Provides an abstract class for accurate dataflow modeling of library
* functions when source code is not available. To use this QL library,
* create a QL class extending `SideEffectFunction` with a characteristic
* predicate that selects the function or set of functions you are modeling.
* Within that class, override the predicates provided by `SideEffectFunction`
* to match the flow within that function.
*/
import semmle.code.cpp.Function
import semmle.code.cpp.models.Models
/**
* Models the side effects of a library function.
*/
abstract class SideEffectFunction extends Function {
/**
* Holds if the function may read from memory that was defined before entry to the function. This
* memory could be from global variables, or from other memory that was reachable from a pointer
* that was passed into the function.
*/
abstract predicate readsMemory();
/**
* Holds if the function may write to memory that remains allocated after the function returns.
* This memory could be from global variables, or from other memory that was reachable from a
* pointer that was passed into the function.
*/
abstract predicate writesMemory();
/**
* Holds if any address passed to the parameter at the specified index is retained after the
* function returns.
*/
abstract predicate parameterEscapes(int index);
}
/**
* Holds if the function `f` may read from memory that was defined before entry to the function.
* This memory could be from global variables, or from other memory that was reachable from a
* pointer that was passed into the function.
*/
predicate functionReadsMemory(Function f) {
not exists(SideEffectFunction sideEffect |
sideEffect = f and not sideEffect.readsMemory()
)
}
/**
* Holds if the function `f` may write to memory that remains allocated after the function returns.
* This memory could be from global variables, or from other memory that was reachable from a
* pointer that was passed into the function.
*/
predicate functionWritesMemory(Function f) {
not exists(SideEffectFunction sideEffect |
sideEffect = f and not sideEffect.writesMemory()
)
}
/**
* Holds if any address passed to the parameter at the specified index is retained after the
* function returns.
*/
predicate functionParameterEscapes(Function f, int index) {
not exists(SideEffectFunction sideEffect |
exists(f.getParameter(index)) and sideEffect = f and not sideEffect.parameterEscapes(index)
)
}