|
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