mockito-scala icon indicating copy to clipboard operation
mockito-scala copied to clipboard

Bad type on operand stack

Open txsmith opened this issue 5 years ago • 5 comments

Hi!

I've hit a problem when running the following:

package com.example

import org.mockito.MockitoSugar
import org.scalatest.FlatSpec

case class ValueType[A](value: A) extends AnyVal

class MySpec extends FlatSpec with MockitoSugar {
  val s = mock[MyService]
}

class MyService {
  def crash(x: ValueType[Int]): Unit = {}
}

Whenever I mock a class with methods that takes an AnyVal like this, i get the following stack trace:

[error] Uncaught exception when running com.example.MySpec: java.lang.VerifyError: Bad type on operand stack
[error] Exception Details:
[error]   Location:
[error]     com/example/MyService$MockitoMock$1741504572.crash$accessor$QHQc6Wjp(I)V @2: invokespecial
[error]   Reason:
[error]     Type integer (current frame, stack[1]) is not assignable to 'java/lang/Integer'
[error]   Current Frame:
[error]     bci: @2
[error]     flags: { }
[error]     locals: { 'com/example/MyService$MockitoMock$1741504572', integer }
[error]     stack: { 'com/example/MyService$MockitoMock$1741504572', integer }
[error]   Bytecode:
[error]     0000000: 2a1b b700 70b1
[error] stack trace is suppressed; run last Test / testOnly for the full output
[info] ScalaTest
[info] Run completed in 2 seconds, 410 milliseconds.
[info] Total number of tests run: 0
[info] Suites: completed 0, aborted 1
[info] Tests: succeeded 0, failed 0, canceled 0, ignored 0, pending 0
[info] *** 1 SUITE ABORTED ***

Specifically, the argument has to be a subclass of AnyVal with a type parameter set to Int.

Any help is much appreciated!

txsmith avatar Apr 23 '20 08:04 txsmith

Hi @txsmith, I tried to reproduce the issue by porting your example to our test suite but I can make it fail, would you mind publishing a small GitHub project that reproduces the issue?

Thanks!

ultrasecreth avatar Apr 24 '20 00:04 ultrasecreth

Hey, thanks for your response. I finally got around to this and put up a minimal project here: https://github.com/txsmith/mockito-scala-bad-operand Let me know if this works for you!

Maybe useful to also include JDK and SBT version:

> sbt -J-showversion
openjdk version "11.0.6" 2020-01-14
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.6+10)
OpenJDK 64-Bit Server VM AdoptOpenJDK (build 11.0.6+10, mixed mode)
> sbt -v
[process_args] java_version = '11'
[sbt_options] declare -a sbt_options='([0]="-Xmx4G")'
[copyRt] java9_rt = '/Users/thomas/.sbt/0.13/java9-rt-ext-adoptopenjdk_11_0_6/rt.jar'
# Executing command line:
java
-Dfile.encoding=UTF-8
-Dscala.ext.dirs=/Users/thomas/.sbt/0.13/java9-rt-ext-adoptopenjdk_11_0_6
-Xmx4G
-jar
/usr/local/Cellar/sbt/1.3.7/libexec/bin/sbt-launch.jar

txsmith avatar May 29 '20 11:05 txsmith

Well, this one took me a while, cause it didn't want to fail in my test code!

The issue is that the default mock maker doesn't play well with some advanced Scala things, so I use by default the inline one.

I didn't make it the default as the core guys where supposed to do so at some point, but it seems that it never happened.

Anyway, create a file like this one in your test resources and the issue should go away

https://github.com/mockito/mockito-scala/blob/release/1.x/scalatest/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker

Thanks!

ultrasecreth avatar May 29 '20 21:05 ultrasecreth

That has worked, thanks a lot!

Now we're running into another problem however:

case class ValueType[A](value: A) extends AnyVal

class MySpec extends AnyFlatSpec with ArgumentMatchersSugar with MockitoSugar {
  val s = mock[MyService]
  when(s.crash(any[ValueType[Int]])).thenReturn(3)
}

class MyService {
  def crash(x: ValueType[Int]): Int = 3
}

This fails to type-check:

[error] /.../src/test/scala/mypackage/MySpec.scala:10:19: type mismatch;
[error]  found   : A
[error]  required: Int
[error]   when(s.crash(any[ValueType[Int]])).thenReturn(3)
[error]                   ^
[error] one error found

txsmith avatar Jul 10 '20 12:07 txsmith

Hi there, sorry but I've been quite busy these past few months

I spent a lot of time with this and sadly I think it has no solution. The issue seems to be related to the boxing that's necessary for generics, so the ValueType uses actually Integer rather than Int in compile time, but then it uses Int on runtime, and that breaks everything (and it's probably the root cause of the first error you posted).

The good news is that I have a workaround, that actually I think you should do regardless of this issue. You see, value classes are actually quite tricky to get right and have a lot of nasty unexpected side effects if they are misused (they'll actually be boxed and unboxed for real under some circumstances). There is an amazing library that actually does the right thing and has zero overhead no matter what, you can check it out here.

I hope it helps

ultrasecreth avatar Aug 22 '20 17:08 ultrasecreth