|
| 1 | +/** |
| 2 | + * @name Useless assignment to property |
| 3 | + * @description An assignment to a property whose value is always overwritten has no effect. |
| 4 | + * @kind problem |
| 5 | + * @problem.severity warning |
| 6 | + * @id js/useless-assignment-to-property |
| 7 | + * @tags maintainability |
| 8 | + * @precision high |
| 9 | + */ |
| 10 | + |
| 11 | +import javascript |
| 12 | +import Expressions.DOMProperties |
| 13 | +import DeadStore |
| 14 | + |
| 15 | +/** |
| 16 | + * Holds if `write` writes to property `name` of `base`, and `base` is the only base object of `write`. |
| 17 | + */ |
| 18 | +predicate unambiguousPropWrite(DataFlow::SourceNode base, string name, DataFlow::PropWrite write) { |
| 19 | + write = base.getAPropertyWrite(name) and |
| 20 | + not exists (DataFlow::SourceNode otherBase | |
| 21 | + otherBase != base and |
| 22 | + write = otherBase.getAPropertyWrite(name) |
| 23 | + ) |
| 24 | +} |
| 25 | + |
| 26 | +/** |
| 27 | + * Holds if `assign1` and `assign2` both assign property `name` of the same object, and `assign2` post-dominates `assign1`. |
| 28 | + */ |
| 29 | +predicate postDominatedPropWrite(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) { |
| 30 | + exists (ControlFlowNode write1, ControlFlowNode write2, DataFlow::SourceNode base, ReachableBasicBlock block1, ReachableBasicBlock block2 | |
| 31 | + write1 = assign1.getWriteNode() and |
| 32 | + write2 = assign2.getWriteNode() and |
| 33 | + block1 = write1.getBasicBlock() and |
| 34 | + block2 = write2.getBasicBlock() and |
| 35 | + unambiguousPropWrite(base, name, assign1) and |
| 36 | + unambiguousPropWrite(base, name, assign2) and |
| 37 | + block2.postDominates(block1) and |
| 38 | + (block1 = block2 implies |
| 39 | + exists (int i1, int i2 | |
| 40 | + write1 = block1.getNode(i1) and |
| 41 | + write2 = block2.getNode(i2) and |
| 42 | + i1 < i2 |
| 43 | + ) |
| 44 | + ) |
| 45 | + ) |
| 46 | +} |
| 47 | + |
| 48 | +/** |
| 49 | + * Holds if `e` may access a property named `name`. |
| 50 | + */ |
| 51 | +bindingset[name] |
| 52 | +predicate maybeAccessesProperty(Expr e, string name) { |
| 53 | + (e.(PropAccess).getPropertyName() = name and e instanceof RValue) or |
| 54 | + // conservatively reject all side-effects |
| 55 | + e.isImpure() |
| 56 | +} |
| 57 | + |
| 58 | +/** |
| 59 | + * Holds if `assign1` and `assign2` both assign property `name`, but `assign1` is dead because of `assign2`. |
| 60 | + */ |
| 61 | +predicate isDeadAssignment(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) { |
| 62 | + postDominatedPropWrite(name, assign1, assign2) and |
| 63 | + noPropAccessBetween(name, assign1, assign2) and |
| 64 | + not isDOMProperty(name) |
| 65 | +} |
| 66 | + |
| 67 | +/** |
| 68 | + * Holds if `assign` assigns a property `name` that may be accessed somewhere else in the same block, |
| 69 | + * `after` indicates if the access happens before or after the node for `assign`. |
| 70 | + */ |
| 71 | +bindingset[name] |
| 72 | +predicate maybeAccessesAssignedPropInBlock(string name, DataFlow::PropWrite assign, boolean after) { |
| 73 | + exists (ControlFlowNode write, ReachableBasicBlock block, int i, int j, Expr e | |
| 74 | + write = assign.getWriteNode() and |
| 75 | + block = assign.getBasicBlock() and |
| 76 | + write = block.getNode(i) and |
| 77 | + e = block.getNode(j) and |
| 78 | + maybeAccessesProperty(e, name) | |
| 79 | + after = true and i < j |
| 80 | + or |
| 81 | + after = false and j < i |
| 82 | + ) |
| 83 | +} |
| 84 | + |
| 85 | +/** |
| 86 | + * Holds if `assign1` and `assign2` both assign property `name`, and the assigned property is not accessed between the two assignments. |
| 87 | + */ |
| 88 | +bindingset[name] |
| 89 | +predicate noPropAccessBetween(string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2) { |
| 90 | + exists (ControlFlowNode write1, ControlFlowNode write2, ReachableBasicBlock block1, ReachableBasicBlock block2 | |
| 91 | + write1 = assign1.getWriteNode() and |
| 92 | + write2 = assign2.getWriteNode() and |
| 93 | + write1.getBasicBlock() = block1 and |
| 94 | + write2.getBasicBlock() = block2 and |
| 95 | + if block1 = block2 then |
| 96 | + // same block: check for access between |
| 97 | + not exists (int i1, Expr mid, int i2 | |
| 98 | + assign1.getWriteNode() = block1.getNode(i1) and |
| 99 | + assign2.getWriteNode() = block2.getNode(i2) and |
| 100 | + mid = block1.getNode([i1+1..i2-1]) and |
| 101 | + maybeAccessesProperty(mid, name) |
| 102 | + ) |
| 103 | + else |
| 104 | + // other block: |
| 105 | + not ( |
| 106 | + // check for an access after the first write node |
| 107 | + maybeAccessesAssignedPropInBlock(name, assign1, true) or |
| 108 | + // check for an access between the two write blocks |
| 109 | + exists (ReachableBasicBlock mid | |
| 110 | + block1.getASuccessor+() = mid and |
| 111 | + mid.getASuccessor+() = block2 | |
| 112 | + maybeAccessesProperty(mid.getANode(), name) |
| 113 | + ) or |
| 114 | + // check for an access before the second write node |
| 115 | + maybeAccessesAssignedPropInBlock(name, assign2, false) |
| 116 | + ) |
| 117 | + ) |
| 118 | +} |
| 119 | + |
| 120 | +from string name, DataFlow::PropWrite assign1, DataFlow::PropWrite assign2 |
| 121 | +where isDeadAssignment(name, assign1, assign2) and |
| 122 | + // whitelist |
| 123 | + not ( |
| 124 | + // Google Closure Compiler pattern: `o.p = o['p'] = v` |
| 125 | + exists (PropAccess p1, PropAccess p2 | |
| 126 | + p1 = assign1.getAstNode() and |
| 127 | + p2 = assign2.getAstNode() | |
| 128 | + p1 instanceof DotExpr and p2 instanceof IndexExpr |
| 129 | + or |
| 130 | + p2 instanceof DotExpr and p1 instanceof IndexExpr |
| 131 | + ) |
| 132 | + or |
| 133 | + // don't flag overwrites for default values |
| 134 | + isDefaultInit(assign1.getRhs().asExpr().getUnderlyingValue()) |
| 135 | + or |
| 136 | + // don't flag assignments in externs |
| 137 | + assign1.getAstNode().inExternsFile() |
| 138 | + or |
| 139 | + // exclude result from js/overwritten-property |
| 140 | + assign2.getBase() instanceof DataFlow::ObjectLiteralNode |
| 141 | + ) |
| 142 | +select assign1.getWriteNode(), "This write to property '" + name + "' is useless, since $@ always overrides it.", assign2.getWriteNode(), "another property write" |
0 commit comments