|
| 1 | +/** |
| 2 | + * Provides classes and predicates for approximating where compiler-generated |
| 3 | + * destructor calls should be placed. This file can be removed when the |
| 4 | + * extractor produces this information directly. |
| 5 | + */ |
| 6 | +private import cpp |
| 7 | + |
| 8 | +private predicate isDeleteDestructorCall(DestructorCall c) { |
| 9 | + exists(DeleteExpr del | c = del.getDestructorCall()) |
| 10 | + or |
| 11 | + exists(DeleteArrayExpr del | c = del.getDestructorCall()) |
| 12 | +} |
| 13 | + |
| 14 | +// Things we know about these calls |
| 15 | +// - isCompilerGenerated() always holds |
| 16 | +// - They all have a predecessor of type VariableAccess |
| 17 | +// - They all have successors |
| 18 | +// - After subtracting all the jumpy dtor calls for a particular variable |
| 19 | +// (PrematureScopeExitNode), there's at most one call left, and that will be |
| 20 | +// the ordinary one. |
| 21 | +// - Except for ConditionDeclExpr! One chain should be directly connected to |
| 22 | +// the false edge out of the parent, and the other should not. |
| 23 | +class SyntheticDestructorCall extends FunctionCall { |
| 24 | + SyntheticDestructorCall() { |
| 25 | + ( |
| 26 | + this instanceof DestructorCall |
| 27 | + or |
| 28 | + // Workaround for CPP-320 |
| 29 | + exists(Function target | |
| 30 | + target = this.(FunctionCall).getTarget() and |
| 31 | + not exists(target.getName()) |
| 32 | + ) |
| 33 | + ) and |
| 34 | + not exists(this.getParent()) and |
| 35 | + not isDeleteDestructorCall(this) and |
| 36 | + not this.isUnevaluated() and |
| 37 | + this.isCompilerGenerated() |
| 38 | + } |
| 39 | + |
| 40 | + VariableAccess getAccess() { successors(result, this) } |
| 41 | + |
| 42 | + SyntheticDestructorCall getNext() { successors(this, result.getAccess()) } |
| 43 | + |
| 44 | + SyntheticDestructorCall getPrev() { this = result.getNext() } |
| 45 | +} |
| 46 | + |
| 47 | +// Things we know about these blocks |
| 48 | +// - If they follow a JumpStmt, the VariableAccesses of their calls never |
| 49 | +// have multiple predecessors. |
| 50 | +// - But after ReturnStmt, that may happen. |
| 51 | +/** |
| 52 | + * Describes a straight line of `SyntheticDestructorCall`s. Node that such |
| 53 | + * lines can share tails. |
| 54 | + */ |
| 55 | +private class SyntheticDestructorBlock extends ControlFlowNodeBase { |
| 56 | + SyntheticDestructorBlock() { |
| 57 | + this = any(SyntheticDestructorCall call | |
| 58 | + not exists(call.getPrev()) |
| 59 | + or |
| 60 | + exists(ControlFlowNodeBase pred | |
| 61 | + not pred instanceof SyntheticDestructorCall and |
| 62 | + successors(pred, call.getAccess()) |
| 63 | + ) |
| 64 | + ) |
| 65 | + } |
| 66 | + |
| 67 | + SyntheticDestructorCall getCall(int i) { |
| 68 | + i = 0 and result = this |
| 69 | + or |
| 70 | + result = this.getCall(i - 1).getNext() |
| 71 | + } |
| 72 | + |
| 73 | + ControlFlowNode getAPredecessor() { |
| 74 | + successors(result, this.(SyntheticDestructorCall).getAccess()) and |
| 75 | + not result instanceof SyntheticDestructorCall |
| 76 | + } |
| 77 | + |
| 78 | + ControlFlowNode getSuccessor() { |
| 79 | + successors(this.getCall(max(int i | exists(this.getCall(i)))), result) |
| 80 | + } |
| 81 | +} |
| 82 | + |
| 83 | +private class PrematureScopeExitNode extends ControlFlowNodeBase { |
| 84 | + PrematureScopeExitNode() { |
| 85 | + this instanceof JumpStmt |
| 86 | + or |
| 87 | + this instanceof Handler |
| 88 | + or |
| 89 | + this instanceof ThrowExpr |
| 90 | + or |
| 91 | + this instanceof ReturnStmt |
| 92 | + or |
| 93 | + this instanceof MicrosoftTryExceptStmt |
| 94 | + or |
| 95 | + // Detecting exception edges out of a MicrosoftTryExceptStmt is not |
| 96 | + // implemented. It may not be easy to do. It'll be something like finding |
| 97 | + // the first synthetic destructor call that crosses out of the scope of the |
| 98 | + // statement and does not belong to some other `PrematureScopeExitNode`. |
| 99 | + // Note that the exception destructors after __try can follow right after |
| 100 | + // ordinary cleanup from the __finally block. |
| 101 | + this instanceof MicrosoftTryFinallyStmt |
| 102 | + } |
| 103 | + |
| 104 | + SyntheticDestructorBlock getSyntheticDestructorBlock() { |
| 105 | + result.getAPredecessor() = this |
| 106 | + or |
| 107 | + // StmtExpr not handled properly here. |
| 108 | + result.getAPredecessor().(Expr).getParent+() = this.(ReturnStmt) |
| 109 | + or |
| 110 | + // Only handles post-order conditions. Won't work with |
| 111 | + // short-circuiting operators. |
| 112 | + falsecond_base(this.(MicrosoftTryExceptStmt).getCondition(), |
| 113 | + result.(SyntheticDestructorCall).getAccess()) |
| 114 | + } |
| 115 | +} |
| 116 | + |
| 117 | +private class DestructedVariable extends LocalScopeVariable { |
| 118 | + DestructedVariable() { |
| 119 | + exists(SyntheticDestructorCall call | call.getAccess().getTarget() = this) |
| 120 | + } |
| 121 | + |
| 122 | + /** |
| 123 | + * Gets the single destructor call that that corresponds to falling off the |
| 124 | + * end of the scope of this variable. |
| 125 | + */ |
| 126 | + SyntheticDestructorCall getOrdinaryCall() { |
| 127 | + exists(SyntheticDestructorBlock block | |
| 128 | + block.getCall(_) = result and |
| 129 | + not exists(PrematureScopeExitNode exit | exit.getSyntheticDestructorBlock() = block) and |
| 130 | + result.getAccess().getTarget() = this |
| 131 | + | |
| 132 | + falsecond_base(getDeclaringLoop().getCondition(), block.getCall(0).getAccess()) |
| 133 | + or |
| 134 | + not exists(this.getDeclaringLoop()) |
| 135 | + ) |
| 136 | + } |
| 137 | + |
| 138 | + predicate hasPositionInScope(int x, int y, Stmt scope) { |
| 139 | + exists(DeclStmt declStmt | |
| 140 | + this = declStmt.getDeclaration(y) and |
| 141 | + declStmt = scope.getChild(x) |
| 142 | + ) |
| 143 | + or |
| 144 | + exists(ConditionDeclExpr decl | |
| 145 | + this = decl.getVariable() and |
| 146 | + // These coordinates are chosen to place the destruction correctly |
| 147 | + // relative to the destruction of other variables declared in |
| 148 | + // `decl.getParent()`. Only a `for` loop can have other declarations in |
| 149 | + // it. These show up as a `DeclStmt` with `x = 0`, so by choosing `x = 1` |
| 150 | + // here we get the `ConditionDeclExpr` placed after all variables |
| 151 | + // declared in the init statement of the `for` loop. |
| 152 | + x = 1 and |
| 153 | + y = 0 and |
| 154 | + scope = decl.getParent() |
| 155 | + ) |
| 156 | + or |
| 157 | + exists(CatchBlock cb | |
| 158 | + this = cb.getParameter() and |
| 159 | + scope = cb and |
| 160 | + // A `CatchBlock` is a `Block`, so there might be other variables |
| 161 | + // declared in it. These coordinates are chosen to place the Parameter |
| 162 | + // before any such declarations. |
| 163 | + x = -1 and |
| 164 | + y = 0 |
| 165 | + ) |
| 166 | + } |
| 167 | + |
| 168 | + SyntheticDestructorCall getInnerScopeCall() { |
| 169 | + exists(SyntheticDestructorBlock block | |
| 170 | + block.getCall(_) = result and |
| 171 | + not exists(PrematureScopeExitNode exit | exit.getSyntheticDestructorBlock() = block) and |
| 172 | + result.getAccess().getTarget() = this |
| 173 | + | |
| 174 | + exists(Loop loop | loop = this.getDeclaringLoop() | |
| 175 | + not falsecond_base(loop.getCondition(), block.getCall(0).getAccess()) |
| 176 | + ) |
| 177 | + ) |
| 178 | + } |
| 179 | + |
| 180 | + predicate hasPositionInInnerScope(int x, int y, ControlFlowNodeBase scope) { |
| 181 | + exists(ConditionDeclExpr decl | |
| 182 | + this = decl.getVariable() and |
| 183 | + // These coordinates are chosen to place the destruction correctly |
| 184 | + // relative to the destruction of other variables in `scope`. Only in the |
| 185 | + // `while` case can there be other variables in `scope`, and in that case |
| 186 | + // `scope` will be a `Block`, whose smallest `x` coordinate can be 0. |
| 187 | + x = -1 and |
| 188 | + y = 0 and |
| 189 | + ( |
| 190 | + scope = decl.getParent().(ForStmt).getUpdate() |
| 191 | + or |
| 192 | + scope = decl.getParent().(WhileStmt).getStmt() |
| 193 | + ) |
| 194 | + ) |
| 195 | + } |
| 196 | + |
| 197 | + private Loop getDeclaringLoop() { |
| 198 | + exists(ConditionDeclExpr decl | this = decl.getVariable() and result = decl.getParent()) |
| 199 | + } |
| 200 | +} |
| 201 | + |
| 202 | +/** |
| 203 | + * Gets the `index`'th synthetic destructor call that should follow `node`. The |
| 204 | + * exact placement of that call in the CFG depends on the type of `node` as |
| 205 | + * follows: |
| 206 | + * |
| 207 | + * - `Block`: after ordinary control flow falls off the end of the block |
| 208 | + * without jumps or exceptions. |
| 209 | + * - `ReturnStmt`: After the statement itself or after its operand (if |
| 210 | + * present). |
| 211 | + * - `ThrowExpr`: After the `throw` expression or after its operand (if |
| 212 | + * present). |
| 213 | + * - `JumpStmt` (`BreakStmt`, `ContinueStmt`, `GotoStmt`): after the statement. |
| 214 | + * - A `ForStmt`, `WhileStmt`, `SwitchStmt`, or `IfStmt`: after control flow |
| 215 | + * falls off the end of the statement without jumping. Destruction can occur |
| 216 | + * here for `for`-loops that have an initializer (`for (C x = a; ...; ...)`) |
| 217 | + * and for statements whose condition is a `ConditionDeclExpr` |
| 218 | + * (`if (C x = a)`). |
| 219 | + * - The `getUpdate()` of a `ForStmt`: after the `getUpdate()` expression. This |
| 220 | + * can happen when the condition is a `ConditionDeclExpr` |
| 221 | + * - `Handler`: On the edge out of the `Handler` for the case where the |
| 222 | + * exception was not matched and is propagated to the next handler or |
| 223 | + * function exit point. |
| 224 | + * - `MicrosoftTryExceptStmt`: After the false-edge out of the `e` in |
| 225 | + * `__except(e)`, before propagating the exception up to the next handler or |
| 226 | + * function exit point. |
| 227 | + * - `MicrosoftTryFinallyStmt`: On the edge following the `__finally` block for |
| 228 | + * the case where an exception was thrown and needs to be propagated. |
| 229 | + */ |
| 230 | +SyntheticDestructorCall getDestructorCallAfterNode(ControlFlowNodeBase node, int index) { |
| 231 | + result = rank[index + 1](SyntheticDestructorCall call, DestructedVariable var, int x, int y | |
| 232 | + call = var.getOrdinaryCall() and |
| 233 | + var.hasPositionInScope(x, y, node) |
| 234 | + or |
| 235 | + call = var.getInnerScopeCall() and |
| 236 | + var.hasPositionInInnerScope(x, y, node) |
| 237 | + | |
| 238 | + call |
| 239 | + order by |
| 240 | + x desc, y desc |
| 241 | + ) |
| 242 | + or |
| 243 | + exists(SyntheticDestructorBlock block | |
| 244 | + node.(PrematureScopeExitNode).getSyntheticDestructorBlock() = block and |
| 245 | + result = block.getCall(index) |
| 246 | + ) |
| 247 | +} |
0 commit comments