mirror of
https://github.com/zealdocs/zeal.git
synced 2026-08-30 00:50:51 +08:00
728 lines
25 KiB
Plaintext
728 lines
25 KiB
Plaintext
/**
|
|
* @id zeal/cpp/use-after-expired-lifetime
|
|
* @name Use of object after its lifetime has ended (Qt-aware patch)
|
|
* @description Accessing an object after its lifetime has ended can result in security vulnerabilities and undefined behavior. This is a local patched copy of the upstream `cpp/use-after-expired-lifetime` query that drops the over-broad "has a field which is a lifetime pointer/owner type" classification clauses, which misclassify any class with a private d-pointer (most Qt value types) as pointer-like.
|
|
* @kind problem
|
|
* @precision medium
|
|
* @problem.severity error
|
|
* @tags correctness
|
|
* security
|
|
* experimental
|
|
* external/cwe/cwe-416
|
|
*/
|
|
|
|
import cpp
|
|
import semmle.code.cpp.controlflow.Nullness
|
|
|
|
class StarOperator extends Operator {
|
|
StarOperator() {
|
|
this.hasName("operator*") and
|
|
this.getNumberOfParameters() = 0
|
|
}
|
|
}
|
|
|
|
class IncrementOperator extends Operator {
|
|
IncrementOperator() {
|
|
this.hasName("operator++") and
|
|
this.getNumberOfParameters() = 0
|
|
}
|
|
}
|
|
|
|
class StructureDerefOperator extends Operator {
|
|
StructureDerefOperator() {
|
|
this.hasName("operator->") and
|
|
this.getNumberOfParameters() = 0
|
|
}
|
|
}
|
|
|
|
class SubscriptOperator extends Operator {
|
|
SubscriptOperator() {
|
|
this.hasName("operator[]") and
|
|
this.getNumberOfParameters() = 1
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A type which is an `Indirection` type according to the Lifetime profile.
|
|
*
|
|
* An indirection type is either a `LifetimePointerType` or `LifetimeOwnerType`.
|
|
*/
|
|
abstract class LifetimeIndirectionType extends Type {
|
|
/**
|
|
* Gets the `DerefType` of this indirection type.
|
|
*
|
|
* This corresponds to the owned or pointed to type.
|
|
*/
|
|
Type getDerefType() {
|
|
result = this.(PointerType).getBaseType()
|
|
or
|
|
result = this.(ReferenceType).getBaseType()
|
|
or
|
|
exists(MemberFunction mf | mf.getDeclaringType() = this |
|
|
result = mf.(StarOperator).getType().getUnspecifiedType().(ReferenceType).getBaseType()
|
|
or
|
|
result = mf.(SubscriptOperator).getType().getUnspecifiedType().(ReferenceType).getBaseType()
|
|
or
|
|
result =
|
|
mf.(StructureDerefOperator).getType().getUnspecifiedType().(PointerType).getBaseType()
|
|
or
|
|
mf.getName() = "begin" and
|
|
result = mf.getType().(LifetimePointerType).getDerefType()
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A lifetime owner type.
|
|
*
|
|
* A type which owns another object. For example, `std::unique_ptr`. Includes
|
|
* `LifetimeSharedOwnerType`.
|
|
*
|
|
* NOTE (zeal patch): the upstream `Has a field which is a lifetime owner type`
|
|
* clause is intentionally omitted — it caused widespread false positives on Qt
|
|
* value classes that hold private d-pointers.
|
|
*/
|
|
class LifetimeOwnerType extends LifetimeIndirectionType {
|
|
LifetimeOwnerType() {
|
|
// Any shared owner types are also owner types
|
|
this instanceof LifetimeSharedOwnerType
|
|
or
|
|
// This is a container type, or a type with a star operator and..
|
|
(
|
|
this instanceof ContainerType
|
|
or
|
|
exists(StarOperator mf | mf.getDeclaringType() = this)
|
|
) and
|
|
// .. has a "user" provided destructor
|
|
exists(Destructor d |
|
|
d.getDeclaringType() = this and
|
|
not d.isCompilerGenerated()
|
|
)
|
|
or
|
|
// Any specified version of an owner type is also an owner type
|
|
this.getUnspecifiedType() instanceof LifetimeOwnerType
|
|
or
|
|
// Derived from a public base class which is a owner type
|
|
exists(ClassDerivation cd |
|
|
cd = this.(Class).getADerivation() and
|
|
cd.getBaseClass() instanceof LifetimeOwnerType and
|
|
cd.getASpecifier().hasName("public")
|
|
)
|
|
or
|
|
// Lifetime profile treats the following types as owner types, even though they don't fully
|
|
// adhere to the requirements above
|
|
this.(Class)
|
|
.hasQualifiedName("std",
|
|
["stack", "queue", "priority_queue", "optional", "variant", "any", "regex"])
|
|
or
|
|
// Explicit annotation on the type
|
|
this.getAnAttribute().getName().matches("gsl::Owner%")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A `ContainerType`, based on `[container.requirements]` with some adaptions to capture more real
|
|
* world containers.
|
|
*/
|
|
class ContainerType extends Class {
|
|
ContainerType() {
|
|
// We use a simpler set of heuristics than the `[container.requirements]`, requiring only
|
|
// `begin()`/`end()`/`size()` as the minimum API for something to be considered a "container"
|
|
// type
|
|
this.getAMemberFunction().getName() = "begin" and
|
|
this.getAMemberFunction().getName() = "end" and
|
|
this.getAMemberFunction().getName() = "size"
|
|
or
|
|
// This class is a `ContainerType` if it is constructed from a `ContainerType`. This is
|
|
// important, because templates may not have instantiated all the required member functions
|
|
exists(TemplateClass tc |
|
|
this.isConstructedFrom(tc) and
|
|
tc instanceof ContainerType
|
|
)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A lifetime "shared owner" type.
|
|
*
|
|
* A shared owner is type that "owns" another object, and shares that ownership with other owners.
|
|
* Examples include `std::shared_ptr` along with other reference counting types.
|
|
*
|
|
* NOTE (zeal patch): the upstream `Has a field which is a lifetime shared owner
|
|
* type` clause is intentionally omitted. Qt's `QSharedDataPointer` /
|
|
* `QExplicitlySharedDataPointer` themselves satisfy the shared-owner criteria
|
|
* (operator*, user destructor, copy semantics); leaving the field clause in
|
|
* would re-route the same Qt-value-class false positives through
|
|
* `LifetimeSharedOwnerType` -> `LifetimeOwnerType`.
|
|
*/
|
|
class LifetimeSharedOwnerType extends Type {
|
|
LifetimeSharedOwnerType() {
|
|
/*
|
|
* Find all types which can be dereferenced (i.e. have unary * operator), and are therefore
|
|
* likely to be "owner"s or "pointer"s to other objects. We then consider these classes to be
|
|
* shared owners if:
|
|
* - They can be copied (a unique "owner" type would not be copyable)
|
|
* - They can destroyed
|
|
*/
|
|
|
|
// unary * (i.e. can be dereferenced)
|
|
exists(StarOperator mf | mf.getDeclaringType() = this) and
|
|
// "User" provided destructor
|
|
exists(Destructor d |
|
|
d.getDeclaringType() = this and
|
|
not d.isCompilerGenerated()
|
|
) and
|
|
// A copy constructor and copy assignment operator
|
|
exists(CopyConstructor cc | cc.getDeclaringType() = this and not cc.isDeleted()) and
|
|
exists(CopyAssignmentOperator cc | cc.getDeclaringType() = this and not cc.isDeleted())
|
|
or
|
|
// This class is a `SharedOwnerType` if it is constructed from a `SharedOwnerType`. This is
|
|
// important, because templates may not have instantiated all the required member functions
|
|
exists(TemplateClass tc |
|
|
this.(Class).isConstructedFrom(tc) and
|
|
tc instanceof LifetimeSharedOwnerType
|
|
)
|
|
or
|
|
// Any specified version of a shared owner type is also a shared owner type
|
|
this.getUnspecifiedType() instanceof LifetimeSharedOwnerType
|
|
or
|
|
// Derived from a public base class which is a shared owner type
|
|
exists(ClassDerivation cd |
|
|
cd = this.(Class).getADerivation() and
|
|
cd.getBaseClass() instanceof LifetimeSharedOwnerType and
|
|
cd.getASpecifier().hasName("public")
|
|
)
|
|
or
|
|
// Lifetime profile treats the following types as shared owner types, even though they don't
|
|
// fully adhere to the requirements above
|
|
this.(Class).hasQualifiedName("std", "shared_future")
|
|
or
|
|
// Explicit annotation on the type
|
|
this.getAnAttribute().getName().matches("gsl::SharedOwner%")
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An `IteratorType`, based on `[iterator.requirements]` with some adaptions to capture more real
|
|
* world iterators.
|
|
*/
|
|
class IteratorType extends Type {
|
|
IteratorType() {
|
|
// We consider anything with an increment and * operator to be sufficient to be an iterator type
|
|
exists(StarOperator mf |
|
|
mf.getDeclaringType() = this and mf.getType().getUnspecifiedType() instanceof ReferenceType
|
|
) and
|
|
exists(IncrementOperator op |
|
|
op.getDeclaringType() = this and op.getType().(ReferenceType).getBaseType() = this
|
|
)
|
|
or
|
|
// Along with unspecified versions of the types above
|
|
this.getUnspecifiedType() instanceof IteratorType
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A lifetime pointer type.
|
|
*
|
|
* A type which points to another object. For example, `std::unique_ptr`. Includes
|
|
* `LifetimeSharedOwnerType`.
|
|
*
|
|
* NOTE (zeal patch): the upstream `Has a field which is a lifetime pointer type`
|
|
* clause is intentionally omitted — it caused widespread false positives on Qt
|
|
* value classes that hold private d-pointers.
|
|
*/
|
|
class LifetimePointerType extends LifetimeIndirectionType {
|
|
LifetimePointerType() {
|
|
this instanceof IteratorType
|
|
or
|
|
this instanceof PointerType
|
|
or
|
|
this instanceof ReferenceType
|
|
or
|
|
// A shared owner type is a pointer type, but an owner type is not.
|
|
this instanceof LifetimeSharedOwnerType and
|
|
not this instanceof LifetimeOwnerType
|
|
or
|
|
this.(Class).hasQualifiedName("std", "reference_wrapper")
|
|
or
|
|
exists(Class vectorBool, UserType reference |
|
|
vectorBool.hasQualifiedName("std", "vector") and
|
|
vectorBool.getATemplateArgument() instanceof BoolType and
|
|
reference.hasName("reference") and
|
|
reference.getDeclaringType() = vectorBool and
|
|
this = reference.getUnderlyingType()
|
|
)
|
|
or
|
|
// Any specified version of a pointer type is also an owner type
|
|
this.getUnspecifiedType() instanceof LifetimePointerType
|
|
or
|
|
// Derived from a public base class which is a pointer type
|
|
exists(ClassDerivation cd |
|
|
cd = this.(Class).getADerivation() and
|
|
cd.getBaseClass() instanceof LifetimePointerType and
|
|
cd.getASpecifier().hasName("public")
|
|
)
|
|
or
|
|
// Explicit annotation on the type
|
|
this.getAnAttribute().getName().matches("gsl::Pointer%")
|
|
}
|
|
}
|
|
|
|
/** A full expression as defined in [intro.execution] of N3797. */
|
|
class FullExpr extends Expr {
|
|
FullExpr() {
|
|
// A full-expression is not a subexpression
|
|
not this.getParent() instanceof Expr
|
|
or
|
|
// A sub-expression that is an unevaluated operand
|
|
this.isUnevaluated()
|
|
}
|
|
}
|
|
|
|
/** Gets the `FullExpression` scope of the `TemporaryObjectExpr`. */
|
|
FullExpr getTemporaryObjectExprScope(TemporaryObjectExpr toe) {
|
|
result = toe.getUnconverted().getParent*()
|
|
}
|
|
|
|
/**
|
|
* See `LifetimeLocalVariable` and subclasses.
|
|
*/
|
|
private newtype TLifetimeLocalVariable =
|
|
TLocalScopeVariable(LocalScopeVariable lsv) { not lsv.isStatic() } or
|
|
TTemporaryObject(TemporaryObjectExpr toe)
|
|
|
|
/**
|
|
* A "LocalVariable" as defined by the lifetime profile.
|
|
*
|
|
* This includes newly introduced objects with a local scope.
|
|
*/
|
|
class LifetimeLocalVariable extends TLifetimeLocalVariable {
|
|
string toString() { none() } // specified in sub-classes
|
|
|
|
Type getType() { none() }
|
|
}
|
|
|
|
/**
|
|
* A parameter or `LocalVariable`, used as a `LifetimeLocalVariable`
|
|
*/
|
|
class LifetimeLocalScopeVariable extends TLocalScopeVariable, LifetimeLocalVariable {
|
|
LocalScopeVariable getVariable() { this = TLocalScopeVariable(result) }
|
|
|
|
override Type getType() { result = this.getVariable().getType() }
|
|
|
|
override string toString() { result = this.getVariable().toString() }
|
|
}
|
|
|
|
/**
|
|
* A temporary object used as a `LifetimeLocalVariable`.
|
|
*/
|
|
class LifetimeTemporaryObject extends TTemporaryObject, LifetimeLocalVariable {
|
|
TemporaryObjectExpr getTemporaryObjectExpr() { this = TTemporaryObject(result) }
|
|
|
|
override Type getType() { result = this.getTemporaryObjectExpr().getType() }
|
|
|
|
override string toString() { result = this.getTemporaryObjectExpr().toString() }
|
|
}
|
|
|
|
newtype TInvalidReason =
|
|
/** LifetimeLocalVariable is invalid because it hasn't been initialized. */
|
|
TUninitialized(DeclStmt ds, Variable v) { ds.getADeclaration() = v } or
|
|
/** LifetimeLocalVariable is invalid because it points to a variable which has gone out of scope. */
|
|
TVariableOutOfScope(LocalScopeVariable v, ControlFlowNode cfn) { goesOutOfScopeAt(v, cfn) } or
|
|
/** LifetimeLocalVariable is invalid because it points to a temporary object expression which has gone out of scope. */
|
|
TTemporaryOutOfScope(TemporaryObjectExpr toe) or
|
|
/** LifetimeLocalVariable is invalid because it points to data held by an owner which has since been invalidated. */
|
|
TOwnerModified(FunctionCall fc)
|
|
|
|
/**
|
|
* A reason why a pointer may be invalid.
|
|
*/
|
|
class InvalidReason extends TInvalidReason {
|
|
/** Holds if this reason indicates the pointer is accessed before the lifetime of an object began. */
|
|
predicate isBeforeLifetime() { this instanceof TUninitialized }
|
|
|
|
/** Holds if this reason indicates the pointer is accessed after the lifetime of an object has finished. */
|
|
predicate isAfterLifetime() { not this.isBeforeLifetime() }
|
|
|
|
/** Gets a description of the reason why this pointer may be invalid. */
|
|
string getDescription() {
|
|
exists(DeclStmt ds, Variable v |
|
|
this = TUninitialized(ds, v) and
|
|
result = "variable " + v.getName() + " was never initialized"
|
|
)
|
|
or
|
|
exists(LocalScopeVariable v, ControlFlowNode cfn |
|
|
this = TVariableOutOfScope(v, cfn) and
|
|
result = "variable " + v.getName() + " went out of scope"
|
|
)
|
|
or
|
|
exists(TemporaryObjectExpr toe |
|
|
this = TTemporaryOutOfScope(toe) and
|
|
result = "temporary object went out of scope"
|
|
)
|
|
or
|
|
exists(FunctionCall fc |
|
|
this = TOwnerModified(fc) and
|
|
result = "owner type was modified"
|
|
)
|
|
}
|
|
|
|
string toString() { result = this.getDescription() }
|
|
|
|
/** Get an element that explains the reason for the invalid determination. */
|
|
private Element getExplanatoryElement() {
|
|
exists(DeclStmt ds |
|
|
this = TUninitialized(ds, _) and
|
|
result = ds
|
|
)
|
|
or
|
|
exists(ControlFlowNode cfn |
|
|
this = TVariableOutOfScope(_, cfn) and
|
|
result = cfn
|
|
)
|
|
or
|
|
exists(TemporaryObjectExpr toe |
|
|
this = TTemporaryOutOfScope(toe) and
|
|
result = getTemporaryObjectExprScope(toe)
|
|
)
|
|
or
|
|
exists(FunctionCall fc |
|
|
this = TOwnerModified(fc) and
|
|
result = fc
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Provides a `message` for use in alert messages.
|
|
*
|
|
* The message will contain a `$@` placeholder, for which `explanation` and `explanationDesc` are
|
|
* the placeholder components which should be added as extra columns.
|
|
*/
|
|
predicate hasMessage(string message, Element explanation, string explanationDesc) {
|
|
message = "because the " + this.getDescription() + " $@." and
|
|
explanation = this.getExplanatoryElement() and
|
|
explanationDesc = "here"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* A reason why a pointer may be null.
|
|
*/
|
|
newtype TNullReason =
|
|
// Null because the `NullValue` was assigned
|
|
TNullAssignment(NullValue e)
|
|
|
|
class NullReason extends TNullReason {
|
|
/** Gets a description of the reason why this pointer may be null. */
|
|
string getDescription() {
|
|
exists(NullValue nv |
|
|
this = TNullAssignment(nv) and
|
|
result = "null value was assigned"
|
|
)
|
|
}
|
|
|
|
string toString() { result = this.getDescription() }
|
|
}
|
|
|
|
/** See `PSetEntry` and subclasses. */
|
|
newtype TPSetEntry =
|
|
/** Points to a lifetime local variable. */
|
|
PSetVar(LifetimeLocalVariable lv) or
|
|
/** Points to a lifetime local variable that represents an owner type. */
|
|
PSetOwner(LifetimeLocalVariable lv, int level) {
|
|
level = [0 .. 2] and lv.getType() instanceof LifetimeOwnerType
|
|
} or
|
|
/** Points to a global variable. */
|
|
PSetGlobal() or
|
|
/** A null pointer. */
|
|
PSetNull(NullReason nr) or
|
|
/** An invalid pointer, for the given reason. */
|
|
PSetInvalid(InvalidReason ir) or
|
|
/** An unknown pointer. */
|
|
PSetUnknown()
|
|
|
|
/**
|
|
* An entry in the points-to set for a particular "LifetimeLocalVariable" at a particular
|
|
* point in the program.
|
|
*/
|
|
class PSetEntry extends TPSetEntry {
|
|
string toString() {
|
|
exists(LifetimeLocalVariable lv |
|
|
this = PSetVar(lv) and
|
|
result = "Var(" + lv.toString() + ")"
|
|
)
|
|
or
|
|
this = PSetGlobal() and result = "global"
|
|
or
|
|
exists(LifetimeLocalVariable lv, int level |
|
|
this = PSetOwner(lv, level) and
|
|
result = "Owner(" + lv.toString() + "," + level + ")"
|
|
)
|
|
or
|
|
exists(NullReason nr | this = PSetNull(nr) and result = "null because" + nr)
|
|
or
|
|
exists(InvalidReason ir | this = PSetInvalid(ir) and result = "invalid because " + ir)
|
|
or
|
|
this = PSetUnknown() and result = "unknown"
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The "pmap" or "points-to map" for a "lifetime" local variable.
|
|
*/
|
|
predicate pointsToMap(ControlFlowNode cfn, LifetimeLocalVariable lv, PSetEntry ps) {
|
|
if isPSetReassigned(cfn, lv)
|
|
then ps = getAnAssignedPSetEntry(cfn, lv)
|
|
else
|
|
// Exclude unknown for now
|
|
exists(ControlFlowNode pred, PSetEntry prevPSet |
|
|
pred = cfn.getAPredecessor() and
|
|
pointsToMap(pred, lv, prevPSet) and
|
|
// Not PSetNull() and a non-null successor of a null check
|
|
not exists(AnalysedExpr ae |
|
|
ps = PSetNull(_) and
|
|
cfn = ae.getNonNullSuccessor(lv.(LifetimeLocalScopeVariable).getVariable())
|
|
) and
|
|
// lv is not out of scope at this node
|
|
not goesOutOfScopeAt(lv.(LifetimeLocalScopeVariable).getVariable(), cfn)
|
|
|
|
|
// Propagate a PSetEntry from the predecessor node, so long as the
|
|
// PSetEntry is not invalidated at this node
|
|
ps = prevPSet and
|
|
not exists(getAnInvalidation(prevPSet, cfn))
|
|
or
|
|
// Replace prevPSet with an invalidation reason at this node
|
|
ps = getAnInvalidation(prevPSet, cfn)
|
|
)
|
|
}
|
|
|
|
private predicate isPSetReassigned(ControlFlowNode cfn, LifetimeLocalVariable lv) {
|
|
exists(DeclStmt ds |
|
|
cfn = ds and
|
|
ds.getADeclaration() = lv.(LifetimeLocalScopeVariable).getVariable() and
|
|
lv.getType() instanceof PointerType
|
|
)
|
|
or
|
|
exists(TemporaryObjectExpr toe |
|
|
toe = lv.(LifetimeTemporaryObject).getTemporaryObjectExpr() and
|
|
cfn = toe
|
|
)
|
|
or
|
|
// Assigned a value
|
|
cfn = lv.(LifetimeLocalScopeVariable).getVariable().getAnAssignedValue()
|
|
or
|
|
// If the address of a local var is passed to a function, then assume it initializes it
|
|
exists(Call fc, AddressOfExpr aoe |
|
|
cfn = aoe and
|
|
fc.getAnArgument() = aoe and
|
|
lv.(LifetimeLocalScopeVariable).getVariable() = aoe.getOperand().(VariableAccess).getTarget()
|
|
)
|
|
}
|
|
|
|
/** Is the `lv` assigned or reassigned at this ControlFlowNode `cfn`. */
|
|
private PSetEntry getAnAssignedPSetEntry(ControlFlowNode cfn, LifetimeLocalVariable lv) {
|
|
exists(DeclStmt ds |
|
|
cfn = ds and
|
|
ds.getADeclaration() = lv.(LifetimeLocalScopeVariable).getVariable()
|
|
|
|
|
lv.getType() instanceof PointerType and
|
|
result = PSetInvalid(TUninitialized(ds, lv.(LifetimeLocalScopeVariable).getVariable()))
|
|
)
|
|
or
|
|
exists(TemporaryObjectExpr toe |
|
|
toe = lv.(LifetimeTemporaryObject).getTemporaryObjectExpr() and
|
|
cfn = toe and
|
|
result = PSetVar(lv)
|
|
)
|
|
or
|
|
// Assigned a value
|
|
exists(Expr assign |
|
|
assign = lv.(LifetimeLocalScopeVariable).getVariable().getAnAssignedValue() and
|
|
cfn = assign
|
|
|
|
|
if isKnownAssignmentType(assign)
|
|
then knownAssignmentType(assign, result)
|
|
else result = PSetUnknown()
|
|
)
|
|
or
|
|
// If the address of a local var is passed to a function, then assume it initializes it
|
|
exists(Call fc, AddressOfExpr aoe |
|
|
cfn = aoe and
|
|
fc.getAnArgument() = aoe and
|
|
lv.(LifetimeLocalScopeVariable).getVariable() = aoe.getOperand().(VariableAccess).getTarget() and
|
|
result = PSetUnknown()
|
|
)
|
|
}
|
|
|
|
predicate isKnownAssignmentType(Expr assign) {
|
|
assign = any(LocalScopeVariable lv).getAnAssignedValue() and
|
|
(
|
|
exists(Variable v | v = assign.(AddressOfExpr).getOperand().(VariableAccess).getTarget() |
|
|
v instanceof LocalScopeVariable
|
|
or
|
|
v instanceof GlobalVariable
|
|
)
|
|
or
|
|
// Assignment of a previous variable
|
|
exists(VariableAccess va |
|
|
va = assign and
|
|
va.getTarget().(LocalScopeVariable).getType() instanceof LifetimePointerType
|
|
)
|
|
or
|
|
assign instanceof NullValue
|
|
or
|
|
exists(FunctionCall fc |
|
|
assign = fc and
|
|
fc.getNumberOfArguments() = 0 and
|
|
fc.getType() instanceof LifetimePointerType
|
|
|
|
|
// A function call is a product of its inputs (just handle qualifiers at the moment)
|
|
exists(LifetimeLocalVariable lv |
|
|
lv = TTemporaryObject(fc.getQualifier().getConversion())
|
|
or
|
|
lv = TLocalScopeVariable(fc.getQualifier().(VariableAccess).getTarget())
|
|
|
|
|
lv.getType() instanceof LifetimePointerType
|
|
or
|
|
lv.getType() instanceof LifetimeOwnerType
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An expression which is assigned to a `LocalScopeVariable`, which has a known PSet value i.e. not
|
|
* an "Unknown" PSet value.
|
|
*/
|
|
predicate knownAssignmentType(Expr assign, PSetEntry ps) {
|
|
assign = any(LocalScopeVariable lv).getAnAssignedValue() and
|
|
(
|
|
// The assigned value is `&v`
|
|
exists(Variable v | v = assign.(AddressOfExpr).getOperand().(VariableAccess).getTarget() |
|
|
v instanceof LocalScopeVariable and
|
|
(
|
|
// If the variable we are taking the address of is a reference type, then we are really
|
|
// taking the address of whatever the reference type "points-to". Use the `pointsToMap`
|
|
// to determine viable `LifetimeLocalScopeVariable`s this could point to.
|
|
if v.getType() instanceof ReferenceType
|
|
then
|
|
pointsToMap(assign.getAPredecessor(),
|
|
any(LifetimeLocalScopeVariable lv | lv.getVariable() = v), ps)
|
|
else
|
|
// This assignment points-to `v` itself.
|
|
ps = PSetVar(TLocalScopeVariable(v))
|
|
)
|
|
or
|
|
// If the variable we are taking the address of is a reference variable, then this points-to
|
|
// a global. If the variable we taking the address of is a reference type, we need to consider
|
|
// that it might point-to a global, even if it is a LocalScopeVariable (this case is required
|
|
// so that we still produce a result even if the pointsToMap is empty for `lv`).
|
|
(v instanceof GlobalVariable or v.getType() instanceof ReferenceType) and
|
|
ps = PSetGlobal()
|
|
)
|
|
or
|
|
// Assignment of a previous variable
|
|
exists(VariableAccess va |
|
|
va = assign and
|
|
va.getTarget().(LocalScopeVariable).getType() instanceof LifetimePointerType and
|
|
// PSet of that become PSet of this
|
|
pointsToMap(assign.getAPredecessor(),
|
|
any(LifetimeLocalScopeVariable lv | lv.getVariable() = va.getTarget()), ps)
|
|
)
|
|
or
|
|
// The `NullValue` class covers all types of null equivalent expressions. This case also handles
|
|
// default and value initialization, where an "implicit" null value expression is added by the
|
|
// extractor
|
|
assign instanceof NullValue and ps = PSetNull(TNullAssignment(assign))
|
|
or
|
|
exists(FunctionCall fc |
|
|
assign = fc and
|
|
// If the assignment is being converted via a ReferenceDereferenceExpr, then
|
|
// we are essentially copying the original object
|
|
not assign.getFullyConverted() instanceof ReferenceDereferenceExpr and
|
|
fc.getNumberOfArguments() = 0 and
|
|
fc.getType() instanceof LifetimePointerType
|
|
|
|
|
// A function call is a product of its inputs (just handle qualifiers at the moment)
|
|
exists(LifetimeLocalVariable lv |
|
|
lv = TTemporaryObject(fc.getQualifier().getConversion())
|
|
or
|
|
lv = TLocalScopeVariable(fc.getQualifier().(VariableAccess).getTarget())
|
|
|
|
|
ps = PSetVar(lv) and lv.getType() instanceof LifetimePointerType
|
|
or
|
|
ps = PSetOwner(lv, 0) and lv.getType() instanceof LifetimeOwnerType
|
|
)
|
|
)
|
|
)
|
|
}
|
|
|
|
/**
|
|
* Holds if `cfn` is a node that occur directly after the local scope variable `lv` has gone out of scope.
|
|
*/
|
|
predicate goesOutOfScopeAt(LocalScopeVariable lv, ControlFlowNode cfn) {
|
|
exists(BlockStmt scope |
|
|
scope = lv.getParentScope() and
|
|
scope.getAChild+() = cfn.getAPredecessor().getEnclosingStmt() and
|
|
not scope.getAChild+() = cfn.getEnclosingStmt()
|
|
)
|
|
}
|
|
|
|
PSetInvalid getAnInvalidation(PSetEntry ps, ControlFlowNode cfn) {
|
|
exists(LifetimeLocalScopeVariable lv | ps = PSetVar(lv) |
|
|
result = PSetInvalid(TVariableOutOfScope(lv.getVariable(), cfn))
|
|
)
|
|
or
|
|
exists(LifetimeLocalScopeVariable lv | ps = PSetOwner(lv, _) |
|
|
result = PSetInvalid(TVariableOutOfScope(lv.getVariable(), cfn))
|
|
or
|
|
exists(FunctionCall fc |
|
|
fc = cfn and
|
|
fc.getQualifier() = lv.getVariable().getAnAccess() and
|
|
not fc.getTarget() instanceof ConstMemberFunction and
|
|
// non-const versions of begin and end should nevertheless be considered const
|
|
not fc.getTarget().hasName(["begin", "end"]) and
|
|
result = PSetInvalid(TOwnerModified(fc))
|
|
)
|
|
)
|
|
or
|
|
// temporary objects end after the full expression
|
|
exists(LifetimeTemporaryObject lto |
|
|
ps = PSetVar(lto)
|
|
or
|
|
ps = PSetOwner(lto, _)
|
|
|
|
|
cfn = lto.getTemporaryObjectExpr().getUnconverted().getParent*().(FullExpr).getASuccessor() and
|
|
result = PSetInvalid(TTemporaryOutOfScope(lto.getTemporaryObjectExpr()))
|
|
)
|
|
}
|
|
|
|
/**
|
|
* An expression which is dereferenced and may be an "invalid" value.
|
|
*/
|
|
class InvalidDereference extends VariableAccess {
|
|
InvalidReason ir;
|
|
|
|
InvalidDereference() {
|
|
// The local points to map suggests this points to an invalid set
|
|
exists(LocalScopeVariable lv |
|
|
lv = this.getTarget() and
|
|
pointsToMap(this, TLocalScopeVariable(lv), PSetInvalid(ir))
|
|
)
|
|
}
|
|
|
|
/** Gets a reason why this dereference could point to an invalid value. */
|
|
InvalidReason getAnInvalidReason() { result = ir }
|
|
}
|
|
|
|
from
|
|
InvalidDereference e, Element explanation, string explanationDesc, InvalidReason ir,
|
|
string invalidMessage
|
|
where
|
|
ir = e.getAnInvalidReason() and
|
|
ir.isAfterLifetime() and
|
|
ir.hasMessage(invalidMessage, explanation, explanationDesc)
|
|
select e,
|
|
e.(VariableAccess).getTarget().getName() + " is dereferenced here but accesses invalid memory " +
|
|
invalidMessage, explanation, explanationDesc
|