pitest icon indicating copy to clipboard operation
pitest copied to clipboard

PIT is generating equivalent mutations

Open zsombor-balogh-dt opened this issue 1 year ago • 1 comments

Hi,

I noticed that Pitest generated a few false mutants, which are equivalent to the original source code, and as a result, they survived. I found a similar previous issue: https://github.com/hcoles/pitest/issues/497. However, it seems that in certain cases, the fix doesn’t work, or I might be missing some configurations. Currently using 1.15.0 version with gradle.

  public boolean example(int maxSize, Duration maxAge) {
    lock.readLock().lock();
    try {
      int size = elements.size();
      if (size == 0) {
        return false; // -> replaced boolean return with false for com.example.Example::example → SURVIVED

      } else if (size >= maxSize) {
        return true; // -> replaced boolean return with true for com.example.Example::example → SURVIVED

      } else {
        long elapsedNanos = ticker.read() - timeOfLastReset;
        return elapsedNanos >= maxAge.toNanos();
      }

    } finally {
      lock.readLock().unlock();
    }
  }
}

Is there a config missing at our side or is it a known bug?

zsombor-balogh-dt avatar Apr 09 '24 08:04 zsombor-balogh-dt

I'm having the same issue

public boolean asyncAggregateTimeData() {
    if(!lock.tryLock()) {
        return false;
    }
    try {
        if(isUpdating) {
            return false; // → replaced boolean return with false for Aggregator::asyncAggregateTimeData → SURVIVED
        }
        var now = OffsetDateTime.now();
        if(now.minus(minTimeBetweenRefreshes).isBefore(lastAttempt)) {
            return false; // → replaced boolean return with false for Aggregator::asyncAggregateTimeData → SURVIVED
        }
        isUpdating = true;
        lastAttempt = now;
        aggregationFaults.clear();
        executor.execute(this::aggregateTimeData);
    } finally {
        lock.unlock();
    }
    return true;
}

The decompiled bytecode looks like this

public boolean asyncAggregateTimeData() {
    if (!this.lock.tryLock()) {
        return false;
    } else {
        boolean now;
        try {
            if (!this.isUpdating) {
                OffsetDateTime now = OffsetDateTime.now();
                if (now.minus(this.minTimeBetweenRefreshes).isBefore(this.lastAttempt)) {
                    boolean var2 = false;
                    return var2;
                }
                this.isUpdating = true;
                this.lastAttempt = now;
                this.aggregationFaults.clear();
                this.executor.execute(this::aggregateTimeData);
                return true;
            }
            now = false;
        } finally {
            this.lock.unlock();
        }
        return now;
    }
}

Looking at the byte code, it is a little more understandable. This almost seems like a compiler bug. Still very annoying, and I have not been able to restructure the code differently enough without changing the natural logic that it doesn't happen anymore.

I'm on 21.0.6+7-LTS Temurin

Gelunox avatar Apr 16 '25 06:04 Gelunox