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

Fix generation of derived value classes wrapping Unit, Null, and Nothing #5938

Merged
merged 1 commit into from Jun 19, 2017

Conversation

hrhino
Copy link
Member

@hrhino hrhino commented Jun 11, 2017

All sorts o' specialness going on here. Unit morphs into a BoxedUnit when it's in a field, but void when it's the return type of a method, which is expected. This means, though, that the unboxing method of a Unit-wrapping value class has the signature ()V, not ()Lscala/runtime/BoxedUnit, so attempting to use its value in the equals method spits out some wonderful invalid bytecode, instead. Similar sadness occurs for Nothing and Null as well.

The "solution" is to not even bother to check for equality, as we've only got at most one legitimate value of each of these types. Because the code is shared with case classes, this also changes the bytecode we generate for them. Obviously this is an "unrelated change" as far as the bugs this is meant to fix go, but it's innocuous enough as far as I can tell.

I also slipped a constructor call into the generated ClassCastException that gets thrown when we are asked to emit a cast for a primitive type in BCodeBodyBuilder, so we generate valid bytecode in that case. I've got no idea how NEW; DUP; ATHROW is possibly ever legitimate, but hey.

Fixes scala/bug#9240
Fixes scala/bug#10361

@scala-jenkins scala-jenkins added this to the 2.12.3 milestone Jun 11, 2017
*/
def equalsCore(eqmeth: Symbol, accessors: List[Symbol]) = {
def usefulEquality(acc: Symbol): Boolean =
acc.info.resultType != NothingTpe && acc.info.resultType != NullTpe && acc.info.resultType != UnitTpe
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 would think that these three types would have an alias somewhere, but I couldn't find it. Feel free to make me swap it out for something else.

Copy link
Member

Choose a reason for hiding this comment

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

The three comparisons look OK to me, but you could extract acc.info.resultType into a local.

// scala/bug#9240
final class AnyValNothing(val self: Nothing) extends AnyVal
final class AnyValNull (val self: Null ) extends AnyVal
// scala/bug#10361
Copy link
Member

Choose a reason for hiding this comment

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

Your references are inverted. The below is scala/bug#9240, and the ones above are scala/bug#10361.

Copy link
Member Author

Choose a reason for hiding this comment

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

Woops. Good catch, thanks!

def avn = new AnyValNull(null)
assert(avn == avn)
/*this throws NPE right now b/c null.hashCode() does...*/
//assert(avn.hashCode() == 0)
Copy link
Member

Choose a reason for hiding this comment

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

Switch this (and avu below) to .##. If this here returns 0 rather than throw an NPE then you also fixed scala/bug#7396 with this patch!

Copy link
Member Author

Choose a reason for hiding this comment

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

Sadly, that problem is deeper than this, as far as I can tell. No hat tricks here 😞

emit(asm.Opcodes.ATHROW)
if (l == UNIT && r == srBoxedUnitRef) {
/* special case: we can "cast" V to Lscala/runtime/BoxedUnit; by summoning the latter */
mnode.instructions.add(backendUtils.getBoxedUnit)
Copy link
Member

Choose a reason for hiding this comment

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

I think this should be handled in erasure, at the place where the cast is inserted, see where cast is invoked.

Copy link
Member

Choose a reason for hiding this comment

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

👍 It would also fix the problem for the three backends at the same time :)

Copy link
Member Author

Choose a reason for hiding this comment

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

How rude of me, I almost forgot that non-JVM backends exist... Changed!

*/
def equalsCore(eqmeth: Symbol, accessors: List[Symbol]) = {
def usefulEquality(acc: Symbol): Boolean =
acc.info.resultType != NothingTpe && acc.info.resultType != NullTpe && acc.info.resultType != UnitTpe
Copy link
Member

Choose a reason for hiding this comment

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

The three comparisons look OK to me, but you could extract acc.info.resultType into a local.

@hrhino hrhino force-pushed the bugfix/wacky-value-classes branch from b4061c2 to 251c1fd Compare June 13, 2017 01:57
Copy link
Member

@lrytz lrytz left a comment

Choose a reason for hiding this comment

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

Very nice work, thanks a lot!

@@ -530,6 +530,7 @@ abstract class BCodeBodyBuilder extends BCodeSkelBuilder {
if (cast) {
mnode.visitTypeInsn(asm.Opcodes.NEW, jlClassCastExceptionRef.internalName)
bc dup ObjectRef
mnode.visitMethodInsn(asm.Opcodes.INVOKESPECIAL, jlClassCastExceptionRef.internalName, INSTANCE_CONSTRUCTOR_NAME, "()V", true)
Copy link
Member

Choose a reason for hiding this comment

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

This certainly looks right, but I wonder how we can end up here. We'd need an x.asInstanceOf[T] where x has primitive type. Any idea? When I write 1.asInstanceOf[String], the value is boxed.

Copy link
Member

Choose a reason for hiding this comment

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

FWIW, Scala.js has corresponding code in its codegen, but I'm not sure it is ever exercised ^^

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 can't figure out how to get this branch to get hit... we should have already inserted the boxing by now. This somewhat implies to me that this could, possibly should, be an assertion or similar; before this patch, this would always generate bytecode that doesn't pass verification.

Copy link
Member

Choose a reason for hiding this comment

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

I agree with your analysis.

When in doubt about old code like this, we tend to add a devWarning (which issues a warning under -Ydebug or -Xdev) rather than a hard assert to fail gracefully

Copy link
Member Author

Choose a reason for hiding this comment

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

Devs are duly warned. Good idea!

@lrytz
Copy link
Member

lrytz commented Jun 13, 2017

  • TODO: run jardiff -- no diff in library+reflect+compiler

@lrytz lrytz added the welcome hello new contributor! label Jun 13, 2017
…ing.

All sorts o' specialness going on here. Unit morphs into a BoxedUnit when
it's in a field, but void when it's the return type of a method, which is
expected. This means, though, that the unboxing method of a Unit-wrapping
value class has the signature `()V`, not `()Lscala/runtime/BoxedUnit`, so
attempting to use its value in the equals method spits out some wonderful
invalid bytecode, instead. Similar sadness occurs for Nothing and Null as
well.

The "solution" is to not even bother to check for equality, as we've only
got at most one legitimate value of each of these types. Because the code
is shared with `case class`es, this also changes the bytecode we generate
for them. Obviously this is an "unrelated change" as far as the bugs this
is meant to fix go, but it's innocuous enough as far as I can tell.

I also slipped a constructor call into the generated `ClassCastException`
that gets thrown when we are asked to emit a cast for a primitive type in
`BCodeBodyBuilder`, so we generate valid bytecode if we ever wind in that
branch. Discussion on scala#5938 implies that this branch shouldn't
ever be reached, so add a devWarning now that it doesn't cause an obvious
error.

Fixes scala/bug#9240
Fixes scala/bug#10361
@hrhino hrhino force-pushed the bugfix/wacky-value-classes branch from 251c1fd to c9d84a1 Compare June 14, 2017 12:22
@lrytz lrytz removed the welcome hello new contributor! label Jun 15, 2017
@lrytz lrytz merged commit 34da49b into scala:2.12.x Jun 19, 2017
xuwei-k referenced this pull request in scalaz/scalaz Jun 20, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
6 participants