Skip to content

Instrument constructor bodies#661

Open
Evgeniy Zhelenskiy (zhelenskiy) wants to merge 1 commit into
developfrom
allow-instrumenting-constructors
Open

Instrument constructor bodies#661
Evgeniy Zhelenskiy (zhelenskiy) wants to merge 1 commit into
developfrom
allow-instrumenting-constructors

Conversation

@zhelenskiy

Copy link
Copy Markdown
Collaborator

No description provided.

@zhelenskiy Evgeniy Zhelenskiy (zhelenskiy) force-pushed the allow-instrumenting-constructors branch from 2ea54e2 to 99efc8c Compare May 20, 2025 14:33
@eupp Evgeniy Moiseenko (eupp) changed the title Instruct constructor bodies Instrument constructor bodies May 20, 2025
| | suspendAndCauseFailure() [suspendable: Continuation#1]: 0 at SingleSuspensionPointTraceRepresentationTest.operation(MultipleSuspensionsTests.kt:49) |
| | counter ➜ AtomicInteger#1 at SingleSuspensionPointTraceRepresentationTest.suspendAndCauseFailure(MultipleSuspensionsTests.kt:54) |
| | counter.get(): 1 at SingleSuspensionPointTraceRepresentationTest.suspendAndCauseFailure(MultipleSuspensionsTests.kt:54) |
| | this$0.counter.get(): 1 at SingleSuspensionPointTraceRepresentationTest.suspendAndCauseFailure(MultipleSuspensionsTests.kt:54) |

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we avoid this$0 here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see no way. I consider the cosmetic changes not urgent as this PR is just an extracted step for native calls.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes the trace slightly less intuitive. Could you please analyze how to fix it, please? It does not extremely complex.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evgeniy Zhelenskiy (@zhelenskiy) I've noticed you made a fix for this issue. Could you please check why it does not fix other this$0 occurrences in the trace?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They already existed. Generally, it is an actual legitimate name for outer this parameter in the constructor of an inner class.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not something you type in the code, so I would prefer omitting it in the trace.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not something you type in the code

I saw such code many times in my life.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't have this$0 in the code; why should you have it in the trace?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you like to refer to the outer this?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is outer-this better? Or this@Type?

| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| $this_runCatching.block(): threw IllegalStateException at BaseRunConcurrentRepresentationTest.testRunWithModelChecker$lambda$1$lambda$0(RunConcurrentRepresentationTests.kt:47) | | |
| block(): threw IllegalStateException at IncorrectConcurrentLinkedDequeRunConcurrentRepresentationTest.block(RunConcurrentRepresentationTests.kt:349) | | |
| Node#1.item.putObject(null) at ConcurrentLinkedDeque$Node.<init>(ConcurrentLinkedDeque.java:303) | | |

@ndkoval Nikita Koval (ndkoval) May 20, 2025

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we show constructor calls as method calls? (it looks as we miss a constructor call here)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, there is no simple way to do that.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are the possible ways?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure but it should be very complicated as the constructor call is separated into two parts.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evgeniy Zhelenskiy (@zhelenskiy), we already have such a logic for reads, wrapping them with "beforeRead" and "afterRead" methods. Would the same approach work here?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please elaborate

@zhelenskiy Evgeniy Zhelenskiy (zhelenskiy) May 24, 2025

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have previously tried doing this for some time and failed, so I think that:

  • It is not that simple.
  • It is another issue, and I don't see any reason to mix the PRs.
  • It is much less urgent.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think adding constructor events to the trace with a dedicated constructor call is the right way to proceed. Besides, I do not see what exactly is not that simple in this discussion. Could you please provide specific obstacles, so we can discuss them and make the right choice?

Also adding Evgeniy Moiseenko (@eupp) to this thread.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I remember, the problems were with stack frames, uninitialized variables, etc.

My argument against doing it now is the following:

  • the simple solution didn't work;
  • there is a separate issue for that;
  • the code is already working without it;
  • it can be done separately;
  • blocking this PR blocks native calls;
  • it is minor by its impact;
  • it is just a different feature, there is no need to mix up everything in one PR.

@eupp

Evgeniy Moiseenko (eupp) commented May 20, 2025

Copy link
Copy Markdown
Collaborator

Should resolve #449

@dmitrii-artuhov

Dmitrii Art (dmitrii-artuhov) commented May 27, 2025

Copy link
Copy Markdown
Contributor

Right now constructors are not fully instrumented, the SharedVariableAccessTransformer has a check inside to omit constructors.
https://github.com/JetBrains/lincheck/blob/allow-instrumenting-constructors/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt#L41

The problem with verification error could be reproduced in the following way:

/*
DataStructure::<init>:
   this.a = 10
   this.b = "hello world"
*/

scenario:
val ds: DataStructure? = null
T1                                                T2
ds = DataStructure()                            | ds?.method()
                                                | ds?.a = 10
                                                | ds?.b = "aaaa"

When JVM loads transformed class for DataStructure, it checks if there is a situation of using reference to ds (potentially non-fully constructed object) in multiple threads and will throw VerifyError.
I commented the check inside a SharedVariableAccessTransformer for constructors and multiple tests in our code base fail of because of that.
In a single threaded example (like in this PR) allowing SharedVariableAccessTransformer to insert its hooks is okey and throws no VerifyError. But when running other tests, where the object could be shared between threads during constructor invocation, the JVM some-how detects that and disallows to insert our hooks in constructors of such classes.


If this is not necessary to allow SharedVariableAccessTransformer to insert its hooks in the code, then we don't need to uncomment that check. And the main PR with native calls should be all right, I guess

@eupp

Copy link
Copy Markdown
Collaborator

Dmitrii Art (@dmitrii-artuhov) thanks for the investigation.

This situation in the scenario you provide looks like unsafe publication. But I wonder how JVM bytecode verifier is capable of detecting it (I thought it cannot). I suspect it has to do some kind of escape analysis to detect this.

Could you please share the whole code snippet you have (using GPMC syntax preferably).

@dmitrii-artuhov

Dmitrii Art (dmitrii-artuhov) commented May 27, 2025

Copy link
Copy Markdown
Contributor

Cannot exactly reproduce decribed case, but I have come up with other tests:

class TestClass {
  @Test
  fun test() {
    runConcurrentTest(1) {
      val t = thread {} // inside has a `val thread = object : Thread { /* references to outer variable `block` */ }` 
    }
  }
}
class TestClass {
  var field = 0
  inner class Node {
    init { field++ }
  }

  @Test
  fun test() {
    runConcurrentTest(1) {
      val n = Node()
    }
  }
}

both produce VerifyError

Probably I was wrong about unsafe publication example. Here is a different reason for the verification error. The unsafe publication examples I tried to get the same error, but could not. Probably thas is not the case.
I was looking at this stack overflow discussion https://stackoverflow.com/questions/9851813/java-leaking-this-in-constructor when creating this issue #424, and thought we are having leaking this to different threads. But it seems to be something else that is causing VerifyError

@zhelenskiy

Copy link
Copy Markdown
Collaborator Author

Dmitrii Art (@dmitrii-artuhov) Does it fail both before and after my change?

@dmitrii-artuhov

Copy link
Copy Markdown
Contributor

Dmitrii Art (@dmitrii-artuhov) Does it fail both before and after my change?

They fail after commenting out this part of the code
https://github.com/JetBrains/lincheck/blob/allow-instrumenting-constructors/src/jvm/main/org/jetbrains/kotlinx/lincheck/transformation/transformers/SharedMemoryAccessTransformer.kt#L41 (e.g. when inserting hooks of SharedVariableAccessTransformer in constructors)

@zhelenskiy

Copy link
Copy Markdown
Collaborator Author

Then can we just not comment them out? It is unnecessary for the native calls support.

@dmitrii-artuhov

Copy link
Copy Markdown
Contributor

If tracking field accesses in constructors is not required for native calls, then we are fine, I believe

@zhelenskiy Evgeniy Zhelenskiy (zhelenskiy) force-pushed the allow-instrumenting-constructors branch 2 times, most recently from f6b4412 to 524f42e Compare June 10, 2025 23:29
@zhelenskiy Evgeniy Zhelenskiy (zhelenskiy) force-pushed the allow-instrumenting-constructors branch from 524f42e to 7b0bda6 Compare June 11, 2025 18:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants