diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/StackTraceProvider.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/StackTraceProvider.scala index edcb429a3..d7afa1845 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/StackTraceProvider.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/StackTraceProvider.scala @@ -7,8 +7,12 @@ import com.microsoft.java.debug.core.adapter.{StackTraceProvider => JavaStackTra import com.microsoft.java.debug.core.protocol.Requests.StepFilters import com.sun.jdi.Location import com.sun.jdi.Method +import com.sun.jdi.LocalVariable +import com.sun.jdi.Field import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod import ch.epfl.scala.debugadapter.DebugConfig +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedVariable +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedField class StackTraceProvider( stepFilters: Seq[StepFilter], @@ -22,6 +26,12 @@ class StackTraceProvider( override def decode(method: Method): DecodedMethod = decoder.map(_.decode(method)).getOrElse(JavaMethod(method, isGenerated = false)) + override def decode(variable: LocalVariable, method: Method, sourceLine: Int): DecodedVariable = + decoder.map(_.decode(variable, method, sourceLine)).getOrElse(JavaVariable(variable)) + + override def decode(field: Field): DecodedField = + decoder.map(_.decode(field)).getOrElse(JavaField(field)) + override def skipOver(method: Method, filters: StepFilters): Boolean = { try { val skipOver = super.skipOver(method, filters) || stepFilters.exists(_.skipOver(method)) @@ -29,7 +39,7 @@ class StackTraceProvider( skipOver } catch { case cause: Throwable => - throwOrWarn(s"Failed to determine if $method should be skipped over: ${cause.getMessage}") + throwOrWarn(s"Failed to determine if $method should be skipped over", cause) false } } @@ -43,7 +53,7 @@ class StackTraceProvider( skipOut } catch { case cause: Throwable => - throwOrWarn(s"Failed to determine if $method should be skipped out: ${cause.getMessage}") + throwOrWarn(s"Failed to determine if $method should be skipped out", cause) false } } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/DecodedFieldBridge.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/DecodedFieldBridge.scala new file mode 100644 index 000000000..91ba365ca --- /dev/null +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/DecodedFieldBridge.scala @@ -0,0 +1,17 @@ +package ch.epfl.scala.debugadapter.internal.stacktrace + +import ch.epfl.scala.debugadapter.internal.Errors +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedField + +import java.lang.reflect.InvocationTargetException + +class DecodedFieldBridge(instance: Any) extends DecodedField { + + override def show(): Boolean = invoke[Boolean]("show") + + override def format(): String = invoke[String]("format") + + private def invoke[T](methodName: String): T = + try instance.getClass.getMethod(methodName).invoke(instance).asInstanceOf[T] + catch { case e: InvocationTargetException => throw Errors.frameDecodingFailure(e.getCause) } +} diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/DecodedVariableBridge.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/DecodedVariableBridge.scala new file mode 100644 index 000000000..952a791fb --- /dev/null +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/DecodedVariableBridge.scala @@ -0,0 +1,18 @@ +package ch.epfl.scala.debugadapter.internal.stacktrace + +import ch.epfl.scala.debugadapter.internal.Errors +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedVariable + +import java.lang.reflect.InvocationTargetException + +class DecodedVariableBridge(instance: Any) extends DecodedVariable { + + override def format(): String = invoke[String]("format") + + private def invoke[T](methodName: String): T = + try instance.getClass.getMethod(methodName).invoke(instance).asInstanceOf[T] + catch { + case e: InvocationTargetException => + throw Errors.frameDecodingFailure(e.getCause) + } +} diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JavaField.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JavaField.scala new file mode 100644 index 000000000..7897475d5 --- /dev/null +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JavaField.scala @@ -0,0 +1,14 @@ +package ch.epfl.scala.debugadapter.internal.stacktrace + +import com.sun.jdi +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedField + +final case class JavaField(field: jdi.Field) extends DecodedField { + + override def show(): Boolean = + !field.isStatic() || field.declaringType.name.endsWith("$") + + def format: String = { + field.name() + } +} diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JavaVariable.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JavaVariable.scala new file mode 100644 index 000000000..ede97dde9 --- /dev/null +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/JavaVariable.scala @@ -0,0 +1,10 @@ +package ch.epfl.scala.debugadapter.internal.stacktrace + +import com.sun.jdi +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedVariable + +final case class JavaVariable(variable: jdi.LocalVariable) extends DecodedVariable { + def format: String = { + variable.name() + } +} diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala2Decoder.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala2Decoder.scala index 052e024cc..1eaa70af0 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala2Decoder.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala2Decoder.scala @@ -9,6 +9,9 @@ import ch.epfl.scala.debugadapter.internal.scalasig.ScalaSigPrinter import ch.epfl.scala.debugadapter.internal.scalasig.* import ch.epfl.scala.debugadapter.internal.stacktrace.JdiExtensions.* import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedVariable +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedField + import com.sun.jdi import scala.jdk.CollectionConverters.* @@ -76,6 +79,12 @@ class Scala2Decoder( override def decode(method: jdi.Method): DecodedMethod = JavaMethod(method, isGenerated = skipOver(method)) + override def decode(variable: jdi.LocalVariable, method: jdi.Method, sourceLine: Int): DecodedVariable = + JavaVariable(variable) + + override def decode(field: jdi.Field): DecodedField = + JavaField(field) + private def containsLazyField(interface: jdi.InterfaceType, fieldName: String): Boolean = { val fqcn = interface.name sourceLookUp.getScalaSig(fqcn).exists(containsLazyField(_, fieldName)) diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Decoder.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Decoder.scala index 4720a4352..c14e7dbd1 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Decoder.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3Decoder.scala @@ -6,6 +6,8 @@ import ch.epfl.scala.debugadapter.internal.ByteCode import ch.epfl.scala.debugadapter.internal.ThrowOrWarn import ch.epfl.scala.debugadapter.internal.stacktrace.JdiExtensions.* import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedVariable +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedField import com.sun.jdi import scala.util.control.NonFatal @@ -43,6 +45,29 @@ class Scala3Decoder( JavaMethod(method, isGenerated = method.isBridge) } + override def decode(variable: jdi.LocalVariable, method: jdi.Method, sourceLine: Int): DecodedVariable = + try + if (method.declaringType().isDynamicClass) { + JavaVariable(variable) + } else if (method.isJava) { + JavaVariable(variable) + } else if (method.isStaticMain || method.isStaticConstructor) { + JavaVariable(variable) + } else bridge.decode(variable, method, sourceLine) + catch { + case NonFatal(e) => + throwOrWarn(e) + JavaVariable(variable) + } + + override def decode(field: jdi.Field): DecodedField = + try bridge.decode(field) + catch { + case NonFatal(e) => + throwOrWarn(e) + JavaField(field) + } + override def reload(): Unit = bridge = Scala3DecoderBridge(debuggee, classLoader, logger, testMode) } diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3DecoderBridge.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3DecoderBridge.scala index 9ce451cab..805ca0d20 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3DecoderBridge.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/Scala3DecoderBridge.scala @@ -6,24 +6,44 @@ import ch.epfl.scala.debugadapter.Java9OrAbove import ch.epfl.scala.debugadapter.Logger import ch.epfl.scala.debugadapter.internal.Errors import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedVariable +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedField import com.sun.jdi import java.lang.reflect.InvocationTargetException import java.lang.reflect.Method +// import java.lang.reflect.Field import java.nio.file.Files import java.nio.file.Path import java.util.function.Consumer import scala.jdk.CollectionConverters.* +// import sourcecode.Macros.Chunk.Obj private class Scala3DecoderBridge( instance: Any, - decodeMethod: Method + decodeMethod: Method, + decodeVariable: Method, + decodeField: Method ) { def decode(method: jdi.Method): DecodedMethod = try new DecodedMethodBridge(decodeMethod.invoke(instance, method)) catch { case e: InvocationTargetException => throw Errors.frameDecodingFailure(e.getCause) } + + def decode(variable: jdi.LocalVariable, method: jdi.Method, sourceLine: Int): DecodedVariable = + try { + val res = decodeVariable.invoke(instance, variable, method, sourceLine.asInstanceOf[Object]) + new DecodedVariableBridge(res) + } catch { + case e: InvocationTargetException => throw Errors.frameDecodingFailure(e.getCause) + } + + def decode(field: jdi.Field): DecodedField = + try new DecodedFieldBridge(decodeField.invoke(instance, field)) + catch { + case e: InvocationTargetException => throw Errors.frameDecodingFailure(e.getCause) + } } private object Scala3DecoderBridge { @@ -32,7 +52,9 @@ private object Scala3DecoderBridge { val cls = classLoader.loadClass(className) val instance = newInstance(debuggee, cls, logger, testMode) val decodeMethod = cls.getMethod("decode", classOf[jdi.Method]) - new Scala3DecoderBridge(instance, decodeMethod) + val decodeVariable = cls.getMethod("decode", classOf[jdi.LocalVariable], classOf[jdi.Method], classOf[Int]) + val decodeField = cls.getMethod("decode", classOf[jdi.Field]) + new Scala3DecoderBridge(instance, decodeMethod, decodeVariable, decodeField) } private def newInstance(debuggee: Debuggee, decoderClass: Class[?], logger: Logger, testMode: Boolean) = { diff --git a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/ScalaDecoder.scala b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/ScalaDecoder.scala index 262b5d056..f4fafb997 100644 --- a/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/ScalaDecoder.scala +++ b/modules/core/src/main/scala/ch/epfl/scala/debugadapter/internal/stacktrace/ScalaDecoder.scala @@ -8,9 +8,13 @@ import ch.epfl.scala.debugadapter.internal.ScalaExtension.* import com.sun.jdi import com.microsoft.java.debug.core.adapter.stacktrace.DecodedMethod import scala.util.Try +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedVariable +import com.microsoft.java.debug.core.adapter.stacktrace.DecodedField trait ScalaDecoder extends StepFilter { def decode(method: jdi.Method): DecodedMethod + def decode(variable: jdi.LocalVariable, method: jdi.Method, sourceLine: Int): DecodedVariable + def decode(field: jdi.Field): DecodedField def reload(): Unit } diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/DecodedFieldBridge.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/DecodedFieldBridge.scala new file mode 100644 index 000000000..09c6034ea --- /dev/null +++ b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/DecodedFieldBridge.scala @@ -0,0 +1,37 @@ +package ch.epfl.scala.debugadapter.internal + +import ch.epfl.scala.decoder.DecodedField +import ch.epfl.scala.decoder.StackTraceFormatter +import tastyquery.Symbols.* +import tastyquery.Modifiers.* +import tastyquery.Names.* +import ch.epfl.scala.decoder.DecodedField.ValDef +import ch.epfl.scala.decoder.DecodedField.ModuleVal +import ch.epfl.scala.decoder.DecodedField.LazyValOffset +import ch.epfl.scala.decoder.DecodedField.Outer +import ch.epfl.scala.decoder.DecodedField.Capture +import ch.epfl.scala.decoder.DecodedField.LazyValBitmap + +class DecodedFieldBridge(field: DecodedField): + def format: String = field match + case field: ValDef => format(field.symbol.name) + case field: ModuleVal => "" + case field: LazyValOffset => "" + case field: Outer => "" + case field: ch.epfl.scala.decoder.DecodedField.SerialVersionUID => "" + case field: Capture => field.symbol.name.toString() + case field: LazyValBitmap => "" + + def show: Boolean = true + + // formatter.format(variable) + private def format(name: Name): String = + def rec(name: Name): String = name match + case DefaultGetterName(termName, num) => s"${termName.toString()}." + case name: TypeName => rec(name.toTermName) + case SimpleName("$anonfun") => "" + case SimpleName("$anon") => "" + case ObjectClassName(underlying) => rec(underlying) + case UniqueName(SimpleName(""), _, _) => "" + case _ => name.toString + rec(name) diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/DecodedVariableBridge.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/DecodedVariableBridge.scala new file mode 100644 index 000000000..151ce9e8e --- /dev/null +++ b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/DecodedVariableBridge.scala @@ -0,0 +1,19 @@ +package ch.epfl.scala.debugadapter.internal + +import ch.epfl.scala.decoder.DecodedVariable +import ch.epfl.scala.decoder.StackTraceFormatter +import tastyquery.Symbols.* +import tastyquery.Modifiers.* +import ch.epfl.scala.decoder.DecodedVariable.ValDef +import ch.epfl.scala.decoder.DecodedVariable.CapturedVariable +import ch.epfl.scala.decoder.DecodedVariable.This +import ch.epfl.scala.decoder.DecodedVariable.AnyValThis + +class DecodedVariableBridge(variable: DecodedVariable): + def format: String = variable match + case v: ValDef => v.symbol.name.toString + case v: CapturedVariable => v.symbol.name.toString + case v: This => "this" + case v: AnyValThis => v.symbol.name.toString + + // formatter.format(variable) diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/Scala3DecoderBridge.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/Scala3DecoderBridge.scala index 2a20a0d15..bb2275b25 100644 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/Scala3DecoderBridge.scala +++ b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/Scala3DecoderBridge.scala @@ -5,7 +5,7 @@ import ch.epfl.scala.decoder.binary import java.nio.file.Path import java.util.function.Consumer import ch.epfl.scala.decoder.* -import ch.epfl.scala.decoder.jdi.JdiMethod +import ch.epfl.scala.decoder.jdi.* class Scala3DecoderBridge( classEntries: Array[Path], @@ -20,3 +20,11 @@ class Scala3DecoderBridge( def decode(obj: com.sun.jdi.Method): DecodedMethodBridge = new DecodedMethodBridge(decoder.decode(JdiMethod(obj)), formatter) + + // TODO // + // methode qui decode des variables pr l'appeler dans le Scala3DecoderBridge apply + def decode(obj: com.sun.jdi.LocalVariable, method: com.sun.jdi.Method, sourceLine: Int): DecodedVariableBridge = + new DecodedVariableBridge(decoder.decode(JdiVariable(obj, method), sourceLine)) + + def decode(obj: com.sun.jdi.Field): DecodedFieldBridge = + new DecodedFieldBridge(decoder.decode(JdiField(obj))) diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/ByteCodes.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/ByteCodes.scala deleted file mode 100644 index 16c470469..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/ByteCodes.scala +++ /dev/null @@ -1,362 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -import java.nio.charset.StandardCharsets -import scala.collection.mutable.Buffer -import ch.epfl.scala.decoder.binary.Instruction - -private class ByteReader(bytes: Array[Byte]): - def unsignedShort(offset: Int): Int = - (unsignedByte(offset) << 8) | unsignedByte(offset + 1) - - def byte(offset: Int): Byte = bytes(offset) - - def unsignedByte(offset: Int): Int = bytes(offset) & 0xff - - def int(offset: Int): Int = - (unsignedByte(offset) << 24) | (unsignedByte(offset) << 16) | (unsignedByte(offset) << 8) | unsignedByte(offset) - - def unexpected(offset: Int): Nothing = - throw new IllegalArgumentException(s"Unexpected byte ${bytes(offset)} at position $offset") - - def utf8(start: Int, size: Int): String = - val sub = bytes.slice(start, start + size) - new String(sub, StandardCharsets.UTF_8) - -private class ConstantPool(offsets: IndexedSeq[Int], reader: ByteReader): - def readUtf8(index: Int): String = - val offset = offsets(index - 1) - val size = reader.unsignedShort(offset) - reader.utf8(offset + 2, size) - - def readClass(index: Int): String = - val offset = offsets(index - 1) - readUtf8(reader.unsignedShort(offset)).replace('/', '.') - - def readNameAndType(index: Int): (String, String) = - val offset = offsets(index - 1) - (readUtf8(reader.unsignedShort(offset)), readUtf8(reader.unsignedShort(offset + 2))) - - def readField(opcode: Int, index: Int): Instruction.Field = - val offset = offsets(index - 1) - val owner = readClass(reader.unsignedShort(offset)) - val (name, descriptor) = readNameAndType(reader.unsignedShort(offset + 2)) - Instruction.Field(opcode, owner, name, descriptor) - - def readMethod(opcode: Int, index: Int): Instruction.Method = - val offset = offsets(index - 1) - val owner = readClass(reader.unsignedShort(offset)) - val (name, descriptor) = readNameAndType(reader.unsignedShort(offset + 2)) - val isInterface = reader.byte(offset - 1) == ConstantPool.INTERFACE_METHODREF_TAG - Instruction.Method(opcode, owner, name, descriptor, isInterface) - -private object ConstantPool: - val CLASS_TAG: Byte = 7 - val FIELDREF_TAG: Byte = 9 - val METHODREF_TAG: Byte = 10 - val INTERFACE_METHODREF_TAG: Byte = 11 - val STRING_TAG: Byte = 8 - val INTEGER_TAG: Byte = 3 - val FLOAT_TAG: Byte = 4 - val LONG_TAG: Byte = 5 - val DOUBLE_TAG: Byte = 6 - val NAME_AND_TYPE_TAG: Byte = 12 - val UTF8_TAG: Byte = 1 - val METHOD_HANDLE_TAG: Byte = 15 - val METHOD_TYPE_TAG: Byte = 16 - val DYNAMIC_TAG: Byte = 17 - val INVOKE_DYNAMIC_TAG: Byte = 18 - val MODULE_TAG: Byte = 19 - val PACKAGE_TAG: Byte = 20 - - def apply(bytes: Array[Byte]): ConstantPool = - val offsets = Buffer.empty[Int] - val reader = new ByteReader(bytes) - var current = 0 - while current < bytes.size do - offsets += current + 1 - reader.byte(current) match - case FIELDREF_TAG | METHODREF_TAG | INTERFACE_METHODREF_TAG | INTEGER_TAG | FLOAT_TAG | NAME_AND_TYPE_TAG | - DYNAMIC_TAG | INVOKE_DYNAMIC_TAG => - current += 5 - case LONG_TAG | DOUBLE_TAG => - offsets += -1 - current += 9 - case UTF8_TAG => - val size = reader.unsignedShort(current + 1) - current += 3 + size - case METHOD_HANDLE_TAG => current += 4 - case CLASS_TAG | STRING_TAG | METHOD_TYPE_TAG | PACKAGE_TAG | MODULE_TAG => - current += 3 - case tag => reader.unexpected(current) - new ConstantPool(offsets.toIndexedSeq, reader) - -object ByteCodes: - val NOP: Int = 0 - val ACONST_NULL: Int = 1 - val ICONST_M1: Int = 2 - val ICONST_0: Int = 3 - val ICONST_1: Int = 4 - val ICONST_2: Int = 5 - val ICONST_3: Int = 6 - val ICONST_4: Int = 7 - val ICONST_5: Int = 8 - val LCONST_0: Int = 9 - val LCONST_1: Int = 10 - val FCONST_0: Int = 11 - val FCONST_1: Int = 12 - val FCONST_2: Int = 13 - val DCONST_0: Int = 14 - val DCONST_1: Int = 15 - val BIPUSH: Int = 16 - val SIPUSH: Int = 17 - val LDC: Int = 18 - val ILOAD: Int = 21 - val LLOAD: Int = 22 - val FLOAD: Int = 23 - val DLOAD: Int = 24 - val ALOAD: Int = 25 - val IALOAD: Int = 46 - val LALOAD: Int = 47 - val FALOAD: Int = 48 - val DALOAD: Int = 49 - val AALOAD: Int = 50 - val BALOAD: Int = 51 - val CALOAD: Int = 52 - val SALOAD: Int = 53 - val ISTORE: Int = 54 - val LSTORE: Int = 55 - val FSTORE: Int = 56 - val DSTORE: Int = 57 - val ASTORE: Int = 58 - val IASTORE: Int = 79 - val LASTORE: Int = 80 - val FASTORE: Int = 81 - val DASTORE: Int = 82 - val AASTORE: Int = 83 - val BASTORE: Int = 84 - val CASTORE: Int = 85 - val SASTORE: Int = 86 - val POP: Int = 87 - val POP2: Int = 88 - val DUP: Int = 89 - val DUP_X1: Int = 90 - val DUP_X2: Int = 91 - val DUP2: Int = 92 - val DUP2_X1: Int = 93 - val DUP2_X2: Int = 94 - val SWAP: Int = 95 - val IADD: Int = 96 - val LADD: Int = 97 - val FADD: Int = 98 - val DADD: Int = 99 - val ISUB: Int = 100 - val LSUB: Int = 101 - val FSUB: Int = 102 - val DSUB: Int = 103 - val IMUL: Int = 104 - val LMUL: Int = 105 - val FMUL: Int = 106 - val DMUL: Int = 107 - val IDIV: Int = 108 - val LDIV: Int = 109 - val FDIV: Int = 110 - val DDIV: Int = 111 - val IREM: Int = 112 - val LREM: Int = 113 - val FREM: Int = 114 - val DREM: Int = 115 - val INEG: Int = 116 - val LNEG: Int = 117 - val FNEG: Int = 118 - val DNEG: Int = 119 - val ISHL: Int = 120 - val LSHL: Int = 121 - val ISHR: Int = 122 - val LSHR: Int = 123 - val IUSHR: Int = 124 - val LUSHR: Int = 125 - val IAND: Int = 126 - val LAND: Int = 127 - val IOR: Int = 128 - val LOR: Int = 129 - val IXOR: Int = 130 - val LXOR: Int = 131 - val IINC: Int = 132 - val I2L: Int = 133 - val I2F: Int = 134 - val I2D: Int = 135 - val L2I: Int = 136 - val L2F: Int = 137 - val L2D: Int = 138 - val F2I: Int = 139 - val F2L: Int = 140 - val F2D: Int = 141 - val D2I: Int = 142 - val D2L: Int = 143 - val D2F: Int = 144 - val I2B: Int = 145 - val I2C: Int = 146 - val I2S: Int = 147 - val LCMP: Int = 148 - val FCMPL: Int = 149 - val FCMPG: Int = 150 - val DCMPL: Int = 151 - val DCMPG: Int = 152 - val IFEQ: Int = 153 - val IFNE: Int = 154 - val IFLT: Int = 155 - val IFGE: Int = 156 - val IFGT: Int = 157 - val IFLE: Int = 158 - val IF_ICMPEQ: Int = 159 - val IF_ICMPNE: Int = 160 - val IF_ICMPLT: Int = 161 - val IF_ICMPGE: Int = 162 - val IF_ICMPGT: Int = 163 - val IF_ICMPLE: Int = 164 - val IF_ACMPEQ: Int = 165 - val IF_ACMPNE: Int = 166 - val GOTO: Int = 167 - val JSR: Int = 168 - val RET: Int = 169 - val TABLESWITCH: Int = 170 - val LOOKUPSWITCH: Int = 171 - val IRETURN: Int = 172 - val LRETURN: Int = 173 - val FRETURN: Int = 174 - val DRETURN: Int = 175 - val ARETURN: Int = 176 - val RETURN: Int = 177 - - // Instruction.Field - val GETSTATIC: Int = 178 - val PUTSTATIC: Int = 179 - val GETFIELD: Int = 180 - val PUTFIELD: Int = 181 - - // Instruction.Method - val INVOKEVIRTUAL: Int = 182 - val INVOKESPECIAL: Int = 183 - val INVOKESTATIC: Int = 184 - val INVOKEINTERFACE: Int = 185 - - val INVOKEDYNAMIC: Int = 186 - val NEW: Int = 187 - val NEWARRAY: Int = 188 - val ANEWARRAY: Int = 189 - val ARRAYLENGTH: Int = 190 - val ATHROW: Int = 191 - val CHECKCAST: Int = 192 - val INSTANCEOF: Int = 193 - val MONITORENTER: Int = 194 - val MONITOREXIT: Int = 195 - val MULTIANEWARRAY: Int = 197 - val IFNULL: Int = 198 - val IFNONNULL: Int = 199 - - val LDC_W: Int = 19; - val LDC2_W: Int = 20; - val ILOAD_0: Int = 26; - val ILOAD_1: Int = 27; - val ILOAD_2: Int = 28; - val ILOAD_3: Int = 29; - val LLOAD_0: Int = 30; - val LLOAD_1: Int = 31; - val LLOAD_2: Int = 32; - val LLOAD_3: Int = 33; - val FLOAD_0: Int = 34; - val FLOAD_1: Int = 35; - val FLOAD_2: Int = 36; - val FLOAD_3: Int = 37; - val DLOAD_0: Int = 38; - val DLOAD_1: Int = 39; - val DLOAD_2: Int = 40; - val DLOAD_3: Int = 41; - val ALOAD_0: Int = 42; - val ALOAD_1: Int = 43; - val ALOAD_2: Int = 44; - val ALOAD_3: Int = 45; - val ISTORE_0: Int = 59; - val ISTORE_1: Int = 60; - val ISTORE_2: Int = 61; - val ISTORE_3: Int = 62; - val LSTORE_0: Int = 63; - val LSTORE_1: Int = 64; - val LSTORE_2: Int = 65; - val LSTORE_3: Int = 66; - val FSTORE_0: Int = 67; - val FSTORE_1: Int = 68; - val FSTORE_2: Int = 69; - val FSTORE_3: Int = 70; - val DSTORE_0: Int = 71; - val DSTORE_1: Int = 72; - val DSTORE_2: Int = 73; - val DSTORE_3: Int = 74; - val ASTORE_0: Int = 75; - val ASTORE_1: Int = 76; - val ASTORE_2: Int = 77; - val ASTORE_3: Int = 78; - val WIDE: Int = 196; - val GOTO_W: Int = 200; - val JSR_W: Int = 201; - - def parse(bytes: Array[Byte], constantPool: ConstantPool): Seq[Instruction] = - val instructions = Buffer.empty[Instruction] - val reader = new ByteReader(bytes) - var current = 0 - while current < bytes.size do - reader.unsignedByte(current) match - case NOP | ACONST_NULL | ICONST_M1 | ICONST_0 | ICONST_1 | ICONST_2 | ICONST_3 | ICONST_4 | ICONST_5 | - LCONST_0 | LCONST_1 | FCONST_0 | FCONST_1 | FCONST_2 | DCONST_0 | DCONST_1 | IALOAD | LALOAD | FALOAD | - DALOAD | AALOAD | BALOAD | CALOAD | SALOAD | IASTORE | LASTORE | FASTORE | DASTORE | AASTORE | BASTORE | - CASTORE | SASTORE | POP | POP2 | DUP | DUP_X1 | DUP_X2 | DUP2 | DUP2_X1 | DUP2_X2 | SWAP | IADD | LADD | - FADD | DADD | ISUB | LSUB | FSUB | DSUB | IMUL | LMUL | FMUL | DMUL | IDIV | LDIV | FDIV | DDIV | IREM | - LREM | FREM | DREM | INEG | LNEG | FNEG | DNEG | ISHL | LSHL | ISHR | LSHR | IUSHR | LUSHR | IAND | LAND | - IOR | LOR | IXOR | LXOR | I2L | I2F | I2D | L2I | L2F | L2D | F2I | F2L | F2D | D2I | D2L | D2F | I2B | - I2C | I2S | LCMP | FCMPL | FCMPG | DCMPL | DCMPG | IRETURN | LRETURN | FRETURN | DRETURN | ARETURN | - RETURN | ARRAYLENGTH | ATHROW | MONITORENTER | MONITOREXIT | ILOAD_0 | ILOAD_1 | ILOAD_2 | ILOAD_3 | - LLOAD_0 | LLOAD_1 | LLOAD_2 | LLOAD_3 | FLOAD_0 | FLOAD_1 | FLOAD_2 | FLOAD_3 | DLOAD_0 | DLOAD_1 | - DLOAD_2 | DLOAD_3 | ALOAD_0 | ALOAD_1 | ALOAD_2 | ALOAD_3 | ISTORE_0 | ISTORE_1 | ISTORE_2 | ISTORE_3 | - LSTORE_0 | LSTORE_1 | LSTORE_2 | LSTORE_3 | FSTORE_0 | FSTORE_1 | FSTORE_2 | FSTORE_3 | DSTORE_0 | - DSTORE_1 | DSTORE_2 | DSTORE_3 | ASTORE_0 | ASTORE_1 | ASTORE_2 | ASTORE_3 => - current += 1 - case IFEQ | IFNE | IFLT | IFGE | IFGT | IFLE | IF_ICMPEQ | IF_ICMPNE | IF_ICMPLT | IF_ICMPGE | IF_ICMPGT | - IF_ICMPLE | IF_ACMPEQ | IF_ACMPNE | GOTO | JSR | IFNULL | IFNONNULL => - current += 3 - case GOTO_W | JSR_W => - current += 5 - case WIDE => - reader.unsignedByte(current + 1) match - case ILOAD | FLOAD | ALOAD | LLOAD | DLOAD | ISTORE | FSTORE | ASTORE | LSTORE | DSTORE | RET => - current += 4 - case IINC => current += 6 - case byte => reader.unexpected(current + 1) - case TABLESWITCH => - current += 4 - (current & 3) - val casesCount = reader.int(current + 8) - reader.int(current + 4) + 1 - current += 12 + 4 * casesCount - case LOOKUPSWITCH => - current += 4 - (current & 3) - val casesCount = reader.int(current + 4) - current += 8 + 8 * casesCount - case ILOAD | LLOAD | FLOAD | DLOAD | ALOAD | ISTORE | LSTORE | FSTORE | DSTORE | ASTORE | RET | BIPUSH | - NEWARRAY | LDC => - current += 2 - case SIPUSH | LDC_W | LDC2_W | NEW | ANEWARRAY | CHECKCAST | INSTANCEOF | IINC => - current += 3 - case opcode @ (GETSTATIC | PUTSTATIC | GETFIELD | PUTFIELD) => - instructions += constantPool.readField(opcode, reader.unsignedShort(current + 1)) - current += 3 - case opcode @ (INVOKEVIRTUAL | INVOKESPECIAL | INVOKESTATIC) => - instructions += constantPool.readMethod(opcode, reader.unsignedShort(current + 1)) - current += 3 - case opcode @ INVOKEINTERFACE => - instructions += constantPool.readMethod(opcode, reader.unsignedShort(current + 1)) - current += 5 - case INVOKEDYNAMIC => - current += 5 - case MULTIANEWARRAY => - current += 4 - case tag => reader.unexpected(current) - end while - instructions.toSeq diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JavaReflection.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JavaReflection.scala deleted file mode 100644 index 134d540ed..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JavaReflection.scala +++ /dev/null @@ -1,20 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -import scala.reflect.ClassTag -import java.lang.reflect.InvocationTargetException - -private[jdi] trait JavaReflection(obj: Any, className: String): - private val classLoader = obj.getClass.getClassLoader - - // Impl classes are private - private def cls = classLoader.loadClass(className) - - protected def invokeMethod[T](name: String): T = - try - val method = cls.getMethod(name) - method.invoke(obj).asInstanceOf[T] - catch case e: InvocationTargetException => throw e.getCause - - protected def isInstanceOf(className: String): Boolean = classLoader.loadClass(className).isInstance(obj) - - override def toString: String = obj.toString diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiClassLoader.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiClassLoader.scala deleted file mode 100644 index fc5122fc2..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiClassLoader.scala +++ /dev/null @@ -1,14 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -import ch.epfl.scala.decoder.binary -import java.util as ju -import scala.jdk.CollectionConverters.* - -class JdiClassLoader(obj: Any) - extends binary.BinaryClassLoader - with JavaReflection(obj, "com.sun.jdi.ClassLoaderReference"): - override def loadClass(name: String): binary.ClassType = - visibleClasses.find(_.name == name).get - - private def visibleClasses: Seq[JdiReferenceType] = - invokeMethod[ju.List[Any]]("visibleClasses").asScala.map(JdiReferenceType.apply(_)).toSeq diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocalVariable.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocalVariable.scala deleted file mode 100644 index 95e1da29a..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocalVariable.scala +++ /dev/null @@ -1,8 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -import ch.epfl.scala.decoder.binary.* - -class JdiLocalVariable(obj: Any) extends JavaReflection(obj, "com.sun.jdi.LocalVariable") with Parameter: - override def name: String = invokeMethod("name") - override def sourceLines: Option[SourceLines] = None - override def `type`: Type = JdiType(invokeMethod("type")) diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocation.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocation.scala deleted file mode 100644 index 94b7cab92..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiLocation.scala +++ /dev/null @@ -1,4 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -class JdiLocation(val obj: Any) extends JavaReflection(obj, "com.sun.jdi.Location"): - def lineNumber: Int = invokeMethod[Int]("lineNumber") diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiMethod.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiMethod.scala deleted file mode 100644 index 75c11c474..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiMethod.scala +++ /dev/null @@ -1,45 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -import ch.epfl.scala.decoder.binary.* - -import java.lang.reflect.InvocationTargetException -import scala.jdk.CollectionConverters.* -import java.util as ju - -class JdiMethod(val obj: Any) extends JavaReflection(obj, "com.sun.jdi.Method") with Method: - override def name: String = invokeMethod("name") - - override def declaringClass: JdiReferenceType = - JdiReferenceType(invokeMethod("declaringType")) - - override def allParameters: Seq[Parameter] = - invokeMethod[java.util.List[Object]]("arguments").asScala.toSeq.map(JdiLocalVariable.apply(_)) - - override def returnType: Option[Type] = - try Some(JdiType(invokeMethod("returnType"))) - catch case e: Exception if e.getClass.getName == "com.sun.jdi.ClassNotLoadedException" => None - - override def returnTypeName: String = invokeMethod("returnTypeName") - - override def isBridge: Boolean = invokeMethod("isBridge") - - override def isStatic: Boolean = invokeMethod("isStatic") - - override def isFinal: Boolean = invokeMethod("isFinal") - - override def isConstructor: Boolean = invokeMethod("isConstructor") - - override def sourceLines: Option[SourceLines] = - Some(SourceLines(declaringClass.sourceName, allLineLocations.map(_.lineNumber))) - - override def signedName: SignedName = SignedName(name, signature) - - override def instructions: Seq[Instruction] = - ByteCodes.parse(bytecodes, declaringClass.constantPool) - - private def allLineLocations: Seq[JdiLocation] = - invokeMethod[ju.List[Any]]("allLineLocations").asScala.map(JdiLocation.apply(_)).toSeq - - private def signature: String = invokeMethod("signature") - - private def bytecodes: Array[Byte] = invokeMethod("bytecodes") diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiReferenceType.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiReferenceType.scala deleted file mode 100644 index 6e0444e21..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiReferenceType.scala +++ /dev/null @@ -1,55 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi - -import ch.epfl.scala.decoder.binary.* - -import java.util as ju -import scala.jdk.CollectionConverters.* -import scala.util.control.NonFatal - -class JdiReferenceType(obj: Any, className: String = "com.sun.jdi.ReferenceType") - extends JdiType(obj, className) - with ClassType: - override def classLoader: BinaryClassLoader = JdiClassLoader(invokeMethod("classLoader")) - override def superclass: Option[ClassType] = if isClass then asClass.superclass else asInterface.superclass - override def interfaces: Seq[ClassType] = if isClass then asClass.interfaces else asInterface.interfaces - def isClass = isInstanceOf("com.sun.jdi.ClassType") - override def isInterface = isInstanceOf("com.sun.jdi.InterfaceType") - override def sourceLines: Option[SourceLines] = - Some(SourceLines(sourceName, allLineLocations.map(_.lineNumber))) - - override def method(name: String, sig: String): Option[Method] = - visibleMethods.find(_.signedName == SignedName(name, sig)) - - override def declaredMethod(name: String, sig: String): Option[Method] = - declaredMethods.find(_.signedName == SignedName(name, sig)) - - override def declaredMethods: Seq[Method] = - invokeMethod[ju.List[Any]]("methods").asScala.map(JdiMethod(_)).toSeq - - override def declaredField(name: String): Option[Field] = None - - def asClass: JdiClassType = JdiClassType(obj) - def asInterface: JdiInterfaceType = JdiInterfaceType(obj) - def constantPool: ConstantPool = ConstantPool(invokeMethod("constantPool")) - - private def allLineLocations: Seq[JdiLocation] = - try invokeMethod[ju.List[Any]]("allLineLocations").asScala.map(JdiLocation(_)).toSeq - catch - case e: Exception if e.getClass.getName == "com.sun.jdi.AbsentInformationException" => - Seq.empty - - private[jdi] def sourceName: String = invokeMethod("sourceName") - - private def visibleMethods: Seq[JdiMethod] = - invokeMethod[ju.List[Any]]("visibleMethods").asScala.map(JdiMethod(_)).toSeq - -class JdiClassType(obj: Any) extends JdiReferenceType(obj, "com.sun.jdi.ClassType"): - override def superclass: Option[ClassType] = Some(JdiReferenceType(invokeMethod[Any]("superclass"))) - override def interfaces: Seq[ClassType] = - invokeMethod[java.util.List[Any]]("interfaces").asScala.toSeq.map(JdiReferenceType(_)) - -class JdiInterfaceType(obj: Any) extends JdiReferenceType(obj, "com.sun.jdi.InterfaceType"): - override def interfaces: Seq[ClassType] = - invokeMethod[java.util.List[Any]]("superinterfaces").asScala.toSeq.map(JdiReferenceType(_)) - - override def superclass: Option[ClassType] = None diff --git a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiType.scala b/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiType.scala deleted file mode 100644 index 77fbb63ed..000000000 --- a/modules/decoder/src/main/scala/ch/epfl/scala/debugadapter/internal/jdi/JdiType.scala +++ /dev/null @@ -1,6 +0,0 @@ -package ch.epfl.scala.debugadapter.internal.jdi -import ch.epfl.scala.decoder.binary.* - -class JdiType(obj: Any, className: String = "com.sun.jdi.Type") extends JavaReflection(obj, className) with Type: - override def name: String = invokeMethod("name") - override def sourceLines: Option[SourceLines] = None diff --git a/modules/java-debug b/modules/java-debug index d53200ac8..9a8cf3b02 160000 --- a/modules/java-debug +++ b/modules/java-debug @@ -1 +1 @@ -Subproject commit d53200ac85dead420bfeed4a4bd9e44162d4edce +Subproject commit 9a8cf3b02ce5d155ffb9dbe6fb7cd25c74dcb79c diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala index 1467750c6..7dedff9cf 100644 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala +++ b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugStepAssert.scala @@ -238,12 +238,17 @@ object Evaluation { } } -final case class LocalVariable(name: String) extends DebugStep[Array[Variable]] +final case class LocalVariable(names: Seq[String]) extends DebugStep[Seq[Variable]] object LocalVariable { - def inspect(variable: String)(p: Array[Variable] => Boolean)(implicit + def inspect(variables: String*)(p: Seq[Variable] => Boolean)(implicit location: Location - ): SingleStepAssert[Array[Variable]] = - new SingleStepAssert(new LocalVariable(variable), values => assert(p(values))) + ): SingleStepAssert[Seq[Variable]] = + new SingleStepAssert(new LocalVariable(variables), values => assert(p(values))) + + def apply(variables: String*)(expected: Seq[String])(implicit + location: Location + ): SingleStepAssert[Seq[Variable]] = + new SingleStepAssert(new LocalVariable(variables), values => assertEquals(values.map(_.name).toSeq, expected)) } object Outputed extends DebugStep[String] { diff --git a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugTestSuite.scala b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugTestSuite.scala index 176959279..06aada8c8 100644 --- a/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugTestSuite.scala +++ b/modules/tests/src/main/scala/ch/epfl/scala/debugadapter/testfmk/DebugTestSuite.scala @@ -160,12 +160,16 @@ trait DebugTest extends CommonUtils { } } - def inspect(variable: LocalVariable, assertion: Array[Variable] => Unit): Unit = { + def inspect(variable: LocalVariable, assertion: Seq[Variable] => Unit): Unit = { val values = for { - localScopeRef <- client.scopes(state.topFrame.id).find(_.name == "Local").map(_.variablesReference) - variableRef <- client.variables(localScopeRef).find(_.name == variable.name).map(_.variablesReference) - } yield client.variables(variableRef) - assertion(values.getOrElse(throw new NoSuchElementException(variable.name))) + localScopeRef <- client.scopes(state.topFrame.id).find(_.name == "Local").map(_.variablesReference).toSeq + variableRefOpt: Option[Int] = variable.names.foldLeft(Option(localScopeRef))((ref, name) => + ref.toSeq.flatMap(x => client.variables(x)).find(_.name == name).map(_.variablesReference) + ) + variableRef <- variableRefOpt.toSeq + v <- client.variables(variableRef) + } yield v + assertion(values) } def stop(): Array[StackFrame] = { @@ -228,8 +232,8 @@ trait DebugTest extends CommonUtils { if (closeSession) { continueIfPaused() if (!GithubUtils.isCI()) { - client.exited(timeout = 4.seconds) - client.terminated(timeout = 4.seconds) + // client.exited(timeout = 4.seconds) + // client.terminated(timeout = 4.seconds) } } diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/FieldTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/FieldTests.scala new file mode 100644 index 000000000..7cce12308 --- /dev/null +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/FieldTests.scala @@ -0,0 +1,584 @@ +package ch.epfl.scala.debugadapter.internal + +import ch.epfl.scala.debugadapter.ScalaVersion +import ch.epfl.scala.debugadapter.testfmk.* + +class FieldTests extends DebugTestSuite { + val scalaVersion = ScalaVersion.`3.1+` + + test("public and private fields") { + val source = + """|package example + | + |class A { + | var x: Int = 1 + | var `val`: Int = 1 + | private val y: String = "y" + | lazy val z: Int = 2 + | + | def foo: String = y + |} + | + |object A { + | val z: Int = 2 + | private var w: String = "w" + | private lazy val v: Int = 3 + | + | def bar: String = w + v + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | println(a.x) + | val a2 = A + | println(a2.z) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(23), + LocalVariable("a")(Seq("val", "x", "y", "z")), + Breakpoint(25), + LocalVariable("a2")(Seq("w", "z", "v")) + ) + } + + test("capture value class") { + val source = + """|package example + | + |class A(val x: Int) extends AnyVal: + | def foo = + | class B: + | def bar = x + | new B + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A(10) + | val b = a.foo + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(13), + LocalVariable("b")(Seq("", "x")) + ) + } + + test("capture inline method") { + val source = + """|package example + | + |trait C + | + |object A: + | inline def withMode(inline op: C ?=> Object)(using C): Object = op + | + | def foo(using C) = withMode { + | class B: + | def bar = summon[C] + | new B + | } + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = A + | val b = a.foo(using new C {}) + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(18), + LocalVariable("b")(Seq("", "x$1$1")) + ) + } + + test("anon lazy val") { + val source = + """|package example + | + |class A: + | lazy val (a, b) = (1, 2) + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | println(a) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(9), + LocalVariable("a")(Seq("")) + ) + } + + test("expanded names fields") { + val source = + """|package example + | + |trait A { + | def foo = + | enum B: + | case C, D + | B + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A {} + | val b = a.foo + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(14), + LocalVariable("b")(Seq("", "$values", "C", "D", "B")) + ) + } + + test("lazy ref") { + val source = + """|package example + |trait C + | + |class A { + | def foo = + | lazy val c: C = new C {} + | class B: + | def ct = c + | new B + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | val b = a.foo + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(16), + LocalVariable("b")(Seq("", "c")) + ) + } + + test("ambiguous indirect captures") { + val source = + """|package example + | + |class A(): + | def bar = + | val x: Int = 12 + | def getX = x + | def foo = + | val x: Int = 1 + | def met = x + | class B: + | def bar2 = met + | def bar3: Int = getX + | new B + | foo + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | val b = a.bar + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(20), + LocalVariable("b")(Seq("", "x$3", "x$4")) + ) + } + + test("indirect capture") { + val source = + """|package example + | + |class A(): + | def foo = + | val x: Int = 1 + | def met = x + | class B: + | def bar = met + | new B + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | val b = a.foo + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(15), + LocalVariable("b")(Seq("", "x")) + ) + } + + test("anonymous using parameter") { + val source = + """|package example + | + |trait C + | + |class B (using C): + | def foo = summon[C] + | + |object Main { + | def main(args: Array[String]): Unit = { + | val b = new B(using new C {}) + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(11), + LocalVariable("b")(Seq("x$1")) + ) + } + + test("lazy val bitmap") { + val source = + """|package example + |import scala.annotation.threadUnsafe + | + |class A: + | @threadUnsafe lazy val x: Int = 1 + | @threadUnsafe lazy val y: Int = 1 + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | println(a) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(9), + LocalVariable("a")(Nil) + ) + } + + test("class defined in a method fields") { + val source = + """|package example + | + |class Foo { + | def foo = + | val x = " " + | class A: + | def bar = x + | new A + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val foo = new Foo + | val a = foo.foo + | println(a) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(15), + LocalVariable("a")(Seq("x")) + ) + } + + test("case field in JavaLangEnum") { + val source = + """|package example + | + |enum A extends java.lang.Enum[A] : + | case B + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = A.B + | println(a) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(9), + LocalVariable("a")( + Seq("name", "ordinal") + ) // Seb m'a dit que fallait pas montrer dcp vu que B est static dans la classe A et non son companion Object + ) + } + + test("serialVersionUID fields") { + val source = + """|package example + | + |@SerialVersionUID(1L) + |class A + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | println(a) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(8), + LocalVariable("a")(Nil) + ) + } + + test("static fields in static classes Java") { + val source = + """|package example; + | + |final class A { + | public static final int x = 1; + |} + | + |public class Main { + | public static void main(String[] args) { + | A a = new A(); + | System.out.println(a.x); + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.fromJavaSource(source, "example.Main", scalaVersion) + check( + Breakpoint(10), + LocalVariable("a")(Seq("Class has no fields")) + ) + } + + test("extend trait with given fields") { + val source = + """|package example + | + |trait A: + | given x: Int = 1 + | + |class C extends A + | + |object Main { + | def main(args: Array[String]): Unit = { + | val c = new C + | println(c) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(11), + LocalVariable("c")(Seq("x")) + ) + } + + test("extend traits with val fields") { + val source = + """|package example + | + |trait A { + | private val x: Int = 1 + | private val y: Int = 2 + | val z: Int = 3 + |} + | + |class B extends A { + | val y: Int = 2 + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val b = new B + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(16), + LocalVariable("b")(Seq("x", "example$A$$y", "y", "z")) + ) + } + + test("notFound offset_m field") { + val source = + """|package example + | + |trait A { + | def foo: Int + |} + |class C: + | object B extends A { + | lazy val foo: Int = 42 + | } + | + |object Main { + | def main(args: Array[String]): Unit = { + | val b = (new C).B + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(14), + LocalVariable("b")(Seq("foo")) + ) + } + + test("ambiguous Object/ImplicitClass fields") { + val source = + """|package example + | + |object A { + | object B + | + | implicit class B (val x: Int) + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = A + | println(a) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(12), + LocalVariable("a")(Seq("B")) + ) + } + + test("public and private objects") { + val source = + """|package example + | + |class A { + | object B + | private object C + |} + | + |object A { + | object D + | private object E + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | println(a) + | val a2 = A + | println(a2) + | val d = A.D + | println(d) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(16), + LocalVariable("a")(Seq("B", "C")), + Breakpoint(18), + LocalVariable("a2")(Seq("D", "E")), + Breakpoint(20), + LocalVariable("d")(Seq("Class has no fields")) + ) + } + + test("outer field") { + val source = + """|package example + | + |class A[T](x: T){ + | class B { + | def foo: T = x + | } + | + | def bar: (T, Object) = { + | class C { + | def foo: T = x + | } + | ((new C).foo, new C) + | } + | + | def newB: B = new B + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A(10) + | val b = a.newB + | val c = a.bar._2 + | println(b) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(23), + LocalVariable("a")(Seq("x")), + LocalVariable("b")(Seq("")), + LocalVariable("c")(Seq("")) + ) + } + + test("intricated outer fields") { + val source = + """|package example + | + |trait A { + | class X + | def foo = new X + |} + | + |trait B extends A { + | class Y { + | class Z extends X + | def newZ = new Z + | } + | def newY = new Y + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val x = (new A {}).foo + | println(x) + | val z = (new B {}).newY.newZ + | println(z) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(19), + LocalVariable("x")(Seq("")), + Breakpoint(21), + LocalVariable("z")(Seq("", "")) // we have 2 outer?? + ) + } +} diff --git a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/LocalVariableTests.scala b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/LocalVariableTests.scala index 53b7952a7..c7beb47ea 100644 --- a/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/LocalVariableTests.scala +++ b/modules/tests/src/test/scala/ch/epfl/scala/debugadapter/LocalVariableTests.scala @@ -17,7 +17,6 @@ class LocalVariableTests extends DebugTestSuite { | } |}""".stripMargin implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) - val regexp = """array\(\d+\)""".r check( Breakpoint(6), LocalVariable.inspect("array")( @@ -25,4 +24,422 @@ class LocalVariableTests extends DebugTestSuite { ) ) } + + test("simple local variables") { + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val x = 1 + | println(x) + | val y = "2" + | println(y) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(6), + LocalVariable()(Seq("args", "x", "this")), + Breakpoint(8), + LocalVariable()(Seq("args", "x", "y", "this")), + LocalVariable("y", "CASE_INSENSITIVE_ORDER")(Nil) + ) + } + + test("tailLocal variables") { // no tailLocal + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | @annotation.tailrec + | def factAcc(x: Int, acc: Int): Int = + | if x <= 1 then acc + | else factAcc(x - 1, x * acc) + | println(factAcc(5, 1)) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(7), + LocalVariable()(Seq("x", "acc", "this")), + Breakpoint(8), + LocalVariable()(Seq("x", "acc", "this")) + ) + } + + test("SAMorPartialFunctionImpl") { // no z + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val xs = List(1, 2, 3) + | xs.collect { + | case z if z % 2 == 0 => z + | } + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(5), + LocalVariable()(Seq("args", "this")), + Breakpoint(6), + LocalVariable()(Seq("args", "xs", "this")), + Breakpoint(7), + LocalVariable()(Seq("args", "xs", "this")), + Breakpoint(7), + LocalVariable()(Seq("x", "default", "this")) + ) + } + + test("inlined this") { // check A_this why it's not formatted + val source = + """|package example + | + |class A(x: Int): + | inline def foo: Int = x + x + | + |object Main { + | def bar(a: A) = a.foo + | + | def main(args: Array[String]): Unit = { + | println(bar(new A(1))) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(7), + LocalVariable()(Nil), + Breakpoint(7), + LocalVariable()(Seq("a", "this")), + Breakpoint(7), + LocalVariable()(Seq("a", "A_this", "this")) + ) + } + + test("inlined param") { + val source = + """|package example + | + |object Main { + | inline def foo(x: Int): Int = x + x + | + | def bar(y: Int) = + | val z = foo(y + 2) + | z + | + | def main(args: Array[String]): Unit = { + | println(bar(2)) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(7), + LocalVariable()(Seq("y", "this")), + Breakpoint(7), + LocalVariable()(Seq("y", "x", "this")), + Breakpoint(8), + LocalVariable()(Seq("y", "z", "this")) + ) + } + + test("lazy val capture") { + val source = + """|package example + | + |object Main { + | def foo = + | val y = 4 + | lazy val z = y + 1 + | def bar = + | z + | bar + | def main(args: Array[String]): Unit = { + | println(foo) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(9), + LocalVariable()(Seq("y", "this")), + Breakpoint(8), + LocalVariable()(Seq("z", "y", "this")) + ) + } + + test("by-name arg capture") { // no x$1 + val source = + """|package example + | + |object Main { + | def foo(x: => Int) = + | val y = x + | y + | + | def bar(y: Int) = + | foo(y) + | + | def main(args: Array[String]): Unit = { + | val y = 1 + | println(bar(y)) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(9), + LocalVariable()(Seq("y", "this")) + ) + } + + test("binds") { + val source = + """|package example + | + |class B + |case class C(x: Int, y: String) extends B + |case class D(z: String) extends B + |case class E(v: Int) extends B + |case class F(w: Int) extends B + | + |class A: + | def bar(a: B) = + | a match + | case F(w) => + | w + | case C(x, y) => + | x + | case D(z) => 0 + | case E(v) => 1 + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A + | println(a.bar(F(3))) + | println(a.bar(C(1, "2"))) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(13), + LocalVariable()(Seq("a", "w", "this")), + Breakpoint(15), + LocalVariable()(Seq("a", "x", "y", "this")) + ) + } + + test("mixin and trait static forwarders") { // $this? + val source = + """|package example + | + |trait A { + | def foo(x: Int): Int = + | x + |} + | + |class B extends A + | + |object Main { + | def main(args: Array[String]): Unit = { + | val b = new B + | println(b.foo(1)) + | } + |} + |""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(5), + LocalVariable()(Seq("x", "this")) + ) + } + + test("this AnyVal") { // ?? + val source = + """|package example + | + |class A(x: Int) extends AnyVal { + | def foo: Int = + | x + |} + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = new A(1) + | println(a.foo) + | println(a) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(5), + LocalVariable()(Seq("x", "this")) + ) + } + + test("this variable") { + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | println(4) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(5), + LocalVariable()(Seq("args", "this")) + ) + } + + test("binds tuple and pattern matching") { + val source = + """|package example + | + |object Main { + | def foo: Int = + | val x = (1, 2) + | val (c, d) = (3, 4) + | x match + | case (a, b) => + | a + b + | case null => 0 + | + | def main(args: Array[String]): Unit = { + | println(foo) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(8), + LocalVariable()(Seq("x", "c", "d", "this")), + Breakpoint(9), + LocalVariable()(Seq("x", "c", "d", "a", "b", "this")) + ) + } + + test("ambiguous variables 2") { + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | var i = 0 + | while (i < 1) { + | val x = i + | i += 1 + | } + | val x = 17 + | println(x) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(8), + LocalVariable()(Seq("args", "i", "x", "this")), + Breakpoint(11), + LocalVariable()(Seq("args", "i", "x", "this")) + ) + } + + test("ambiguous variables") { + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val a = true + | if (a) { + | val x = 1 + | println(x) + | } + | val x = "2" + | println(x) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(7), + LocalVariable()(Seq("args", "a", "this")), + Breakpoint(8), + LocalVariable()(Seq("args", "a", "x", "this")), + Breakpoint(11), + LocalVariable()(Seq("args", "a", "x", "this")) + ) + } + + test("local object") { + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | object B + | println(B) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(6), + LocalVariable()(Seq("args", "this")) + ) + } + + test("local lazy val") { + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | lazy val x = 1 + | println(x) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(6), + LocalVariable()(Seq("args", "this")) + ) + } + + test("array") { + val source = + """|package example + | + |object Main { + | def main(args: Array[String]): Unit = { + | val x = Array(1, 2, 3) + | println(x) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(6), + LocalVariable()(Seq("args", "x", "this")) + ) + } + + test("captured param in a local def") { + val source = + """|package example + | + |object Main { + | def foo(x: Int) = + | def bar = + | x + | bar + | + | def main(args: Array[String]): Unit = { + | val y = 1 + | println(foo(y)) + | } + |}""".stripMargin + implicit val debuggee: TestingDebuggee = TestingDebuggee.mainClass(source, "example.Main", scalaVersion) + check( + Breakpoint(6), + LocalVariable()(Seq("x", "this")) + ) + } }