Skip to content

Check that constructor calls follow a chaining discipline. #5020

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

Merged
merged 2 commits into from
Oct 7, 2024

Conversation

sjrd
Copy link
Member

@sjrd sjrd commented Aug 24, 2024

(Toned down my ambitions compared to #5019. This PR only checks the constructor chaining discipline as a first easier step.)


  • The constructor of jl.Object cannot call any other constructor.
  • Every other constructor must call exactly one other constructor, on this, either from the same class or the direct superclass.

There cannot be any other call to constructors.

These restrictions are also enforced by the JVM. Technically we are more restrictive because the JVM allows different code paths, each having a single constructor call, whereas we require exactly one at the top level. This is fine because both Scala and Java, as languages, enforce that more restrictive property.

Unfortunately, we used to generate an illegal super constructor call in the synthetic classes that extend DynamicImportThunk. We patch them with a deserialization hack.

In the first commit, we do not yet change the compiler to fix the above. In the second commit, we fix the compiler and only enable the deserialization hack on IR < 1.17.

@sjrd sjrd requested a review from gzm0 August 24, 2024 11:46
@sjrd

This comment was marked as outdated.

@sjrd sjrd force-pushed the enforce-ctor-discipline branch from 13a134b to 182ab54 Compare August 26, 2024 10:11
@sjrd sjrd force-pushed the enforce-ctor-discipline branch from 08bb559 to 40222a0 Compare September 29, 2024 12:41
@gzm0
Copy link
Contributor

gzm0 commented Oct 6, 2024

@sjrd, do you know if / how the JVM validates constructor calls if it allows different calls depending on control flow?

One one hand, disallowing stuff like:

if (...)
  super()
else
  super()

Seems restrictive for the IR level.

On the other hand:

  • Consistent with ES
  • Potentially a reasonable trade-off to allow validation.

@sjrd
Copy link
Member Author

sjrd commented Oct 6, 2024

They basically have a different validation type for an uninitialized object and an initialized one. Then they have stack typing at each instruction. Finally after the constructor call the type of the argument transitions from uninitialized to initialized. At merge points, the JVM checks that the stack types align, and that the type of the this value (which is tracked per instruction) align as well.

We could do it as well, but IMO it's overkill.

val thisType = if (static) NoType else instanceThisType
val bodyEnv = Env.fromParams(params)
.withThisType(thisType)
.withInConstructor(isConstructor)
Copy link
Contributor

Choose a reason for hiding this comment

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

Does it make sense (maybe in a follow-up commit or even PR) to remove this env value and check StoreModule inside checkConstructorBody? (I realize this probably makes presence of the tree more restrictive).

Copy link
Member Author

Choose a reason for hiding this comment

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

I think that would make sense, but I believe it belongs more to #5019 than this PR.

@sjrd sjrd force-pushed the enforce-ctor-discipline branch from 093a5a0 to ba34ce5 Compare October 7, 2024 07:02
* The constructor of `jl.Object` cannot call any other constructor.
* Every other constructor must call exactly one other constructor,
  on `this`, either from the same class or the direct superclass.

There cannot be any other call to constructors.

These restrictions are also enforced by the JVM. Technically we are
more restrictive because the JVM allows different code paths, each
having a single constructor call, whereas we require exactly one at
the top level. This is fine because both Scala and Java, as
languages, enforce that more restrictive property.

Unfortunately, we used to generate an illegal super constructor
call in the synthetic classes that extend `DynamicImportThunk`. We
patch them with a deserialization hack.

In this commit, we do not yet change the compiler to fix the above.
@sjrd sjrd force-pushed the enforce-ctor-discipline branch from ba34ce5 to ee10158 Compare October 7, 2024 07:05
And only enable the corresponding deserialization hack for
IR < 1.18.
@sjrd
Copy link
Member Author

sjrd commented Oct 7, 2024

(rebased since we already have a commit changing the version on main)

@sjrd sjrd merged commit 0bc8c06 into scala-js:main Oct 7, 2024
3 checks passed
@sjrd sjrd deleted the enforce-ctor-discipline branch October 7, 2024 11:40
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.

2 participants