Self-Assignment
ID |
kotlin.self_assignment |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Reliability |
Language |
Kotlin |
Tags |
CWE:480, reliability, suspicious-comparison |
Description
Reports an assignment whose two sides are the same expression: y = y, this.count = this.count, dst[i] = dst[i]. The statement has no effect at run time.
The two sides are compared over the parsed tokens, so comments, whitespace and line breaks are ignored while a difference in operand, property or index is not.
Rationale
// Bad
class Counter(count: Int) {
private var count: Int = 0
init {
this.count = this.count // meant: this.count = count
}
}
fun copy(src: IntArray, dst: IntArray, i: Int) {
dst[i] = dst[i] // meant: dst[i] = src[i]
}
-
The statement is either dead weight or, far more often, a typo for the assignment that was meant — a missing
this.on one side, or an operand left unchanged after a copy-paste. -
Nothing fails: the property keeps its previous value, frequently the type’s default, and the defect surfaces much later, far from its cause.
-
The scope functions make the mistake easy to write and hard to see. Inside
builder.apply { name = name }a bare name resolves against the innermost receiver on both sides, so both arebuilder.name— the outer value the author reached for needs the explicitthis@Outer.name.
Remediation
Assign the value that was meant, or delete the statement.
// Good
class Counter(count: Int) {
private var count: Int = count
}
fun copy(src: IntArray, dst: IntArray, i: Int) {
dst[i] = src[i]
}
fun build(outer: Config, builder: Builder) {
builder.apply { name = outer.name }
}
What is not reported
Augmented assignments are a different statement and are never no-ops: y += y doubles, y -= y zeroes, y *= y squares. Sides that differ in any operand or index are not reported, including dst[i] = dst[i + 1] and this.count = count.
Either side is also left alone when it contains a call or an increment, since those make two textually identical sides evaluate differently: values[i] = values[i] writes one element and reads the next, and values[next()] = values[next()] may use two different indices.
One reported shape is not strictly a no-op: assigning a property that declares a custom setter, or whose delegate observes writes, does run that code. This is reported deliberately, as it is in every other language’s version of this check — such a statement is at best a very obscure way to trigger a side effect, and reads to every reviewer as the typo it usually is.