forked from github/codeql-coding-standards
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathConstructorErrorLeavesObjectInInvalidState.ql
More file actions
157 lines (139 loc) · 5.82 KB
/
ConstructorErrorLeavesObjectInInvalidState.ql
File metadata and controls
157 lines (139 loc) · 5.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* @id cpp/autosar/constructor-error-leaves-object-in-invalid-state
* @name A15-2-2: Constructors which fail initialization should deallocate the object's resources and throw an exception
* @description If a constructor is not noexcept and the constructor cannot finish object
* initialization, then it shall deallocate the object's resources and it shall throw
* an exception.
* @kind path-problem
* @precision medium
* @problem.severity error
* @tags external/autosar/id/a15-2-2
* correctness
* external/autosar/allocated-target/implementation
* external/autosar/enforcement/partially-automated
* external/autosar/obligation/required
*/
import cpp
import semmle.code.cpp.dataflow.new.DataFlow
import codingstandards.cpp.autosar
import codingstandards.cpp.exceptions.ExceptionFlow
import codingstandards.cpp.exceptions.ExceptionSpecifications
import ExceptionPathGraph
predicate functionMayThrow(Function f) {
not f.isNoExcept() and
not f.isNoThrow()
}
/** An expression which, directly or indirectly, calls `new`. */
class NewAllocationExpr extends OriginThrowingExpr {
NewAllocationExpr() {
this instanceof NewExpr and
functionMayThrow(this.(NewExpr).getAllocator())
or
this.(FunctionCall).getTarget() instanceof NewWrapperFunction
}
override ExceptionType getAnExceptionType() { result instanceof StdBadAlloc }
}
/**
* A function which "wraps" a call to a `NewWrapperExpr`, and returns the
* result.
*/
class NewWrapperFunction extends Function {
NewWrapperFunction() {
exists(DataFlow::Node alloc | alloc.asExpr() instanceof NewAllocationExpr |
exists(ReturnStmt rs | DataFlow::localFlow(alloc, DataFlow::exprNode(rs.getExpr()))) and
alloc.getFunction() = this
)
}
}
/** An expression on which `delete` is called, directly or indirectly. */
class DeletedExpr extends Expr {
pragma[noinline, nomagic]
DeletedExpr() {
this = any(DeleteExpr deleteExpr).getExpr()
or
exists(DeleteWrapperFunction dwf, FunctionCall call |
this = call.getArgument(dwf.getADeleteParameter().getIndex()) and
call.getTarget() = dwf
)
}
}
/**
* A function which "wraps" a call to a `DeletedExpr`, and returns the result.
*/
class DeleteWrapperFunction extends Function {
Parameter p;
DeleteWrapperFunction() {
DataFlow::localFlow(DataFlow::parameterNode(getAParameter()),
DataFlow::exprNode(any(DeletedExpr dwe)))
}
Parameter getADeleteParameter() { result = p }
}
class ExceptionThrowingConstructor extends ExceptionThrowingFunction, Constructor {
ExceptionThrowingConstructor() {
exists(getAFunctionThrownType(this, _)) and
// The constructor is within the users source code
exists(getFile().getRelativePath())
}
}
class ExceptionThrownInConstructor extends ExceptionThrowingExpr {
Constructor c;
ExceptionThrownInConstructor() {
exists(getAFunctionThrownType(c, this)) and
// The constructor is within the users source code
exists(c.getFile().getRelativePath())
}
Constructor getConstructor() { result = c }
}
module NewDeleteConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof NewAllocationExpr }
predicate isSink(DataFlow::Node sink) { sink.asExpr() instanceof DeletedExpr }
DataFlow::FlowFeature getAFeature() {
result instanceof DataFlow::FeatureEqualSourceSinkCallContext
}
}
module NewDeleteFlow = DataFlow::Global<NewDeleteConfig>;
from
ExceptionThrowingConstructor c, ExceptionThrownInConstructor throwingExpr,
NewAllocationExpr newExpr, ExceptionFlowNode exceptionSource,
ExceptionFlowNode throwingExprFlowNode, ExceptionFlowNode reportingNode
where
not isExcluded(c, Exceptions2Package::constructorErrorLeavesObjectInInvalidStateQuery()) and
not isNoExceptTrue(c) and
// Constructor must exit with an exception
c = throwingExpr.getConstructor() and
throwingExpr.hasExceptionFlowReflexive(exceptionSource, throwingExprFlowNode, _) and
exists(ExceptionFlowNode mid |
edges*(exceptionSource, mid) and
newExpr.getASuccessor+() = mid.asThrowingExpr() and
edges*(mid, throwingExprFlowNode) and
not exists(ExceptionFlowNode prior | edges(prior, mid) |
prior.asCatchBlock().getEnclosingFunction() = c
)
) and
// New expression is in the constructor
c = newExpr.getEnclosingFunction() and
// Exception is thrown which directly leaves the function, and
// occurs after the new expression Note: if an exception is caught
// and re-thrown, this is the re-throw
newExpr.getASuccessor+() = throwingExpr and
// No delete of the new'd memory between allocation and
// exception escape
not exists(DeletedExpr deletedExpr |
deletedExpr.getEnclosingFunction() = c and
// Deletes the same memory location that was new'd
NewDeleteFlow::flow(DataFlow::exprNode(newExpr), DataFlow::exprNode(deletedExpr)) and
newExpr.getASuccessor+() = deletedExpr and
deletedExpr.getASuccessor+() = throwingExpr
) and
// In CodeQL CLI 2.12.7 there is a bug which causes an infinite loop during results interpretation
// when a result includes more than maxPaths paths and also includes a path with no edges i.e.
// where the source and sink node are the same.
// To avoid this edge case, if we report a path where the source and sink are the same (i.e the
// throwingExpr directly throws an exception), we adjust the sink node to report the constructor,
// which creates a one step path from the throwingExprFlowNode to the constructor node.
if throwingExprFlowNode = exceptionSource
then reportingNode.asFunction() = c and edges(throwingExprFlowNode, reportingNode)
else reportingNode = throwingExprFlowNode
select c, exceptionSource, reportingNode, "Constructor throws $@ and allocates memory at $@",
throwingExpr, throwingExpr.(ThrowingExpr).getAnExceptionType().getExceptionName(), newExpr,
"alloc"