|
11 | 11 | */ |
12 | 12 |
|
13 | 13 | import cpp |
| 14 | +import semmle.code.cpp.valuenumbering.GlobalValueNumbering |
| 15 | +import semmle.code.cpp.controlflow.Guards |
14 | 16 |
|
15 | 17 | /** |
16 | | - * Lookup if condition compare with 0 |
| 18 | + * A C++ `delete` or `delete[]` expression. |
17 | 19 | */ |
18 | | -class IfCompareWithZero extends IfStmt { |
19 | | - IfCompareWithZero() { |
20 | | - this.getCondition().(EQExpr).getAChild().getValue() = "0" |
21 | | - or |
22 | | - this.getCondition().(NEExpr).getAChild().getValue() = "0" and |
23 | | - this.hasElse() |
24 | | - or |
25 | | - this.getCondition().(NEExpr).getAChild().getValue() = "0" and |
26 | | - this.getThen().getAChild*() instanceof ReturnStmt |
| 20 | +class DeleteOrDeleteArrayExpr extends Expr { |
| 21 | + DeleteOrDeleteArrayExpr() { this instanceof DeleteExpr or this instanceof DeleteArrayExpr } |
| 22 | + |
| 23 | + DeallocationFunction getDeallocator() { |
| 24 | + result = [this.(DeleteExpr).getDeallocator(), this.(DeleteArrayExpr).getDeallocator()] |
27 | 25 | } |
28 | 26 | } |
29 | 27 |
|
| 28 | +/** Gets the `Constructor` invoked when `newExpr` allocates memory. */ |
| 29 | +Constructor getConstructorForAllocation(NewOrNewArrayExpr newExpr) { |
| 30 | + result.getACallToThisFunction().getLocation() = newExpr.getLocation() |
| 31 | +} |
| 32 | + |
| 33 | +/** Gets the `Destructor` invoked when `deleteExpr` deallocates memory. */ |
| 34 | +Destructor getDestructorForDeallocation(DeleteOrDeleteArrayExpr deleteExpr) { |
| 35 | + result.getACallToThisFunction().getLocation() = deleteExpr.getLocation() |
| 36 | +} |
| 37 | + |
| 38 | +/** Holds if the evaluation of `newExpr` may throw an exception. */ |
| 39 | +predicate newMayThrow(NewOrNewArrayExpr newExpr) { |
| 40 | + functionMayThrow(newExpr.getAllocator()) or |
| 41 | + functionMayThrow(getConstructorForAllocation(newExpr)) |
| 42 | +} |
| 43 | + |
| 44 | +/** Holds if the evaluation of `deleteExpr` may throw an exception. */ |
| 45 | +predicate deleteMayThrow(DeleteOrDeleteArrayExpr deleteExpr) { |
| 46 | + functionMayThrow(deleteExpr.getDeallocator()) or |
| 47 | + functionMayThrow(getDestructorForDeallocation(deleteExpr)) |
| 48 | +} |
| 49 | + |
30 | 50 | /** |
31 | | - * lookup for calls to `operator new`, with incorrect error handling. |
| 51 | + * Holds if the function may throw an exception when called. That is, if the body of the function looks |
| 52 | + * like it might throw an exception, and the function does not have a `noexcept` or `throw()` specifier. |
32 | 53 | */ |
33 | | -class WrongCheckErrorOperatorNew extends FunctionCall { |
34 | | - Expr exp; |
35 | | - |
36 | | - WrongCheckErrorOperatorNew() { |
37 | | - this = exp.(NewOrNewArrayExpr).getAChild().(FunctionCall) and |
38 | | - ( |
39 | | - this.getTarget().hasGlobalOrStdName("operator new") |
40 | | - or |
41 | | - this.getTarget().hasGlobalOrStdName("operator new[]") |
| 54 | +predicate functionMayThrow(Function f) { |
| 55 | + (not exists(f.getBlock()) or stmtMayThrow(f.getBlock())) and |
| 56 | + not f.isNoExcept() and |
| 57 | + not f.isNoThrow() |
| 58 | +} |
| 59 | + |
| 60 | +/** Holds if the evaluation of `stmt` may throw an exception. */ |
| 61 | +predicate stmtMayThrow(Stmt stmt) { |
| 62 | + stmtMayThrow(stmt.(BlockStmt).getAStmt()) |
| 63 | + or |
| 64 | + convertedExprMayThrow(stmt.(ExprStmt).getExpr()) |
| 65 | + or |
| 66 | + exists(IfStmt ifStmt | ifStmt = stmt | |
| 67 | + convertedExprMayThrow(ifStmt.getCondition()) or |
| 68 | + stmtMayThrow([ifStmt.getThen(), ifStmt.getElse()]) |
| 69 | + ) |
| 70 | + or |
| 71 | + exists(Loop loop | loop = stmt | |
| 72 | + convertedExprMayThrow(loop.getCondition()) or |
| 73 | + stmtMayThrow(loop.getStmt()) |
| 74 | + ) |
| 75 | +} |
| 76 | + |
| 77 | +/** Holds if the evaluation of `e` (including conversions) may throw an exception. */ |
| 78 | +predicate convertedExprMayThrow(Expr e) { exprMayThrow(e.getFullyConverted()) } |
| 79 | + |
| 80 | +/** Holds if the evaluation of `e` may throw an exception. */ |
| 81 | +predicate exprMayThrow(Expr e) { |
| 82 | + e instanceof DynamicCast |
| 83 | + or |
| 84 | + e instanceof TypeidOperator |
| 85 | + or |
| 86 | + e instanceof ThrowExpr |
| 87 | + or |
| 88 | + newMayThrow(e) |
| 89 | + or |
| 90 | + deleteMayThrow(e) |
| 91 | + or |
| 92 | + convertedExprMayThrow(e.(UnaryOperation).getOperand()) |
| 93 | + or |
| 94 | + exists(BinaryOperation binOp | binOp = e | |
| 95 | + convertedExprMayThrow([binOp.getLeftOperand(), binOp.getRightOperand()]) |
| 96 | + ) |
| 97 | + or |
| 98 | + exists(CommaExpr comma | comma = e | |
| 99 | + convertedExprMayThrow([comma.getLeftOperand(), comma.getRightOperand()]) |
| 100 | + ) |
| 101 | + or |
| 102 | + exists(StmtExpr stmtExpr | stmtExpr = e | |
| 103 | + convertedExprMayThrow(stmtExpr.getResultExpr()) or |
| 104 | + stmtMayThrow(stmtExpr.getStmt()) |
| 105 | + ) |
| 106 | + or |
| 107 | + convertedExprMayThrow(e.(Conversion).getExpr()) |
| 108 | + or |
| 109 | + exists(FunctionCall fc | fc = e | |
| 110 | + not exists(fc.getTarget()) or |
| 111 | + functionMayThrow(fc.getTarget()) or |
| 112 | + convertedExprMayThrow(fc.getAnArgument()) |
| 113 | + ) |
| 114 | +} |
| 115 | + |
| 116 | +/** An allocator that will not throw an exception. */ |
| 117 | +class NoThrowAllocator extends Function { |
| 118 | + NoThrowAllocator() { |
| 119 | + exists(NewOrNewArrayExpr newExpr | |
| 120 | + newExpr.getAllocator() = this and |
| 121 | + not functionMayThrow(this) |
42 | 122 | ) |
43 | 123 | } |
| 124 | +} |
44 | 125 |
|
45 | | - /** |
46 | | - * Holds if handler `try ... catch` exists. |
47 | | - */ |
48 | | - predicate isExistsTryCatchBlock() { |
49 | | - exists(TryStmt ts | this.getEnclosingStmt() = ts.getStmt().getAChild*()) |
50 | | - } |
| 126 | +/** An allocator that might throw an exception. */ |
| 127 | +class ThrowingAllocator extends Function { |
| 128 | + ThrowingAllocator() { not this instanceof NoThrowAllocator } |
| 129 | +} |
51 | 130 |
|
52 | | - /** |
53 | | - * Holds if results call `operator new` check in `operator if`. |
54 | | - */ |
55 | | - predicate isExistsIfCondition() { |
56 | | - exists(IfCompareWithZero ifc, AssignExpr aex, Initializer it | |
57 | | - // call `operator new` directly from the condition of `operator if`. |
58 | | - this = ifc.getCondition().getAChild*() |
59 | | - or |
60 | | - // check results call `operator new` with variable appropriation |
61 | | - postDominates(ifc, this) and |
62 | | - aex.getAChild() = exp and |
63 | | - ifc.getCondition().getAChild().(VariableAccess).getTarget() = |
64 | | - aex.getLValue().(VariableAccess).getTarget() |
65 | | - or |
66 | | - // check results call `operator new` with declaration variable |
67 | | - postDominates(ifc, this) and |
68 | | - exp = it.getExpr() and |
69 | | - it.getDeclaration() = ifc.getCondition().getAChild().(VariableAccess).getTarget() |
70 | | - ) |
| 131 | +/** The `std::bad_alloc` exception and its `bsl` variant. */ |
| 132 | +class BadAllocType extends Class { |
| 133 | + BadAllocType() { this.hasGlobalOrStdOrBslName("bad_alloc") } |
| 134 | +} |
| 135 | + |
| 136 | +/** |
| 137 | + * A catch block that catches a `std::bad_alloc` (or any of its subclasses), or a catch |
| 138 | + * block that catches every exception (i.e., `catch(...)`). |
| 139 | + */ |
| 140 | +class BadAllocCatchBlock extends CatchBlock { |
| 141 | + BadAllocCatchBlock() { |
| 142 | + this.getParameter().getUnspecifiedType() = any(BadAllocType badAlloc).getADerivedClass*() |
| 143 | + or |
| 144 | + not exists(this.getParameter()) |
71 | 145 | } |
| 146 | +} |
| 147 | + |
| 148 | +/** |
| 149 | + * Holds if `newExpr` will not throw an exception, but is embedded in a `try` statement |
| 150 | + * with a catch block `catchBlock` that catches an `std::bad_alloc` exception. |
| 151 | + */ |
| 152 | +predicate noThrowInTryBlock(NewOrNewArrayExpr newExpr, BadAllocCatchBlock catchBlock) { |
| 153 | + exists(TryStmt try | |
| 154 | + forall(Expr cand | cand.getEnclosingBlock().getEnclosingBlock*() = try.getStmt() | |
| 155 | + not convertedExprMayThrow(cand) |
| 156 | + ) and |
| 157 | + try.getACatchClause() = catchBlock and |
| 158 | + newExpr.getEnclosingBlock().getEnclosingBlock*() = try.getStmt() and |
| 159 | + not newMayThrow(newExpr) |
| 160 | + ) |
| 161 | +} |
72 | 162 |
|
73 | | - /** |
74 | | - * Holds if `(std::nothrow)` or `(std::noexcept)` exists in call `operator new`. |
75 | | - */ |
76 | | - predicate isExistsNothrow() { getTarget().isNoExcept() or getTarget().isNoThrow() } |
| 163 | +/** |
| 164 | + * Holds if `newExpr` is handles allocation failures by throwing an exception, yet |
| 165 | + * the guard condition `guard` compares the result of `newExpr` to a null value. |
| 166 | + */ |
| 167 | +predicate nullCheckInThrowingNew(NewOrNewArrayExpr newExpr, GuardCondition guard) { |
| 168 | + newExpr.getAllocator() instanceof ThrowingAllocator and |
| 169 | + ( |
| 170 | + // Handles null comparisons. |
| 171 | + guard.ensuresEq(globalValueNumber(newExpr).getAnExpr(), any(NullValue null), _, _, _) |
| 172 | + or |
| 173 | + // Handles `if(ptr)` and `if(!ptr)` cases. |
| 174 | + guard = globalValueNumber(newExpr).getAnExpr() |
| 175 | + ) |
77 | 176 | } |
78 | 177 |
|
79 | | -from WrongCheckErrorOperatorNew op |
| 178 | +from NewOrNewArrayExpr newExpr, Element element, string msg, string elementString |
80 | 179 | where |
81 | | - // use call `operator new` with `(std::nothrow)` and checking error using `try ... catch` block and not `operator if` |
82 | | - op.isExistsNothrow() and not op.isExistsIfCondition() and op.isExistsTryCatchBlock() |
83 | | - or |
84 | | - // use call `operator new` without `(std::nothrow)` and checking error using `operator if` and not `try ... catch` block |
85 | | - not op.isExistsNothrow() and not op.isExistsTryCatchBlock() and op.isExistsIfCondition() |
86 | | -select op, "memory allocation error check is incorrect or missing" |
| 180 | + not newExpr.isFromUninstantiatedTemplate(_) and |
| 181 | + ( |
| 182 | + noThrowInTryBlock(newExpr, element) and |
| 183 | + msg = "This allocation cannot throw. $@ is unnecessary." and |
| 184 | + elementString = "This catch block" |
| 185 | + or |
| 186 | + nullCheckInThrowingNew(newExpr, element) and |
| 187 | + msg = "This allocation cannot return null. $@ is unnecessary." and |
| 188 | + elementString = "This check" |
| 189 | + ) |
| 190 | +select newExpr, msg, element, elementString |
0 commit comments