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 are builder.name — the outer value the author reached for needs the explicit this@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.

© 2026 Xygeni. All rights reserved