Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve support of static initialization blocks and statically-reachable memory #389

Open
eupp opened this issue Sep 25, 2024 · 1 comment
Assignees

Comments

@eupp
Copy link
Collaborator

eupp commented Sep 25, 2024

Currently, if a Lincheck test execution both:

  • reads some values from statically-reachable memory, and
  • modifies the content of the same memory during the test

it can lead to non-determinism error (the Lincheck would not be able to replay the execution trace, because the content of the static memory changed).

[Above by statically-reachable memory we mean any mutable state reachable from static variables.]

Note that during the test statically-reachable memory can be modified both by

  • static initialization blocks (<clinit>),
  • and the regular functions from the test.

Thus, in order to fix possible non-determinism errors, we need to track modifications performed by <clinit> blocks, save them into trace, and manually re-perform them during replay (in the same order as in the original execution).

@dmitrii-artuhov
Copy link
Collaborator

dmitrii-artuhov commented Nov 27, 2024

Todo list for proper support of the static memory restoring:

  • Static memory snapshot collecting and restoring #418 the leaking this problem: the code below will not work with the lazy snapshot tracking, because detection of this's fields modifications in constructors is implemented incorrectly (I merely check if methodName == "<init>" && fieldOwnerClass == transformingClass then the bytecode instructions are ignored. The correct way would be to check what obj owns the modified fields. And if obj == this we should only then skip these instructions, otherwise track them. However, I don't see how that can be achieved right now, because of Bytecode verification error after instrumentation of constructors (<init> methods) because of leaking this #424
    class LincheckTest {
       class Node(var a: Int) {
           constructor(other: Node) : this(2) { // constructor accepting the same type
               other.a = 0 // in case if other is static variable, the change should be tracked
                           // because `other != this` here, but in my solution it will be missed.
           }
       }
       
       companion object {
           val staticNode = Node(1)
       }
       
       @Operation
       fun modify() {
           val localNode = Node(staticNode) // here will be a write to staticNode.a = 0, which is missed
       }
     }
    
  • Static memory snapshot collecting and restoring #418 in the constructor calls instrumentation there should be check for sub-classing and not the pure equals. Right now there is no obvious way of doing so during class transformation without loading the class hierarchy (which is discouraged during transformation stage).
  • System.arraycopy(...) this call is not considered as modification, so overridden values will not be remembered. This call is used in ArrayList::remove(...) for example. It prevents from collections lazy tracking.
  • referencing fields and modifiying their values via reflection
  • java afu objects: method calls on atomic field updators (eg. kotlinx.atomicfu.AtomicInt::getAndIncrement() it will compile to a call on java afu object which references plain int and getAndIncrement is not considered as modification)
  • modification via unsafe calls
  • modification via var-handle calls

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants