Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
262 changes: 262 additions & 0 deletions cpp/common/src/codingstandards/cpp/InitializationContext.qll
Original file line number Diff line number Diff line change
@@ -0,0 +1,262 @@
/**
* Provides classes and predicates for identifying uninitialized variables.
*/

import cpp
import semmle.code.cpp.controlflow.Guards
import semmle.code.cpp.controlflow.SubBasicBlocks
import codingstandards.cpp.InitializationFunctions

private newtype TInitializationContext =
/** No specific context - for functions where conditional initialization doesn't play a role */
NoContext(UninitializedVariable uv) { count(uv.getACorrelatedConditionVariable()) = 0 } or
/**
* A context where the given `LocalScopeVariable` is identified as a correlated variable with the
* given state value.
*/
CorrelatedVariable(UninitializedVariable uv, LocalScopeVariable correlatedVariable, boolean state) {
uv.getACorrelatedConditionVariable() = correlatedVariable and state = [true, false]
}

/**
* A context to apply when determining whether a given variable is uninitialized at a particular
* `SubBasicBlock` in the control flow graph.
*
* If no suitable context is found for an `UninitializedVariable`, `NoContext` is used.
*
* If one or more correlated variables are found, a `CorrelatedVariable` context is provided, with
* `true` and `false` states.
*/
class InitializationContext extends TInitializationContext {
/**
* Gets the `UninitializedVariable` for this context.
*/
UninitializedVariable getUninitializedVariable() {
this = NoContext(result) or
this = CorrelatedVariable(result, _, _)
}

/**
* Gets the correlated variable, if any.
*/
LocalScopeVariable getCorrelatedVariable() { this = CorrelatedVariable(_, result, _) }

string toString() {
if this instanceof CorrelatedVariable
then
result =
"Uninitialized variable " + getUninitializedVariable().getName() + " where location " +
getCorrelatedVariable().getName() + " is " +
any(boolean b | this = CorrelatedVariable(_, _, b))
else result = "Uninitialized variable " + getUninitializedVariable().getName()
}
}

/**
* A `SubBasicBlockCutNode` that ensures that any uninitialized variable definitions appear at the
* start of a `SubBasicBlock`.
*/
class InitializationSubBasicBlock extends SubBasicBlockCutNode {
InitializationSubBasicBlock() { this = any(UninitializedVariable uv).getADefinitionAccess() }
}

/**
* Holds if the `gc` dictates the state of variable `lv`, i.e. the state is heuristically
* identified to be different under different branches.
*/
private predicate guardDictatesLocalVariableState(
GuardCondition gc, LocalScopeVariable lv, boolean lvStateOnTrueBranch
) {
// Condition is a boolean check on the variable
gc = lv.getAnAccess() and lvStateOnTrueBranch = true
or
// Condition is a negated boolean check on the variable
gc.(NotExpr).getOperand() = lv.getAnAccess() and lvStateOnTrueBranch = false
or
// Condition controls a block which assigns to `lv`
gc.controls(lv.getAnAssignedValue().getBasicBlock(), lvStateOnTrueBranch)
}

/**
* Catches `new int;` as an expression that doesn't initialize its value. Note that the pointer returned has been initialized (ie it is a valid pointer),
* but the pointee/value has not. In our analysis, we simply count `x` as uninitialized in `x = new int` for now, though a more thorough analysis might track the initialization of `x` and `*x` separately.
Comment thread
knewbury01 marked this conversation as resolved.
*/
class NewNotInit extends NewExpr {
NewNotInit() {
this.getAllocatedType() instanceof BuiltInType and
not exists(this.getAChild())
}
}

class NonInitAssignment extends Assignment {
NonInitAssignment() { this.getRValue() instanceof NewNotInit }
}

/**
* A local variable without an initializer which is amenable to initialization analysis.
*/
class UninitializedVariable extends LocalVariable {
UninitializedVariable() {
// Not initialized at declaration
(
not exists(getInitializer().getExpr())
or
getInitializer().getExpr() instanceof NewNotInit
) and
// Not static or thread local, because they are not initialized with indeterminate values
not isStatic() and
not isThreadLocal() and
// Not atomic, which have special initialization rules
not getType().hasSpecifier("atomic") and
// Not a class type, because default initialization of a class calls the default constructor
// The default constructor may leave certain fields uninitialized, but that would be a separate
// field-wise analysis
not this.getType().getUnspecifiedType() instanceof Class and
// An analysis of an array entry also requires a field wise analysis
not this.getType().getUnspecifiedType() instanceof ArrayType and
// Ignore variables in uninstantiated templates, because we often do not have enough semantic
// information to accurately determine initialization state.
not isFromUninstantiatedTemplate(_) and
// Ignore `va_list`, that is part of the mechanism for
not getType().hasName("va_list") and
// Ignore variable defined in a block with an `asm` statement, as that may initialized the variables
not exists(AsmStmt asm | asm.getEnclosingBlock() = getParentScope()) and
// Ignore variables generated for `RangeBasedForStmt` e.g. `for(auto x : y)`
not this = any(RangeBasedForStmt f).getAChild().(DeclStmt).getADeclaration()
}

/** Gets a variable correlated with at least one use of `this` uninitialized variable. */
private LocalScopeVariable getAUseCorrelatedConditionVariable() {
/* Extracted to improve join order of getACorrelatedConditionVariable(). */
// The use is guarded by the access of a variable
exists(GuardCondition gc |
gc.controls(getAUse().getBasicBlock(), _) and
gc = result.getAnAccess()
)
}

/** Find another variable which looks like it may be correlated with the initialization of this variable. */
pragma[noinline]
LocalScopeVariable getACorrelatedConditionVariable() {
result = getAUseCorrelatedConditionVariable() and
(
// Definition is guarded by an access of the same variable
exists(GuardCondition gc |
gc.controls(getADefinitionAccess().getBasicBlock(), _) and
gc = result.getAnAccess()
)
or
// The variable is assigned in the same basic block as one of our definitions
result.getAnAssignedValue().getBasicBlock() = getADefinitionAccess().getBasicBlock()
)
}

/**
* Get a access of the variable that is assumed to initialize the variable.
* This approximates that any access in the lvalue category may be a definition.
*/
VariableAccess getADefinitionAccess() {
result = getAnAccess() and
result.isLValueCategory() and
// Not a pointless read
not result = any(ExprStmt es).getExpr() and
// not involved in a new expr assignment since that does not define
not result = any(NonInitAssignment a).getLValue()
}

/**
* Gets an access of the this variable which is not used as an lvalue, and not used as an argument
* to an initialization function.
*/
VariableAccess getAUse() {
result = this.getAnAccess() and
(
//count rvalue x (or *x) as a use if not new int
result.isRValue() and
not this.getInitializer().getExpr() instanceof NewNotInit
or
//count lvalue x as a use if used in *x and not new int
result.isLValue() and
exists(PointerDereferenceExpr e | result = e.getAChild()) and
exists(this.getInitializer()) and
not this.getInitializer().getExpr() instanceof NewNotInit
or
//count rvalue *x as a use if has new int
result.isRValue() and
this.getInitializer().getExpr() instanceof NewNotInit and
exists(PointerDereferenceExpr e | result = e.getAChild())
) and
// Not passed to another initialization function
not exists(Call c, int j | j = c.getTarget().(InitializationFunction).initializedParameter() |
result = c.getArgument(j).(AddressOfExpr).getOperand()
) and
// Not a pointless read
not result = any(ExprStmt es).getExpr() and
// sizeof operators are not real uses
not result.getParent+() instanceof SizeofOperator
}

/** Get a read of the variable that may occur while the variable is uninitialized. */
VariableAccess getAnUnitializedUse() {
exists(SubBasicBlock useSbb |
result = getAUse() and
useSbb.getANode() = result and
// This sbb is considered uninitialized in all the contexts we identified
forex(InitializationContext ct | ct.getUninitializedVariable() = this |
useSbb = getAnUninitializedSubBasicBlock(ct)
)
)
}

/**
* Gets a `SubBasicBlock` where this variable is uninitialized under the conditions specified by
* `InitializationContext`.
*/
private SubBasicBlock getAnUninitializedSubBasicBlock(InitializationContext ic) {
ic.getUninitializedVariable() = this and
(
// Base case - this SBB is the one that declares the variable
exists(DeclStmt ds |
ds.getADeclaration() = this and
result.getANode() = ds
)
or
// Recursive case - SBB is a successor of an SBB where this variable is uninitialized
exists(SubBasicBlock mid |
// result is the successor of an SBB where this is considered to be uninitialized under the
// context ic
mid = getAnUninitializedSubBasicBlock(ic) and
result = mid.getASuccessor() and
// Result is not an SBB where this variable is initialized
not getADefinitionAccess() = result and
// Determine if this is a branch at __builtin_expect where the initialization occurs inside
// the checked argument, and exclude it if so, because the CFG is known to be broken here.
not exists(FunctionCall fc |
mid.getEnd() = fc and
fc.getTarget().hasName("__builtin_expect") and
fc.getArgument(0).getAChild*() = getADefinitionAccess()
)
|
// If this is an analysis with no context
ic = NoContext(this)
or
exists(LocalScopeVariable lv | lv = ic.getCorrelatedVariable() |
// If the final node in `mid` SBB is a guard condition that affects our tracked correlated
// variable
guardDictatesLocalVariableState(mid.getEnd(), lv, _)
implies
// Then our context must match the inferred state of the correlated variable after the branch
exists(boolean lvStateOnTrueBranch |
guardDictatesLocalVariableState(mid.getEnd(), lv, lvStateOnTrueBranch)
|
result = mid.getATrueSuccessor() and
ic = CorrelatedVariable(this, lv, lvStateOnTrueBranch)
or
result = mid.getAFalseSuccessor() and
ic = CorrelatedVariable(this, lv, lvStateOnTrueBranch.booleanNot())
)
)
)
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//** THIS FILE IS AUTOGENERATED, DO NOT MODIFY DIRECTLY. **/
import cpp
import RuleMetadata
import codingstandards.cpp.exclusions.RuleMetadata

newtype Declarations7Query = TUninitializedVariableQuery()

predicate isDeclarations7QueryMetadata(Query query, string queryId, string ruleId, string category) {
query =
// `Query` instance for the `uninitializedVariable` query
Declarations7Package::uninitializedVariableQuery() and
queryId =
// `@id` for the `uninitializedVariable` query
"cpp/misra/uninitialized-variable" and
ruleId = "RULE-11-6-1" and
category = "advisory"
}

module Declarations7Package {
Query uninitializedVariableQuery() {
//autogenerate `Query` type
result =
// `Query` type for `uninitializedVariable` query
TQueryCPP(TDeclarations7PackageQuery(TUninitializedVariableQuery()))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ import Declarations1
import Declarations2
import Declarations3
import Declarations4
import Declarations7
import ExceptionSafety
import Exceptions1
import Exceptions2
Expand Down Expand Up @@ -141,6 +142,7 @@ newtype TCPPQuery =
TDeclarations2PackageQuery(Declarations2Query q) or
TDeclarations3PackageQuery(Declarations3Query q) or
TDeclarations4PackageQuery(Declarations4Query q) or
TDeclarations7PackageQuery(Declarations7Query q) or
TExceptionSafetyPackageQuery(ExceptionSafetyQuery q) or
TExceptions1PackageQuery(Exceptions1Query q) or
TExceptions2PackageQuery(Exceptions2Query q) or
Expand Down Expand Up @@ -243,6 +245,7 @@ predicate isQueryMetadata(Query query, string queryId, string ruleId, string cat
isDeclarations2QueryMetadata(query, queryId, ruleId, category) or
isDeclarations3QueryMetadata(query, queryId, ruleId, category) or
isDeclarations4QueryMetadata(query, queryId, ruleId, category) or
isDeclarations7QueryMetadata(query, queryId, ruleId, category) or
isExceptionSafetyQueryMetadata(query, queryId, ruleId, category) or
isExceptions1QueryMetadata(query, queryId, ruleId, category) or
isExceptions2QueryMetadata(query, queryId, ruleId, category) or
Expand Down
Loading
Loading