From f3fe53a7060a56f0c991ae032f273d1d199e76bf Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Tue, 23 May 2023 00:19:18 +0300 Subject: [PATCH 1/9] Finish the JDWP spec --- readme.md | 130 +------------- src/client.rs | 7 +- src/codec.rs | 13 ++ src/commands/array_reference.rs | 4 +- src/commands/array_type.rs | 25 --- src/commands/class_loader_reference.rs | 29 ---- src/commands/class_object_reference.rs | 13 -- src/commands/class_type.rs | 157 +++++++++++++++++ src/commands/event.rs | 2 +- src/commands/method.rs | 171 +++++++++++++++++++ src/commands/mod.rs | 105 ++++++++++-- src/commands/object_reference.rs | 211 ++++++++++++++++++++++- src/commands/reference_type.rs | 10 +- src/commands/stack_frame.rs | 82 +++++++++ src/commands/string_reference.rs | 10 -- src/commands/thread_reference.rs | 224 +++++++++++++++++++++++-- src/commands/virtual_machine.rs | 40 +---- src/enums.rs | 7 +- src/types.rs | 10 +- tests/reference_type.rs | 21 ++- tests/virtual_machine.rs | 15 +- 21 files changed, 989 insertions(+), 297 deletions(-) delete mode 100644 src/commands/array_type.rs delete mode 100644 src/commands/class_loader_reference.rs delete mode 100644 src/commands/class_object_reference.rs create mode 100644 src/commands/class_type.rs create mode 100644 src/commands/method.rs create mode 100644 src/commands/stack_frame.rs delete mode 100644 src/commands/string_reference.rs diff --git a/readme.md b/readme.md index 554aaf2..90a8a51 100644 --- a/readme.md +++ b/readme.md @@ -5,138 +5,14 @@ It only provides the base types and encoding/decoding traits. Also contains a ~~dumb~~ simple blocking JDWP client implementation. -Currently work in progress. +Currently work in progress. All the commands from JDWP spec are implemented, but very little of them are tested. Planned: -- Implement all the commands (currently ~half) -- Cover everything with tests (currently very little, but the setup is there) - there are definitely human errors in this. +- Cover everything with tests - there are definitely human errors in this. +- More and better documentation (currently commands are repeating the JDWP spec) - Async client?. MSRV is 1.66.1 -### JDWP commands implemented: -- [x] VirtualMachine Command Set (1) - - [x] Version (1) - - [x] ClassesBySignature (2) - - [x] AllClasses (3) - - [x] AllThreads (4) - - [x] TopLevelThreadGroups (5) - - [x] Dispose (6) - - [x] IDSizes (7) - - [x] Suspend (8) - - [x] Resume (9) - - [x] Exit (10) - - [x] CreateString (11) - - [x] Capabilities (12) - - [x] ClassPaths (13) - - [x] DisposeObjects (14) - - [x] HoldEvents (15) - - [x] ReleaseEvents (16) - - [x] CapabilitiesNew (17) - - [x] RedefineClasses (18) - - [x] SetDefaultStratum (19) - - [x] AllClassesWithGeneric (20) - - [x] InstanceCounts (21) - -- [x] ReferenceType Command Set (2) - - [x] Signature (1) - - [x] ClassLoader (2) - - [x] Modifiers (3) - - [x] Fields (4) - - [x] Methods (5) - - [x] GetValues (6) - - [x] SourceFile (7) - - [x] NestedTypes (8) - - [x] Status (9) - - [x] Interfaces (10) - - [x] ClassObject (11) - - [x] SourceDebugExtension (12) - - [x] SignatureWithGeneric (13) - - [x] FieldsWithGeneric (14) - - [x] MethodsWithGeneric (15) - - [x] Instances (16) - - [x] ClassFileVersion (17) - - [x] ConstantPool (18) - -- [ ] ClassType Command Set (3) - - [ ] Superclass (1) - - [ ] SetValues (2) - - [ ] InvokeMethod (3) - - [ ] NewInstance (4) - -- [x] ArrayType Command Set (4) - - [x] NewInstance (1) - -- [x] InterfaceType Command Set (5) - -- [ ] Method Command Set (6) - - [ ] LineTable (1) - - [ ] VariableTable (2) - - [ ] Bytecodes (3) - - [ ] IsObsolete (4) - - [ ] VariableTableWithGeneric (5) - -- [x] Field Command Set (8) - -- [ ] ObjectReference Command Set (9) - - [ ] ReferenceType (1) - - [ ] GetValues (2) - - [ ] SetValues (3) - - [ ] MonitorInfo (5) - - [ ] InvokeMethod (6) - - [ ] DisableCollection (7) - - [ ] EnableCollection (8) - - [ ] IsCollected (9) - - [ ] ReferringObjects (10) - -- [x] StringReference Command Set (10) - - [x] Value (1) - -- [ ] ThreadReference Command Set (11) - - [x] Name (1) - - [x] Suspend (2) - - [ ] Resume (3) - - [ ] Status (4) - - [ ] ThreadGroup (5) - - [ ] Frames (6) - - [ ] FrameCount (7) - - [ ] OwnedMonitors (8) - - [ ] CurrentContendedMonitor (9) - - [ ] Stop (10) - - [ ] Interrupt (11) - - [ ] SuspendCount (12) - - [ ] OwnedMonitorsStackDepthInfo (13) - - [ ] ForceEarlyReturn (14) - -- [x] ThreadGroupReference Command Set (12) - - [x] Name (1) - - [x] Parent (2) - - [x] Children (3) - -- [x] ArrayReference Command Set (13) - - [x] Length (1) - - [x] GetValues (2) - - [x] SetValues (3) - -- [x] ClassLoaderReference Command Set (14) - - [x] VisibleClasses (1) - -- [x] EventRequest Command Set (15) - - [x] Set (1) - - [x] Clear (2) - - [x] ClearAllBreakpoints (3) - -- [ ] StackFrame Command Set (16) - - [ ] GetValues (1) - - [ ] SetValues (2) - - [ ] ThisObject (3) - - [ ] PopFrames (4) - -- [x] ClassObjectReference Command Set (17) - - [x] ReflectedType (1) - -- [x] Event Command Set (64) - - [x] Composite (100) - ## License This crate is licensed under the MIT license diff --git a/src/client.rs b/src/client.rs index beb3824..08d39e3 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1,5 +1,6 @@ use std::{ collections::HashMap, + fmt::Debug, io::{self, Cursor, Read, Write}, net::{Shutdown, TcpStream, ToSocketAddrs}, sync::{ @@ -96,7 +97,11 @@ impl JdwpClient { &self.host_events_rx } - pub fn send(&mut self, command: C) -> Result { + pub fn send(&mut self, command: C) -> Result + where + C: Command + JdwpWritable + Debug, + C::Output: JdwpReadable + Debug, + { match self.reader_handle { Some(ref handle) if handle.is_finished() => { return Err(self.reader_handle.take().unwrap().join().unwrap()) diff --git a/src/codec.rs b/src/codec.rs index 17876ac..f4f6dcc 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -214,3 +214,16 @@ impl JdwpWritable for Vec { Ok(()) } } + +impl JdwpReadable for (A, B) { + fn read(read: &mut JdwpReader) -> io::Result { + Ok((A::read(read)?, B::read(read)?)) + } +} + +impl JdwpWritable for (A, B) { + fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { + self.0.write(write)?; + self.1.write(write) + } +} diff --git a/src/commands/array_reference.rs b/src/commands/array_reference.rs index ca656a6..2aa77cb 100644 --- a/src/commands/array_reference.rs +++ b/src/commands/array_reference.rs @@ -1,6 +1,6 @@ use crate::{ codec::JdwpWritable, - types::{ArrayID, ArrayRegion, Untagged}, + types::{ArrayID, ArrayRegion, UntaggedValue}, }; use super::jdwp_command; @@ -45,5 +45,5 @@ pub struct SetValues { /// The first index to set first_index: i32, /// Values to set - values: Vec, + values: Vec, } diff --git a/src/commands/array_type.rs b/src/commands/array_type.rs deleted file mode 100644 index c9dbaae..0000000 --- a/src/commands/array_type.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::{ - codec::{JdwpReadable, JdwpWritable}, - enums::Tag, - types::{ArrayID, ArrayTypeID}, -}; - -use super::jdwp_command; - -/// Creates a new array object of this type with a given length. -#[jdwp_command(4, 1)] -#[derive(Debug, JdwpWritable)] -pub struct NewInstance { - /// The array type of the new instance - array_type_id: ArrayTypeID, - /// The length of the array - length: i32, -} - -#[derive(Debug, JdwpReadable)] -pub struct NewInstanceReply { - // should always be Tag::Array - _tag: Tag, - /// The newly created array object - pub new_array: ArrayID, -} diff --git a/src/commands/class_loader_reference.rs b/src/commands/class_loader_reference.rs deleted file mode 100644 index d6c00d2..0000000 --- a/src/commands/class_loader_reference.rs +++ /dev/null @@ -1,29 +0,0 @@ -use super::jdwp_command; -use crate::{ - codec::JdwpWritable, - types::{ClassLoaderID, TaggedReferenceTypeID}, -}; - -/// Returns a list of all classes which this class loader has been requested to -/// load. -/// -/// This class loader is considered to be an initiating class loader for each -/// class in the returned list. The list contains each reference type defined -/// by this loader and any types for which loading was delegated by this class -/// loader to another class loader. -/// -/// The visible class list has useful properties with respect to the type -/// namespace. -/// -/// A particular type name will occur at most once in the list. -/// -/// Each field or variable declared with that type name in a class defined by -/// this class loader must be resolved to that single type. -/// -/// No ordering of the returned list is guaranteed. -#[jdwp_command(Vec, 14, 1)] -#[derive(Debug, JdwpWritable)] -pub struct VisibleClasses { - /// The class loader object ID - class_loader_id: ClassLoaderID, -} diff --git a/src/commands/class_object_reference.rs b/src/commands/class_object_reference.rs deleted file mode 100644 index 82804ab..0000000 --- a/src/commands/class_object_reference.rs +++ /dev/null @@ -1,13 +0,0 @@ -use super::jdwp_command; -use crate::{ - codec::JdwpWritable, - types::{ClassObjectID, TaggedReferenceTypeID}, -}; - -/// Returns the reference type reflected by this class object. -#[jdwp_command(TaggedReferenceTypeID, 17, 1)] -#[derive(Debug, JdwpWritable)] -pub struct ReflectedType { - /// The class object - class_object_id: ClassObjectID, -} diff --git a/src/commands/class_type.rs b/src/commands/class_type.rs new file mode 100644 index 0000000..682c0b7 --- /dev/null +++ b/src/commands/class_type.rs @@ -0,0 +1,157 @@ +use std::io::{self, Read}; + +use jdwp_macros::jdwp_command; + +use crate::{ + codec::JdwpReader, + enums::InvokeOptions, + types::{ClassID, FieldID, MethodID, TaggedObjectID, ThreadID, UntaggedValue, Value}, +}; + +use super::{JdwpReadable, JdwpWritable}; + +/// Returns the immediate superclass of a class. +/// +/// The return is null if the class is java.lang.Object. +#[jdwp_command(Option, 3, 1)] +#[derive(Debug, JdwpWritable)] +pub struct Superclass { + /// The class type ID. + class_id: ClassID, +} + +/// Sets the value of one or more static fields. +/// +/// Each field must be member of the class type or one of its superclasses, +/// superinterfaces, or implemented interfaces. +/// +/// Access control is not enforced; for example, the values of private fields +/// can be set. +/// +/// Final fields cannot be set. +/// +/// For primitive values, the value's type must match the field's type exactly. +/// +/// For object values, there must exist a widening reference conversion from the +/// value's type to thefield's type and the field's type must be loaded. +#[jdwp_command((), 3, 2)] +#[derive(Debug, JdwpWritable)] +pub struct SetValues { + /// The class type ID. + class_id: ClassID, + /// Fields to set and their values. + values: Vec<(FieldID, UntaggedValue)>, +} + +/// Invokes a static method. The method must be member of the class type or one +/// of its superclasses, superinterfaces, or implemented interfaces. Access +/// control is not enforced; for example, private methods can be invoked. +/// +/// The method invocation will occur in the specified thread. Method invocation +/// can occur only if the specified thread has been suspended by an event. +/// Method invocation is not supported when the target VM has been suspended by +/// the front-end. +/// +/// The specified method is invoked with the arguments in the specified argument +/// list. The method invocation is synchronous; the reply packet is not sent +/// until the invoked method returns in the target VM. The return value +/// (possibly the void value) is included in the reply packet. If the invoked +/// method throws an exception, the exception object ID is set in the reply +/// packet; otherwise, the exception object ID is null. +/// +/// For primitive arguments, the argument value's type must match the argument's +/// type exactly. For object arguments, there must exist a widening reference +/// conversion from the argument value's type to the argument's type and the +/// argument's type must be loaded. +/// +/// By default, all threads in the target VM are resumed while the method is +/// being invoked if they were previously suspended by an event or by command. +/// This is done to prevent the deadlocks that will occur if any of the threads +/// own monitors that will be needed by the invoked method. It is possible that +/// breakpoints or other events might occur during the invocation. Note, +/// however, that this implicit resume acts exactly like the ThreadReference +/// [resume](super::thread_reference::Resume) command, so if the +/// thread's suspend count is greater than 1, it will remain in a suspended +/// state during the invocation. By default, when the invocation completes, all +/// threads in the target VM are suspended, regardless their state before the +/// invocation. +/// +/// The resumption of other threads during the invoke can be prevented by +/// specifying the +/// [INVOKE_SINGLE_THREADED](crate::enums::InvokeOptions::SINGLE_THREADED) bit +/// flag in the options field; however, there is no protection against or +/// recovery from the deadlocks described above, so this option should be used +/// with great caution. Only the specified thread will be resumed (as described +/// for all threads above). Upon completion of a single threaded invoke, the +/// invoking thread will be suspended once again. Note that any threads started +/// during the single threaded invocation will not be suspended when the +/// invocation completes. +/// +/// If the target VM is disconnected during the invoke (for example, through the +/// VirtualMachine [dispose](super::virtual_machine::Dispose) command) the +/// method invocation continues. + +#[jdwp_command(3, 3)] +#[derive(Debug, JdwpWritable)] +pub struct InvokeMethod { + /// The class type ID. + class_id: ClassID, + /// The thread in which to invoke. + thread_id: ThreadID, + /// The method to invoke. + method_id: MethodID, + /// Arguments to the method. + arguments: Vec, + // Invocation options + options: InvokeOptions, +} + +// todo: figure out what happens with value if exception is thrown and make a +// enum reply type similar to NewInstanceReply +#[derive(Debug, JdwpReadable)] +pub struct InvokeMethodReply { + /// The returned value. + pub value: Value, + /// The thrown exception. + pub exception: Option, +} + +#[jdwp_command(3, 4)] +#[derive(Debug, JdwpWritable)] +pub struct NewInstance { + /// The class type ID. + class_id: ClassID, + /// The thread in which to invoke the constructor. + thread_id: ThreadID, + /// The constructor to invoke. + method_id: MethodID, + /// Arguments for the constructor method. + arguments: Vec, + // Constructor invocation options + options: InvokeOptions, +} + +#[derive(Debug)] +pub enum NewInstanceReply { + /// The newly created object. + NewObject(TaggedObjectID), + /// The thrown exception. + Exception(TaggedObjectID), +} + +// better types everyone +impl JdwpReadable for NewInstanceReply { + fn read(read: &mut JdwpReader) -> io::Result { + let new_object = Option::::read(read)?; + let exception = Option::::read(read)?; + + match (new_object, exception) { + (Some(new_object), None) => Ok(NewInstanceReply::NewObject(new_object)), + (None, Some(exception)) => Ok(NewInstanceReply::Exception(exception)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "Invalid NewInstance reply", + )), + } + } +} diff --git a/src/commands/event.rs b/src/commands/event.rs index 933e770..d699f77 100644 --- a/src/commands/event.rs +++ b/src/commands/event.rs @@ -450,7 +450,7 @@ event_io! { } #[jdwp_command((), 64, 100)] -#[derive(Debug, JdwpWritable, JdwpReadable)] +#[derive(Debug, JdwpReadable)] pub struct Composite { pub suspend_policy: SuspendPolicy, pub events: Vec, diff --git a/src/commands/method.rs b/src/commands/method.rs new file mode 100644 index 0000000..81f1e59 --- /dev/null +++ b/src/commands/method.rs @@ -0,0 +1,171 @@ +use jdwp_macros::JdwpReadable; + +use crate::{ + codec::JdwpWritable, + types::{MethodID, ReferenceTypeID}, +}; + +use super::jdwp_command; + +/// Returns line number information for the method, if present. +/// +/// The line table maps source line numbers to the initial code index of the +/// line. +/// +/// The line table is ordered by code index (from lowest to highest). +/// +/// The line number information is constant unless a new class definition is +/// installed using [RedefineClasses](super::virtual_machine::RedefineClasses). +#[jdwp_command(6, 1)] +#[derive(Debug, JdwpWritable)] +pub struct LineTable { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, +} + +#[derive(Debug, JdwpReadable)] +pub struct LineTableReply { + /// Lowest valid code index for the method, >=0, or -1 if the method is + /// native + pub start: i64, + /// Highest valid code index for the method, >=0, or -1 if the method is + /// native + pub end: i64, + /// The number of entries in the line table for this method. + pub lines: Vec, +} + +#[derive(Debug, JdwpReadable)] +pub struct Line { + /// Initial code index of the line, start <= lineCodeIndex < end + pub line_code_index: u64, + /// Line number. + pub line_number: u32, +} + +/// Returns variable information for the method. +/// +/// The variable table includes arguments and locals declared within the method. +/// For instance methods, the "this" reference is included in the table. Also, +/// synthetic variables may be present. +#[jdwp_command(6, 2)] +#[derive(Debug, JdwpWritable)] +pub struct VariableTable { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, +} + +#[derive(Debug, JdwpReadable)] +pub struct VariableTableReply { + /// The number of words in the frame used by arguments. Eight-byte arguments + /// use two words; all others use one. + pub arg_cnt: u32, + /// The variables. + pub variables: Vec, +} + +#[derive(Debug, JdwpReadable)] +pub struct Variable { + /// First code index at which the variable is visible. + /// + /// Used in conjunction with length. The variable can be get or set only + /// when the current codeIndex <= current frame code index < codeIndex + + /// length + pub code_index: u64, + /// The variable's name. + pub name: String, + /// The variable type's JNI signature. + pub signature: String, + /// Unsigned value used in conjunction with codeIndex. + /// + /// The variable can be get or set only when the current codeIndex <= + /// current frame code index < code index + length + pub length: u32, + /// The local variable's index in its frame + pub slot: u32, +} + +/// Retrieve the method's bytecodes as defined in The Java™ Virtual Machine +/// Specification. +/// +/// Requires `canGetBytecodes` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +#[jdwp_command(Vec, 6, 3)] +#[derive(Debug, JdwpWritable)] +pub struct Bytecodes { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, +} + +/// Determine if this method is obsolete. +/// +/// A method is obsolete if it has been replaced by a non-equivalent method +/// using the [RedefineClasses](super::virtual_machine::RedefineClasses) +/// command. The original and redefined methods are considered equivalent if +/// their bytecodes are the same except for indices into the constant pool and +/// the referenced constants are equal. +#[jdwp_command(bool, 6, 4)] +#[derive(Debug, JdwpWritable)] +pub struct IsObsolete { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, +} + +/// Returns variable information for the method, including generic signatures +/// for the variables. +/// +/// The variable table includes arguments and locals declared within the method. +/// For instance methods, the "this" reference is included in the table. Also, +/// synthetic variables may be present. Generic signatures are described in the +/// signature attribute section in The Java™ Virtual Machine Specification. +/// +/// Since JDWP version 1.5. +#[jdwp_command(6, 5)] +#[derive(Debug, JdwpWritable)] +pub struct VariableTableWithGeneric { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, +} + +#[derive(Debug, JdwpReadable)] +pub struct VariableTableWithGenericReply { + /// The number of words in the frame used by arguments. Eight-byte arguments + /// use two words; all others use one. + pub arg_cnt: u32, + /// The variables. + pub variables: Vec, +} + +#[derive(Debug, JdwpReadable)] +pub struct VariableWithGeneric { + /// First code index at which the variable is visible. + /// + /// Used in conjunction with length. The variable can be get or set only + /// when the current codeIndex <= current frame code index < codeIndex + + /// length + pub code_index: u64, + /// The variable's name. + pub name: String, + /// The variable type's JNI signature. + pub signature: String, + /// The variable type's generic signature or an empty string if there is + /// none. + pub generic_signature: String, + /// Unsigned value used in conjunction with codeIndex. + /// + /// The variable can be get or set only when the current codeIndex <= + /// current frame code index < code index + length + pub length: u32, + /// The local variable's index in its frame + pub slot: u32, +} diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 31bcc58..869417b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -4,32 +4,109 @@ use jdwp_macros::jdwp_command; use crate::{ codec::{JdwpReadable, JdwpWritable}, + types::ClassObjectID, CommandId, }; +use crate::{ + enums::Tag, + types::{ArrayID, ArrayTypeID, ClassLoaderID, ObjectID, TaggedReferenceTypeID}, +}; + +pub mod array_type { + use super::*; + + /// Creates a new array object of this type with a given length. + #[jdwp_command(4, 1)] + #[derive(Debug, JdwpWritable)] + pub struct NewInstance { + /// The array type of the new instance + array_type_id: ArrayTypeID, + /// The length of the array + length: i32, + } + + #[derive(Debug, JdwpReadable)] + pub struct NewInstanceReply { + // should always be Tag::Array + _tag: Tag, + /// The newly created array object + pub new_array: ArrayID, + } +} + +pub mod class_loader_reference { + use super::*; + + /// Returns a list of all classes which this class loader has been requested + /// to load. + /// + /// This class loader is considered to be an initiating class loader for + /// each class in the returned list. The list contains each reference + /// type defined by this loader and any types for which loading was + /// delegated by this class loader to another class loader. + /// + /// The visible class list has useful properties with respect to the type + /// namespace. + /// + /// A particular type name will occur at most once in the list. + /// + /// Each field or variable declared with that type name in a class defined + /// by this class loader must be resolved to that single type. + /// + /// No ordering of the returned list is guaranteed. + #[jdwp_command(Vec, 14, 1)] + #[derive(Debug, JdwpWritable)] + pub struct VisibleClasses { + /// The class loader object ID + class_loader_id: ClassLoaderID, + } +} + +pub mod class_object_reference { + use super::*; + + /// Returns the reference type reflected by this class object. + #[jdwp_command(TaggedReferenceTypeID, 17, 1)] + #[derive(Debug, JdwpWritable)] + pub struct ReflectedType { + /// The class object + class_object_id: ClassObjectID, + } +} + +pub mod string_reference { + use super::*; + + /// Returns the characters contained in the string. + #[jdwp_command(String, 10, 1)] + #[derive(Debug, JdwpWritable)] + pub struct Value { + /// The String object ID + string_object: ObjectID, + } +} + +/// This module is defined to mirror the JDWP command set, which is empty +pub mod field {} + +/// This module is defined to mirror the JDWP command set, which is empty +pub mod interface_type {} + pub mod array_reference; -pub mod array_type; -pub mod class_loader_reference; -pub mod class_object_reference; +pub mod class_type; pub mod event; pub mod event_request; +pub mod method; pub mod object_reference; pub mod reference_type; -pub mod string_reference; +pub mod stack_frame; pub mod thread_group_reference; pub mod thread_reference; pub mod virtual_machine; -pub mod field { - // no commands defined in this set -} - -pub mod interface_type { - // no commands defined in this set -} - -pub trait Command: JdwpWritable + Debug { +pub trait Command { const ID: CommandId; - type Output: JdwpReadable + Debug; + type Output; } diff --git a/src/commands/object_reference.rs b/src/commands/object_reference.rs index 7ca76db..52e3d3d 100644 --- a/src/commands/object_reference.rs +++ b/src/commands/object_reference.rs @@ -1,7 +1,13 @@ +use std::io::{self, Read}; + use super::jdwp_command; use crate::{ - codec::JdwpWritable, - types::{ObjectID, TaggedReferenceTypeID}, + codec::{JdwpReadable, JdwpReader, JdwpWritable}, + enums::InvokeOptions, + types::{ + ClassID, FieldID, ObjectID, TaggedObjectID, TaggedReferenceTypeID, ThreadID, UntaggedValue, + Value, + }, }; #[jdwp_command(TaggedReferenceTypeID, 9, 1)] @@ -10,3 +16,204 @@ pub struct ReferenceType { /// The object ID object: ObjectID, } + +/// Returns the value of one or more instance fields. +/// +/// Each field must be member of the object's type or one of its superclasses, +/// superinterfaces, or implemented interfaces. Access control is not enforced; +/// for example, the values of private fields can be obtained. +#[jdwp_command(Vec, 9, 2)] +#[derive(Debug, JdwpWritable)] +pub struct GetValues { + /// The object ID + object: ObjectID, + /// Fields to get + fields: Vec, +} + +/// Sets the value of one or more instance fields. +/// +/// Each field must be member of the object's type or one of its superclasses, +/// superinterfaces, or implemented interfaces. Access control is not enforced; +/// for example, the values of private fields can be set. For primitive values, +/// the value's type must match the field's type exactly. For object values, +/// there must be a widening reference conversion from the value's type to the +/// field's type and the field's type must be loaded. +#[jdwp_command((), 9, 3)] +#[derive(Debug, JdwpWritable)] +pub struct SetValues { + /// The object ID + object: ObjectID, + /// Fields and the values to set them to + fields: Vec<(FieldID, UntaggedValue)>, +} + +/// Returns monitor information for an object. +/// +/// All threads in the VM must be suspended. +/// +/// Requires `can_get_monitor_info` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +#[jdwp_command(9, 5)] +#[derive(Debug, JdwpWritable)] +pub struct MonitorInfo { + /// The object ID + object: ObjectID, +} + +#[derive(Debug, JdwpReadable)] +pub struct MonitorInfoReply { + /// The monitor owner, or null if it is not currently owned + pub owner: Option, + /// The number of times the monitor has been entered. + pub entry_count: i32, + /// The threads that are waiting for the monitor 0 if there is no current + /// owner + pub waiters: Vec, +} + +/// Invokes a instance method. +/// +/// The method must be member of the object's type or one of its superclasses, +/// superinterfaces, or implemented interfaces. Access control is not enforced; +/// for example, private methods can be invoked. +/// +/// The method invocation will occur in the specified thread. Method invocation +/// can occur only if the specified thread has been suspended by an event. +/// Method invocation is not supported when the target VM has been suspended by +/// the front-end. +/// +/// The specified method is invoked with the arguments in the specified argument +/// list. The method invocation is synchronous; the reply packet is not sent +/// until the invoked method returns in the target VM. The return value +/// (possibly the void value) is included in the reply packet. +/// +/// For primitive arguments, the argument value's type must match the argument's +/// type exactly. For object arguments, there must be a widening reference +/// conversion from the argument value's type to the argument's type and the +/// argument's type must be loaded. +/// +/// By default, all threads in the target VM are resumed while the method is +/// being invoked if they were previously suspended by an event or by a command. +/// This is done to prevent the deadlocks that will occur if any of the threads +/// own monitors that will be needed by the invoked method. It is possible that +/// breakpoints or other events might occur during the invocation. Note, +/// however, that this implicit resume acts exactly like the ThreadReference +/// resume command, so if the thread's suspend count is greater than 1, it will +/// remain in a suspended state during the invocation. By default, when the +/// invocation completes, all threads in the target VM are suspended, regardless +/// their state before the invocation. +/// +/// The resumption of other threads during the invoke can be prevented by +/// specifying the INVOKE_SINGLE_THREADED bit flag in the options field; +/// however, there is no protection against or recovery from the deadlocks +/// described above, so this option should be used with great caution. Only the +/// specified thread will be resumed (as described for all threads above). Upon +/// completion of a single threaded invoke, the invoking thread will be +/// suspended once again. Note that any threads started during the single +/// threaded invocation will not be suspended when the invocation completes. +/// +/// If the target VM is disconnected during the invoke (for example, through the +/// VirtualMachine dispose command) the method invocation continues. +#[jdwp_command(9, 6)] +#[derive(Debug, JdwpWritable)] +pub struct InvokeMethod { + /// The object ID + object: ObjectID, + /// The thread in which to invoke + thread: ThreadID, + /// The class type + class: ClassID, + /// The method to invoke + method: FieldID, + /// The arguments + arguments: Vec, + /// Invocation options + options: InvokeOptions, +} + +// todo move this to types and use it for static invokes as well +#[derive(Debug)] +pub enum InvokeMethodReply { + Value(Value), + Exception(TaggedObjectID), +} + +impl JdwpReadable for InvokeMethodReply { + fn read(read: &mut JdwpReader) -> io::Result { + let new_object = Option::::read(read)?; + let exception = Option::::read(read)?; + + match (new_object, exception) { + (Some(new_object), None) => Ok(InvokeMethodReply::Value(new_object)), + (None, Some(exception)) => Ok(InvokeMethodReply::Exception(exception)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "Invalid InvokeMethod reply", + )), + } + } +} + +/// Prevents garbage collection for the given object. +/// +/// By default all objects in back-end replies may be collected at any time the +/// target VM is running. A call to this command guarantees that the object will +/// not be collected. The [EnableCollection] command can be used to allow +/// collection once again. +/// +/// Note that while the target VM is suspended, no garbage collection will occur +/// because all threads are suspended. The typical examination of variables, +/// fields, and arrays during the suspension is safe without explicitly +/// disabling garbage collection. +/// +/// This method should be used sparingly, as it alters the pattern of garbage +/// collection in the target VM and, consequently, may result in application +/// behavior under the debugger that differs from its non-debugged behavior. +#[jdwp_command((), 9, 7)] +#[derive(Debug, JdwpWritable)] +pub struct DisableCollection { + /// The object ID + object: ObjectID, +} + +/// Permits garbage collection for this object. +/// +/// By default all objects returned by JDWP may become unreachable in the target +/// VM, and hence may be garbage collected. A call to this command is necessary +/// only if garbage collection was previously disabled with the +/// [DisableCollection] command. +#[jdwp_command((), 9, 8)] +#[derive(Debug, JdwpWritable)] +pub struct EnableCollection { + /// The object ID + object: ObjectID, +} + +/// Determines whether an object has been garbage collected in the target VM. +#[jdwp_command(bool, 9, 9)] +#[derive(Debug, JdwpWritable)] +pub struct IsCollected { + /// The object ID + object: ObjectID, +} + +/// Returns objects that directly reference this object. Only objects that are +/// reachable for the purposes of garbage collection are returned. Note that an +/// object can also be referenced in other ways, such as from a local variable +/// in a stack frame, or from a JNI global reference. Such non-object referrers +/// are not returned by this command. +/// +/// Since JDWP version 1.6. +/// +/// Requires `can_get_instance_info` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +#[jdwp_command(Vec, 9, 10)] +#[derive(Debug, JdwpWritable)] +pub struct ReferringObjects { + /// The object ID + object: ObjectID, + /// Maximum number of referring objects to return. Must be non-negative. If + /// zero, all referring objects are returned. + max_referrers: u32, +} diff --git a/src/commands/reference_type.rs b/src/commands/reference_type.rs index 05169d4..a4b8751 100644 --- a/src/commands/reference_type.rs +++ b/src/commands/reference_type.rs @@ -335,9 +335,9 @@ pub struct ClassFileVersion { #[derive(Debug, JdwpReadable)] pub struct ClassFileVersionReply { /// Major version number - pub major_version: i32, + pub major_version: u32, /// Minor version number - pub minor_version: i32, + pub minor_version: u32, } /// Return the raw bytes of the constant pool in the format of the @@ -361,14 +361,14 @@ pub struct ConstantPoolReply { /// Format in The Java™ Virtual Machine Specification. pub count: u32, /// Raw bytes of constant pool - pub cpbytes: Vec, + pub bytes: Vec, } // special debug so that trace logs dont take a quadrillion lines impl Debug for ConstantPoolReply { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let hex_bytes = self - .cpbytes + .bytes .iter() .map(|b| format!("{:02x}", b)) .collect::(); @@ -383,7 +383,7 @@ impl Debug for ConstantPoolReply { f.debug_struct("ConstantPoolReply") .field("count", &self.count) - .field("cpbytes", &Unquoted(hex_bytes)) + .field("bytes", &Unquoted(hex_bytes)) .finish() } } diff --git a/src/commands/stack_frame.rs b/src/commands/stack_frame.rs new file mode 100644 index 0000000..a2f1537 --- /dev/null +++ b/src/commands/stack_frame.rs @@ -0,0 +1,82 @@ +use crate::{ + codec::JdwpWritable, + enums::Tag, + types::{FrameID, TaggedObjectID, ThreadID, Value}, +}; + +use super::jdwp_command; + +/// Returns the value of one or more local variables in a given frame. +/// +/// Each variable must be visible at the frame's code index. +/// +/// Even if local variable information is not available, values can be retrieved +/// if the front-end is able to determine the correct local variable index. +/// (Typically, this index can be determined for method arguments from the +/// method signature without access to the local variable table information.) +#[jdwp_command(Vec, 16, 1)] +#[derive(Debug, JdwpWritable)] +pub struct GetValues { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, + /// Local variable indices and types to get. + pub slots: Vec<(u32, Tag)>, +} + +/// Sets the value of one or more local variables. +/// +/// Each variable must be visible at the current frame code index. For primitive +/// values, the value's type must match the variable's type exactly. For object +/// values, there must be a widening reference conversion from the value's type +/// to thevariable's type and the variable's type must be loaded. +/// +/// Even if local variable information is not available, values can be set, if +/// the front-end is able to determine the correct local variable index. +/// (Typically, thisindex can be determined for method arguments from the method +/// signature without access to the local variable table information.) +#[jdwp_command((), 16, 2)] +#[derive(Debug, JdwpWritable)] +pub struct SetValues { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, + /// Local variable indices and values to set. + pub slots: Vec<(u32, Value)>, +} + +/// Returns the value of the 'this' reference for this frame. +/// +/// If the frame's method is static or native, the reply will contain the null +/// object reference. +#[jdwp_command(Option, 16, 3)] +#[derive(Debug, JdwpWritable)] +pub struct ThisObject { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, +} + +/// Pop the top-most stack frames of the thread stack, up to, and including +/// 'frame'. The thread must be suspended to perform this command. The top-most +/// stack frames are discarded and the stack frame previous to 'frame' becomes +/// the current frame. The operand stack is restored -- the argument values are +/// added back and if the invoke was not invokestatic, objectref is added back +/// as well. The Java virtual machine program counter is restored to the opcode +/// of the invoke instruction. +/// +/// Since JDWP version 1.4. +/// +/// Requires `canPopFrames` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +#[jdwp_command((), 16, 4)] +#[derive(Debug, JdwpWritable)] +pub struct PopFrames { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, +} diff --git a/src/commands/string_reference.rs b/src/commands/string_reference.rs deleted file mode 100644 index 99849e3..0000000 --- a/src/commands/string_reference.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::jdwp_command; -use crate::{codec::JdwpWritable, types::ObjectID}; - -/// Returns the characters contained in the string. -#[jdwp_command(String, 10, 1)] -#[derive(Debug, JdwpWritable)] -pub struct Value { - /// The String object ID - string_object: ObjectID, -} diff --git a/src/commands/thread_reference.rs b/src/commands/thread_reference.rs index b4476fe..bb28400 100644 --- a/src/commands/thread_reference.rs +++ b/src/commands/thread_reference.rs @@ -1,6 +1,10 @@ use jdwp_macros::jdwp_command; -use crate::{codec::JdwpWritable, types::ThreadID}; +use crate::{ + codec::{JdwpReadable, JdwpWritable}, + enums::{SuspendStatus, ThreadStatus}, + types::{FrameID, Location, TaggedObjectID, ThreadGroupID, ThreadID, Value}, +}; /// Returns the thread name. #[jdwp_command(String, 11, 1)] @@ -12,26 +16,226 @@ pub struct Name { /// Suspends the thread. /// -/// Unlike java.lang.Thread.suspend(), suspends of both the virtual machine and -/// individual threads are counted. Before a thread will run again, it must be -/// resumed the same number of times it has been suspended. +/// Unlike `java.lang.Thread.suspend()`, suspends of both the virtual machine +/// and individual threads are counted. Before a thread will run again, it must +/// be resumed the same number of times it has been suspended. /// /// Suspending single threads with command has the same dangers -/// java.lang.Thread.suspend(). If the suspended thread holds a monitor needed +/// `java.lang.Thread.suspend()`. If the suspended thread holds a monitor needed /// by another running thread, deadlock is possible in the target VM (at least /// until the suspended thread is resumed again). /// /// The suspended thread is guaranteed to remain suspended until resumed through /// one of the JDI resume methods mentioned above; the application in the target -/// VM cannot resume the suspended thread through {@link -/// java.lang.Thread#resume}. +/// VM cannot resume the suspended thread through `java.lang.Thread.resume()`. /// -/// Note that this doesn't change the status of the thread (see the ThreadStatus -/// command.) For example, if it was Running, it will still appear running to -/// other threads. +/// Note that this doesn't change the status of the thread (see the +/// [ThreadStatus] command.) For example, if it was Running, it will still +/// appear running to other threads. #[jdwp_command((), 11, 2)] #[derive(Debug, JdwpWritable)] pub struct Suspend { /// The thread object ID. pub thread: ThreadID, } + +/// Resumes the execution of a given thread. +/// +/// If this thread was not previously suspended by the front-end, calling this +/// command has no effect. Otherwise, the count of pending suspends on this +/// thread is decremented. If it is decremented to 0, the thread will continue +/// to execute. +#[jdwp_command((), 11, 3)] +#[derive(Debug, JdwpWritable)] +pub struct Resume { + /// The thread object ID. + pub thread: ThreadID, +} + +/// Returns the current status of a thread. +/// +/// The thread status reply indicates the thread status the last time it was +/// running. the suspend status provides information on the thread's suspension, +/// if any. +#[jdwp_command((ThreadStatus, SuspendStatus), 11, 4)] +#[derive(Debug, JdwpWritable)] +pub struct Status { + /// The thread object ID. + pub thread: ThreadID, +} + +/// Returns the thread group that contains a given thread. +#[jdwp_command(ThreadGroupID, 11, 5)] +#[derive(Debug, JdwpWritable)] +pub struct ThreadGroup { + /// The thread object ID. + pub thread: ThreadID, +} + +/// Returns the current call stack of a suspended thread. +/// +/// The sequence of frames starts with the currently executing frame, followed +/// by its caller, and so on. The thread must be suspended, and the returned +/// frameID is valid only while the thread is suspended. +#[jdwp_command(Vec<(FrameID, Location)>, 11, 6)] +#[derive(Debug, JdwpWritable)] +pub struct Frames { + /// The thread object ID. + pub thread: ThreadID, + /// The index of the first frame to retrieve. + pub start_frame: u32, + /// The count of frames to retrieve (-1 means all remaining). + pub length: i32, +} + +/// Returns the count of frames on this thread's stack. +/// +/// The thread must be suspended, and the returned count is valid only while the +/// thread is suspended. +/// +/// Returns [ThreadNotSuspended](crate::enums::ErrorCode::ThreadNotSuspended) if +/// not suspended. +#[jdwp_command(u32, 11, 7)] +#[derive(Debug, JdwpWritable)] +pub struct FrameCount { + /// The thread object ID. + pub thread: ThreadID, +} + +/// Returns the objects whose monitors have been entered by this thread. +/// +/// The thread must be suspended, and the returned information is relevant only +/// while the thread is suspended. +/// +/// Requires `can_get_owned_monitor_info` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +#[jdwp_command(Vec, 11, 8)] +#[derive(Debug, JdwpWritable)] +pub struct OwnedMonitors { + /// The thread object ID. + pub thread: ThreadID, +} + +/// Returns the object, if any, for which this thread is waiting. +/// +/// The thread may be waiting to enter a monitor, or it may be waiting, via the +/// `java.lang.Object.wait` method, for another thread to invoke the notify +/// method. The thread must be suspended, and the returned information is +/// relevant only while the thread is suspended. +/// +/// Requires `can_get_current_contended_monitor` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +#[jdwp_command(Option, 11, 9)] +#[derive(Debug, JdwpWritable)] +pub struct CurrentContendedMonitor { + /// The thread object ID. + pub thread: ThreadID, +} + +/// Stops the thread with an asynchronous exception, as if done by +/// `java.lang.Thread.stop` +#[jdwp_command((), 11, 10)] +#[derive(Debug, JdwpWritable)] +pub struct Stop { + /// The thread object ID. + pub thread: ThreadID, + /// Asynchronous exception. + /// + /// This object must be an instance of `java.lang.Throwable` or a subclass + pub throwable: TaggedObjectID, +} + +/// Interrupt the thread, as if done by `java.lang.Thread.interrupt` +#[jdwp_command((), 11, 11)] +#[derive(Debug, JdwpWritable)] +pub struct Interrupt { + /// The thread object ID. + pub thread: ThreadID, +} + +/// Get the suspend count for this thread. +/// +/// The suspend count is the number of times the thread has been suspended +/// through the thread-level or VM-level suspend commands without a +/// corresponding resume +#[jdwp_command(u32, 11, 12)] +#[derive(Debug, JdwpWritable)] +pub struct SuspendCount { + /// The thread object ID. + pub thread: ThreadID, + /// The number of times the thread has been suspended. + pub suspend_count: u32, +} + +/// Returns monitor objects owned by the thread, along with stack depth at which +/// the monitor was acquired. +/// +/// Returns stack depth of -1 if the implementation cannot determine the stack +/// depth (e.g., for monitors acquired by JNI MonitorEnter). The thread must be +/// suspended, and the returned information is relevant only while the thread is +/// suspended. +/// +/// Requires `can_get_monitor_frame_info` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +/// +/// Since JDWP version 1.6. +#[jdwp_command(Vec, 11, 13)] +#[derive(Debug, JdwpWritable)] +pub struct OwnedMonitorsStackDepthInfo { + /// The thread object ID. + pub thread: ThreadID, +} + +#[derive(Debug, JdwpReadable)] +pub struct Monitor { + /// An owned monitor + pub monitor: TaggedObjectID, + /// Stack depth location where monitor was acquired + pub stack_depth: i32, +} + +/// Force a method to return before it reaches a return statement. +/// +/// The method which will return early is referred to as the called method. The +/// called method is the current method (as defined by the Frames section in The +/// Java™ Virtual Machine Specification) for the specified thread at the time +/// this command is received. +/// +/// The specified thread must be suspended. The return occurs when execution of +/// Java programming language code is resumed on this thread. Between sending +/// this command and resumption of thread execution, the state of the stack is +/// undefined. +/// +/// No further instructions are executed in the called method. Specifically, +/// finally blocks are not executed. Note: this can cause inconsistent states in +/// the application. +/// +/// A lock acquired by calling the called method (if it is a synchronized +/// method) and locks acquired by entering synchronized blocks within the called +/// method are released. Note: this does not apply to JNI locks or +/// java.util.concurrent.locks locks. +/// +/// Events, such as [MethodExit](super::event::MethodExit), are generated as +/// they would be in a normal return. +/// +/// The called method must be a non-native Java programming language method. +/// Forcing return on a thread with only one frame on the stack causes the +/// thread to exit when resumed. +/// +/// For void methods, the value must be a void value. For methods that return +/// primitive values, the value's type must match the return type exactly. For +/// object values, there must be a widening reference conversion from the +/// value's type to the return type type and the return type must be loaded. +/// +/// Since JDWP version 1.6. Requires `can_force_early_return` capability - see +/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). +#[jdwp_command((), 11, 14)] +#[derive(Debug, JdwpWritable)] +pub struct ForceEarlyReturn { + /// The thread object ID. + pub thread: ThreadID, + /// The frame object ID. + pub frame: FrameID, + /// The value to return. + pub value: Value, +} diff --git a/src/commands/virtual_machine.rs b/src/commands/virtual_machine.rs index 826fa50..41f5af3 100644 --- a/src/commands/virtual_machine.rs +++ b/src/commands/virtual_machine.rs @@ -118,15 +118,15 @@ pub struct IDSizes; #[derive(Debug, Clone, JdwpReadable)] pub struct IDSizeInfo { /// field_id size in bytes - pub field_id_size: i32, + pub field_id_size: u32, /// method_id size in bytes - pub method_id_size: i32, + pub method_id_size: u32, /// object_id size in bytes - pub object_id_size: i32, + pub object_id_size: u32, /// reference_type_id size in bytes - pub reference_type_id_size: i32, + pub reference_type_id_size: u32, /// frame_id size in bytes - pub frame_id_size: i32, + pub frame_id_size: u32, } /// Suspends the execution of the application running in the target VM. @@ -228,18 +228,6 @@ pub struct ClassPathsReply { pub bootclasspaths: Vec, } -#[derive(Debug, JdwpWritable)] -pub struct ObjectRef { - object: ObjectID, - ref_cnt: i32, -} - -impl ObjectRef { - pub fn new(object: ObjectID, ref_cnt: i32) -> Self { - Self { object, ref_cnt } - } -} - /// Releases a list of object IDs. /// /// For each object in the list, the following applies. @@ -273,7 +261,7 @@ impl ObjectRef { #[jdwp_command((), 1, 14)] #[derive(Debug, JdwpWritable)] pub struct DisposeObjects { - requests: Vec, + requests: Vec<(ObjectID, u32)>, } /// Tells the target VM to stop sending events. Events are not discarded; they @@ -411,20 +399,6 @@ impl Debug for CapabilitiesNewReply { } } -#[derive(Debug, JdwpWritable)] -pub struct RedefiningClass { - /// The reference type. - ref_type: ReferenceTypeID, - /// Bytes defining class in JVM class file format. - bytes: Vec, -} - -impl RedefiningClass { - pub fn new(ref_type: ReferenceTypeID, bytes: Vec) -> Self { - Self { ref_type, bytes } - } -} - /// Installs new class definitions. /// /// If there are active stack frames in methods of the redefined classes in the @@ -448,7 +422,7 @@ impl RedefiningClass { #[jdwp_command((), 1, 18)] #[derive(Debug, JdwpWritable)] pub struct RedefineClasses { - classes: Vec, + classes: Vec<(ReferenceTypeID, Vec)>, } /// Set the default stratum. Requires `can_set_default_stratum` capability - diff --git a/src/enums.rs b/src/enums.rs index 33d25ea..0ba96cc 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -242,11 +242,12 @@ readable_enum! { } bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct InvokeOptions: u32 { - /// otherwise, normal virtual invoke (instance methods only) - const VERIFIED = 1; /// otherwise, all threads started - const PREPARED = 2; + const SINGLE_THREADED = 0x01; + /// otherwise, normal virtual invoke (instance methods only) + const NONVIRTUAL = 0x02; } } diff --git a/src/types.rs b/src/types.rs index 4f39fea..ee306a9 100644 --- a/src/types.rs +++ b/src/types.rs @@ -294,9 +294,9 @@ impl Value { /// Used in places where JDWP specifies an `untagged-value` type and expects /// no tag since it should be derived from context. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct Untagged(Value); +pub struct UntaggedValue(Value); -impl Deref for Untagged { +impl Deref for UntaggedValue { type Target = Value; fn deref(&self) -> &Self::Target { @@ -304,7 +304,7 @@ impl Deref for Untagged { } } -impl JdwpWritable for Untagged { +impl JdwpWritable for UntaggedValue { fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { match self.0 { Value::Void => Ok(()), @@ -565,7 +565,7 @@ pub struct Location { index: u64, } -macro_rules! optional_tag_impl { +macro_rules! optional_impl { ($($tpe:ident),* $(,)?) => { $( impl JdwpReadable for Option<$tpe> { @@ -591,7 +591,7 @@ macro_rules! optional_tag_impl { }; } -optional_tag_impl![Location, TaggedObjectID]; +optional_impl![Value, TaggedObjectID, Location]; /// An opaque type for the request id, which is represented in JDWP docs as just /// a raw integer and exists only here in Rust similar to all the other IDs. diff --git a/tests/reference_type.rs b/tests/reference_type.rs index 949b771..41498e9 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -1,7 +1,8 @@ -use std::{assert_eq, io::Cursor}; +use std::{assert_eq, fmt::Debug, io::Cursor}; use jdwp::{ client::JdwpClient, + codec::{JdwpReadable, JdwpWritable}, commands::{ class_object_reference::ReflectedType, reference_type::{ @@ -25,14 +26,18 @@ const ARRAY_CLS: &str = "[I"; const CASES: &[&str] = &[OUR_CLS, "Ljava/lang/String;", "Ljava/util/List;", ARRAY_CLS]; -fn get_responses( +fn get_responses( client: &mut JdwpClient, signatures: &[&str], new: fn(ReferenceTypeID) -> C, -) -> Result> { +) -> Result> +where + C: Command + JdwpWritable + Debug, + C::Output: JdwpReadable + Debug, +{ signatures.try_map(|item| { - let type_id = client.send(ClassesBySignature::new(*item))?[0].type_id; - client.send(new(*type_id)) + let id = client.send(ClassesBySignature::new(*item))?[0].type_id; + client.send(new(*id)) }) } @@ -293,8 +298,8 @@ fn source_file() -> Result { ] "###); - let type_id = client.send(ClassesBySignature::new(ARRAY_CLS))?[0].type_id; - let array_source_file = client.send(SourceFile::new(*type_id)); + let id = client.send(ClassesBySignature::new(ARRAY_CLS))?[0].type_id; + let array_source_file = client.send(SourceFile::new(*id)); assert_snapshot!(array_source_file, @r###" Err( @@ -473,7 +478,7 @@ fn constant_pool() -> Result { let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; let constant_pool = client.send(ConstantPool::new(*id))?; - let mut reader = Cursor::new(constant_pool.cpbytes); + let mut reader = Cursor::new(constant_pool.bytes); // pfew lol why did I bother so much let items = ConstantPoolItem::read_all(constant_pool.count, &mut reader)?; diff --git a/tests/virtual_machine.rs b/tests/virtual_machine.rs index ad2391e..d8f1cf5 100644 --- a/tests/virtual_machine.rs +++ b/tests/virtual_machine.rs @@ -1,3 +1,5 @@ +use std::assert_eq; + use jdwp::{ client::ClientError, commands::{string_reference::Value, thread_reference, virtual_machine::*}, @@ -360,17 +362,12 @@ fn set_default_stratum() -> Result { fn instance_counts() -> Result { let mut client = common::launch_and_attach("basic")?; - let type_id = client.send(ClassesBySignature::new("LBasic;"))?[0].type_id; - let type_id_2 = client.send(ClassesBySignature::new("LBasic$NestedClass;"))?[0].type_id; + let id = client.send(ClassesBySignature::new("LBasic;"))?[0].type_id; + let id2 = client.send(ClassesBySignature::new("LBasic$NestedClass;"))?[0].type_id; - let counts = client.send(InstanceCounts::new(vec![*type_id, *type_id_2]))?; + let counts = client.send(InstanceCounts::new(vec![*id, *id2]))?; - assert_snapshot!(counts, @r###" - [ - 2, - 0, - ] - "###); + assert_eq!(counts, [2, 0]); Ok(()) } From fa9be24cd1a436ae367f2f0b82cfd044399f1725 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Mon, 29 May 2023 04:29:41 +0300 Subject: [PATCH 2/9] Thread group ref tests, interface InvokeMethod, other minor fixes --- .vscode/extensions.json | 9 +++ src/commands/class_type.rs | 16 +----- src/commands/mod.rs | 23 ++++++-- src/commands/object_reference.rs | 31 +--------- src/commands/virtual_machine.rs | 3 +- src/types.rs | 98 ++++++++++++++++++++------------ tests/common.rs | 23 -------- tests/reference_type.rs | 20 ++++--- tests/thread_group_reference.rs | 66 +++++++++++++++++++++ tests/virtual_machine.rs | 40 ++++--------- 10 files changed, 184 insertions(+), 145 deletions(-) create mode 100644 .vscode/extensions.json create mode 100644 tests/thread_group_reference.rs diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..08b9e5f --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "mitsuhiko.insta", + "rust-lang.rust-analyzer", + "vadimcn.vscode-lldb", + "serayuzgur.crates", + "mkhl.direnv" + ] +} \ No newline at end of file diff --git a/src/commands/class_type.rs b/src/commands/class_type.rs index 682c0b7..ad791d0 100644 --- a/src/commands/class_type.rs +++ b/src/commands/class_type.rs @@ -2,11 +2,7 @@ use std::io::{self, Read}; use jdwp_macros::jdwp_command; -use crate::{ - codec::JdwpReader, - enums::InvokeOptions, - types::{ClassID, FieldID, MethodID, TaggedObjectID, ThreadID, UntaggedValue, Value}, -}; +use crate::{codec::JdwpReader, enums::InvokeOptions, types::*}; use super::{JdwpReadable, JdwpWritable}; @@ -106,16 +102,6 @@ pub struct InvokeMethod { options: InvokeOptions, } -// todo: figure out what happens with value if exception is thrown and make a -// enum reply type similar to NewInstanceReply -#[derive(Debug, JdwpReadable)] -pub struct InvokeMethodReply { - /// The returned value. - pub value: Value, - /// The thrown exception. - pub exception: Option, -} - #[jdwp_command(3, 4)] #[derive(Debug, JdwpWritable)] pub struct NewInstance { diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 869417b..541e80b 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -8,10 +8,7 @@ use crate::{ CommandId, }; -use crate::{ - enums::Tag, - types::{ArrayID, ArrayTypeID, ClassLoaderID, ObjectID, TaggedReferenceTypeID}, -}; +use crate::{enums::Tag, types::*}; pub mod array_type { use super::*; @@ -90,8 +87,22 @@ pub mod string_reference { /// This module is defined to mirror the JDWP command set, which is empty pub mod field {} -/// This module is defined to mirror the JDWP command set, which is empty -pub mod interface_type {} +pub mod interface_type { + use super::*; + + #[jdwp_command(5, 1)] + #[derive(Debug, JdwpWritable)] + pub struct InvokeMethod { + /// The interface type ID + interface_id: InterfaceID, + /// The thread in which to invoke + thread_id: ThreadID, + /// The method to invoke + method_id: MethodID, + /// Invocation options + arguments: Vec, + } +} pub mod array_reference; pub mod class_type; diff --git a/src/commands/object_reference.rs b/src/commands/object_reference.rs index 52e3d3d..b7e21e5 100644 --- a/src/commands/object_reference.rs +++ b/src/commands/object_reference.rs @@ -1,12 +1,10 @@ -use std::io::{self, Read}; - use super::jdwp_command; use crate::{ - codec::{JdwpReadable, JdwpReader, JdwpWritable}, + codec::{JdwpReadable, JdwpWritable}, enums::InvokeOptions, types::{ - ClassID, FieldID, ObjectID, TaggedObjectID, TaggedReferenceTypeID, ThreadID, UntaggedValue, - Value, + ClassID, FieldID, InvokeMethodReply, ObjectID, TaggedObjectID, TaggedReferenceTypeID, + ThreadID, UntaggedValue, Value, }, }; @@ -132,29 +130,6 @@ pub struct InvokeMethod { options: InvokeOptions, } -// todo move this to types and use it for static invokes as well -#[derive(Debug)] -pub enum InvokeMethodReply { - Value(Value), - Exception(TaggedObjectID), -} - -impl JdwpReadable for InvokeMethodReply { - fn read(read: &mut JdwpReader) -> io::Result { - let new_object = Option::::read(read)?; - let exception = Option::::read(read)?; - - match (new_object, exception) { - (Some(new_object), None) => Ok(InvokeMethodReply::Value(new_object)), - (None, Some(exception)) => Ok(InvokeMethodReply::Exception(exception)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - "Invalid InvokeMethod reply", - )), - } - } -} - /// Prevents garbage collection for the given object. /// /// By default all objects in back-end replies may be collected at any time the diff --git a/src/commands/virtual_machine.rs b/src/commands/virtual_machine.rs index 41f5af3..e459c1d 100644 --- a/src/commands/virtual_machine.rs +++ b/src/commands/virtual_machine.rs @@ -403,7 +403,8 @@ impl Debug for CapabilitiesNewReply { /// /// If there are active stack frames in methods of the redefined classes in the /// target VM then those active frames continue to run the bytecodes of the -/// original method. These methods are considered obsolete - see IsObsolete. +/// original method. These methods are considered obsolete - see +/// [IsObsolete](super::method::IsObsolete). /// /// The methods in the redefined classes will be used for new invokes in the /// target VM. The original method ID refers to the redefined method. diff --git a/src/types.rs b/src/types.rs index ee306a9..4b7e992 100644 --- a/src/types.rs +++ b/src/types.rs @@ -23,9 +23,10 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; /// collection of the object. /// Any attempt to access a a garbage collected object with its object ID will /// result in the INVALID_OBJECT error code. -/// Garbage collection can be disabled with the DisableCollection command, -/// but it is not usually necessary to do so. -#[derive(Copy, Clone, PartialEq, Eq)] +/// Garbage collection can be disabled with the +/// [DisableCollection](super::commands::object_reference::DisableCollection) +/// command, but it is not usually necessary to do so. +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ObjectID(u64); /// Uniquely identifies a method in some class in the target VM. @@ -38,7 +39,7 @@ pub struct ObjectID(u64); /// /// The [ReferenceTypeID] can identify either the declaring type of the method /// or a subtype. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct MethodID(u64); /// Uniquely identifies a field in some class in the target VM. @@ -51,7 +52,7 @@ pub struct MethodID(u64); /// /// The [ReferenceTypeID] can identify either the declaring type of the field /// or a subtype. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct FieldID(u64); /// Uniquely identifies a frame in the target VM. @@ -60,7 +61,7 @@ pub struct FieldID(u64); /// only within a given thread). /// /// The [FrameID] need only be valid during the time its thread is suspended. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct FrameID(u64); /// Uniquely identifies a reference type in the target VM. @@ -72,7 +73,7 @@ pub struct FrameID(u64); /// commands and replies throughout its lifetime A [ReferenceTypeID] is not /// reused to identify a different reference type, regardless of whether the /// referenced class has been unloaded. -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ReferenceTypeID(u64); macro_rules! ids { @@ -129,56 +130,56 @@ macro_rules! ids { } ids! { - object_id_size: ObjectID, - method_id_size: MethodID, field_id_size: FieldID, - frame_id_size: FrameID, + method_id_size: MethodID, + object_id_size: ObjectID, reference_type_id_size: ReferenceTypeID, + frame_id_size: FrameID, } /// Uniquely identifies an object in the target VM that is known to be a thread. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ThreadID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a thread /// group. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ThreadGroupID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a string /// object. /// /// Note: this is very different from string, which is a value. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct StringID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a class /// loader object. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ClassLoaderID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a class /// object. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ClassObjectID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be an array. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ArrayID(ObjectID); /// Uniquely identifies a reference type in the target VM that is known to be /// a class type. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ClassID(ReferenceTypeID); /// Uniquely identifies a reference type in the target VM that is known to be /// an interface type. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct InterfaceID(ReferenceTypeID); /// Uniquely identifies a reference type in the target VM that is known to be /// an array type. -#[derive(Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ArrayTypeID(ReferenceTypeID); macro_rules! wrapper_ids { @@ -356,7 +357,7 @@ tagged_io! { { Self::Void => Ok(()) } } -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum TaggedObjectID { /// an array object Array(ArrayID), @@ -491,7 +492,7 @@ tagged_io! { /// This construct is not separated into a separate value type in JDWP spec and /// exists only here in Rust, in JDWP it's usually represented by a pair of /// [TypeTag] and [ReferenceTypeID] values. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum TaggedReferenceTypeID { /// a class reference Class(ClassID), @@ -558,7 +559,7 @@ tagged_io! { /// identifies a class or an interface. Almost all locations are within /// classes, but it is possible to have executable code in the static /// initializer of an interface. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct Location { reference_id: TaggedReferenceTypeID, method_id: MethodID, @@ -595,7 +596,7 @@ optional_impl![Value, TaggedObjectID, Location]; /// An opaque type for the request id, which is represented in JDWP docs as just /// a raw integer and exists only here in Rust similar to all the other IDs. -#[derive(Debug, Copy, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct RequestID(i32); impl RequestID { @@ -627,14 +628,14 @@ impl RequestID { /// In either case subsequent events are never reported for this request. /// /// This modifier can be used with any event kind. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct Count { /// Count before event. One for one-off pub count: i32, } /// Conditional on expression -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct Conditional { /// For the future pub expr_id: i32, @@ -642,7 +643,7 @@ pub struct Conditional { /// Restricts reported events to those in the given thread. /// This modifier can be used with any event kind except for class unload. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ThreadOnly { /// Required thread pub thread: ThreadID, @@ -663,7 +664,7 @@ pub struct ThreadOnly { /// /// This modifier can be used with any event kind except class unload, thread /// start, and thread end. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ClassOnly { /// Required class pub class: ReferenceTypeID, @@ -683,7 +684,7 @@ pub struct ClassOnly { /// /// This modifier can be used with any event kind except thread start and /// thread end. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ClassMatch { /// Required class pattern. /// @@ -707,7 +708,7 @@ pub struct ClassMatch { /// /// This modifier can be used with any event kind except thread start and /// thread end. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ClassExclude { /// Disallowed class pattern. /// @@ -721,7 +722,7 @@ pub struct ClassExclude { /// /// This modifier can be used with breakpoint, field access, field /// modification, step, and exception event kinds. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct LocationOnly { /// Required location pub location: Location, @@ -731,7 +732,7 @@ pub struct LocationOnly { /// uncaught. /// /// This modifier can be used with exception event kinds only. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct ExceptionOnly { /// Exception to report. `None` means report exceptions of all types. /// @@ -754,7 +755,7 @@ pub struct ExceptionOnly { /// /// This modifier can be used with field access and field modification event /// kinds only. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct FieldOnly { /// Type in which field is declared pub declaring: ReferenceTypeID, @@ -766,7 +767,7 @@ pub struct FieldOnly { /// constraints. /// /// This modifier can be used with step event kinds only. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct Step { /// Thread in which to step pub thread: ThreadID, @@ -785,7 +786,7 @@ pub struct Step { /// class unload, thread start, and thread end. /// /// Introduced in JDWP version 1.4. -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct InstanceOnly { /// Required 'this' object pub instance: ObjectID, @@ -803,7 +804,7 @@ pub struct InstanceOnly { /// /// Requires the `can_use_source_name_filters` capability - see /// [CapabilitiesNew](crate::commands::virtual_machine::CapabilitiesNew). -#[derive(Debug, Clone, PartialEq, Eq, JdwpReadable, JdwpWritable)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct SourceNameMatch { /// Required source name pattern. /// Matches are limited to exact matches of the given pattern and matches @@ -812,7 +813,7 @@ pub struct SourceNameMatch { pub source_name_pattern: String, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Modifier { Count(Count), Conditional(Conditional), @@ -833,3 +834,28 @@ tagged_io! { Count, Conditional, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, LocationOnly, ExceptionOnly, FieldOnly, Step, InstanceOnly, SourceNameMatch {} {} } + +/// A response from 3 types of invoke method commands: virtual, static and +/// interface static. The response is either a value or an exception - we model +/// it with a enum with a custom read implementation for better ergonomics. +#[derive(Debug)] +pub enum InvokeMethodReply { + Value(Value), + Exception(TaggedObjectID), +} + +impl JdwpReadable for InvokeMethodReply { + fn read(read: &mut JdwpReader) -> io::Result { + let new_object = Option::::read(read)?; + let exception = Option::::read(read)?; + + match (new_object, exception) { + (Some(new_object), None) => Ok(InvokeMethodReply::Value(new_object)), + (None, Some(exception)) => Ok(InvokeMethodReply::Exception(exception)), + _ => Err(io::Error::new( + io::ErrorKind::InvalidData, + "Invalid InvokeMethod reply", + )), + } + } +} diff --git a/tests/common.rs b/tests/common.rs index 90b60f1..460b84b 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -185,26 +185,3 @@ macro_rules! assert_snapshot { }); }; } - -pub trait TryMapExt { - fn try_map(self, f: F) -> std::result::Result, E> - where - F: FnMut(T) -> std::result::Result, - E: From; -} - -impl TryMapExt for I -where - I: IntoIterator, -{ - fn try_map(self, mut f: F) -> std::result::Result, E> - where - F: FnMut(T) -> std::result::Result, - E: From, - { - self.into_iter().try_fold(Vec::new(), move |mut acc, item| { - acc.push(f(item)?); - Ok(acc) - }) - } -} diff --git a/tests/reference_type.rs b/tests/reference_type.rs index 41498e9..3e1461c 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -16,10 +16,9 @@ use jdwp::{ types::{InterfaceID, ReferenceTypeID, TaggedReferenceTypeID}, }; -#[macro_use] mod common; -use common::{Result, TryMapExt}; +use common::Result; const OUR_CLS: &str = "LBasic;"; const ARRAY_CLS: &str = "[I"; @@ -35,10 +34,13 @@ where C: Command + JdwpWritable + Debug, C::Output: JdwpReadable + Debug, { - signatures.try_map(|item| { - let id = client.send(ClassesBySignature::new(*item))?[0].type_id; - client.send(new(*id)) - }) + signatures + .iter() + .map(|item| { + let id = client.send(ClassesBySignature::new(*item))?[0].type_id; + Ok(client.send(new(*id))?) + }) + .collect() } trait GetSignature { @@ -63,8 +65,10 @@ where S: GetSignature, I: IntoIterator, { - let sigs: Result<_> = iterable.try_map(|ref_id| ref_id.get_signature(client)); - let mut sigs = sigs?; + let mut sigs = iterable + .into_iter() + .map(|ref_id| ref_id.get_signature(client)) + .collect::>>()?; sigs.sort_unstable(); Ok(sigs) } diff --git a/tests/thread_group_reference.rs b/tests/thread_group_reference.rs new file mode 100644 index 0000000..562f8cb --- /dev/null +++ b/tests/thread_group_reference.rs @@ -0,0 +1,66 @@ +use std::collections::HashSet; + +use common::Result; +use jdwp::commands::{ + thread_group_reference::{Children, Name, Parent}, + thread_reference, + virtual_machine::TopLevelThreadGroups, +}; + +mod common; + +#[test] +fn system_tree_names() -> Result { + let mut client = common::launch_and_attach("basic")?; + + let thread_group_ids = client.send(TopLevelThreadGroups).unwrap(); + assert_eq!(thread_group_ids.len(), 1); + let thread_group = thread_group_ids[0]; + + let children = client.send(Children::new(thread_group))?; + + let parent_names = children + .child_groups + .iter() + .map(|id| { + let parent = client + .send(Parent::new(*id))? + .expect("Thread Group Parent was None"); + let name = client.send(Name::new(parent))?; + Ok(name) + }) + .collect::>>()?; + + let child_names = children + .child_groups + .iter() + .map(|id| Ok(client.send(Name::new(*id))?)) + .collect::>>()?; + + let thread_names = children + .child_threads + .iter() + .map(|id| Ok(client.send(thread_reference::Name::new(*id))?)) + .collect::>>()?; + + let expected_threads = &["Signal Dispatcher", "Reference Handler", "Finalizer"]; + + assert!(thread_names.len() >= expected_threads.len()); + assert!(thread_names + .iter() + .any(|n| expected_threads.contains(&n.as_str()))); + + assert_snapshot!((parent_names, child_names), @r###" + ( + { + "system", + }, + [ + "main", + "InnocuousThreadGroup", + ], + ) + "###); + + Ok(()) +} diff --git a/tests/virtual_machine.rs b/tests/virtual_machine.rs index d8f1cf5..3913d5a 100644 --- a/tests/virtual_machine.rs +++ b/tests/virtual_machine.rs @@ -9,8 +9,6 @@ mod common; use common::Result; -use crate::common::TryMapExt; - const CASES: &[&str] = &[ "Ljava/lang/String;", "Ljava/util/List;", @@ -37,9 +35,10 @@ fn version() -> Result { fn class_by_signature() -> Result { let mut client = common::launch_and_attach("basic")?; - let classes: Result<_> = - CASES.try_map(|&signature| client.send(ClassesBySignature::new(signature))); - let classes = classes?; + let classes = CASES + .iter() + .map(|&signature| Ok(client.send(ClassesBySignature::new(signature))?)) + .collect::>>()?; assert_snapshot!(classes, @r###" [ @@ -151,16 +150,13 @@ fn all_classes() -> Result { fn all_threads() -> Result { let mut client = common::launch_and_attach("basic")?; - let thread_ids = client.send(AllThreads)?; - - let names: Result<_> = thread_ids + let mut thread_names = client + .send(AllThreads)? .iter() - .try_fold(Vec::new(), move |mut acc, item| { - acc.push(client.send(thread_reference::Name::new(*item))?); - Ok(acc) - }); - let mut names = names?; - names.sort_unstable(); + .map(|id| Ok(client.send(thread_reference::Name::new(*id))?)) + .collect::>>()?; + + thread_names.sort_unstable(); // there are at least those present let expected = &[ @@ -169,20 +165,8 @@ fn all_threads() -> Result { "Reference Handler", "Finalizer", ]; - assert!(names.len() >= expected.len()); - assert!(names.iter().any(|n| expected.contains(&n.as_str()))); - - Ok(()) -} - -#[test] -fn top_level_thread_groups() -> Result { - let mut client = common::launch_and_attach("basic")?; - - let thread_group_ids = client.send(TopLevelThreadGroups)?; - - // should have just one?. if no more are created manually - assert_eq!(thread_group_ids.len(), 1); + assert!(thread_names.len() >= expected.len()); + assert!(thread_names.iter().any(|n| expected.contains(&n.as_str()))); Ok(()) } From 46fea75bafea2d89e6305a92515053433b43dbef Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Mon, 29 May 2023 06:52:25 +0300 Subject: [PATCH 3/9] More tests, make all commands cloneable, other minor changes --- src/commands/array_reference.rs | 6 +- src/commands/array_type.rs | 27 +++ src/commands/class_loader_reference.rs | 27 +++ src/commands/class_object_reference.rs | 11 ++ src/commands/class_type.rs | 8 +- src/commands/event_request.rs | 6 +- src/commands/interface_type.rs | 67 +++++++ src/commands/method.rs | 10 +- src/commands/mod.rs | 107 +--------- src/commands/object_reference.rs | 18 +- src/commands/reference_type.rs | 36 ++-- src/commands/stack_frame.rs | 8 +- src/commands/string_reference.rs | 11 ++ src/commands/thread_group_reference.rs | 6 +- src/commands/thread_reference.rs | 84 +++++--- src/commands/virtual_machine.rs | 42 ++-- src/enums.rs | 1 + src/types.rs | 6 +- tests/common.rs | 3 +- tests/fixtures/Basic.java | 8 +- tests/reference_type.rs | 33 ++-- tests/thread_reference.rs | 263 +++++++++++++++++++++++++ 22 files changed, 566 insertions(+), 222 deletions(-) create mode 100644 src/commands/array_type.rs create mode 100644 src/commands/class_loader_reference.rs create mode 100644 src/commands/class_object_reference.rs create mode 100644 src/commands/interface_type.rs create mode 100644 src/commands/string_reference.rs create mode 100644 tests/thread_reference.rs diff --git a/src/commands/array_reference.rs b/src/commands/array_reference.rs index 2aa77cb..8e80c40 100644 --- a/src/commands/array_reference.rs +++ b/src/commands/array_reference.rs @@ -7,7 +7,7 @@ use super::jdwp_command; /// Returns the number of components in a given array. #[jdwp_command(i32, 13, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Length { /// The array object ID array_id: ArrayID, @@ -17,7 +17,7 @@ pub struct Length { /// /// The specified range must be within the bounds of the array. #[jdwp_command(ArrayRegion, 13, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct GetValues { /// The array object ID array_id: ArrayID, @@ -38,7 +38,7 @@ pub struct GetValues { /// value's type to the array component type and the array component type must /// be loaded. #[jdwp_command((), 13, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SetValues { /// The array object ID array_id: ArrayID, diff --git a/src/commands/array_type.rs b/src/commands/array_type.rs new file mode 100644 index 0000000..85ad7e8 --- /dev/null +++ b/src/commands/array_type.rs @@ -0,0 +1,27 @@ +use jdwp_macros::JdwpReadable; + +use super::jdwp_command; + +use crate::{ + codec::JdwpWritable, + enums::Tag, + types::{ArrayID, ArrayTypeID}, +}; + +/// Creates a new array object of this type with a given length. +#[jdwp_command(4, 1)] +#[derive(Debug, Clone, JdwpWritable)] +pub struct NewInstance { + /// The array type of the new instance + array_type_id: ArrayTypeID, + /// The length of the array + length: i32, +} + +#[derive(Debug, JdwpReadable)] +pub struct NewInstanceReply { + // should always be Tag::Array + _tag: Tag, + /// The newly created array object + pub new_array: ArrayID, +} diff --git a/src/commands/class_loader_reference.rs b/src/commands/class_loader_reference.rs new file mode 100644 index 0000000..d1a6825 --- /dev/null +++ b/src/commands/class_loader_reference.rs @@ -0,0 +1,27 @@ +use jdwp_macros::{jdwp_command, JdwpWritable}; + +use crate::types::{ClassLoaderID, TaggedReferenceTypeID}; + +/// Returns a list of all classes which this class loader has been requested +/// to load. +/// +/// This class loader is considered to be an initiating class loader for +/// each class in the returned list. The list contains each reference +/// type defined by this loader and any types for which loading was +/// delegated by this class loader to another class loader. +/// +/// The visible class list has useful properties with respect to the type +/// namespace. +/// +/// A particular type name will occur at most once in the list. +/// +/// Each field or variable declared with that type name in a class defined +/// by this class loader must be resolved to that single type. +/// +/// No ordering of the returned list is guaranteed. +#[jdwp_command(Vec, 14, 1)] +#[derive(Debug, Clone, JdwpWritable)] +pub struct VisibleClasses { + /// The class loader object ID + class_loader_id: ClassLoaderID, +} diff --git a/src/commands/class_object_reference.rs b/src/commands/class_object_reference.rs new file mode 100644 index 0000000..59b3ed2 --- /dev/null +++ b/src/commands/class_object_reference.rs @@ -0,0 +1,11 @@ +use jdwp_macros::{jdwp_command, JdwpWritable}; + +use crate::types::{ClassObjectID, TaggedReferenceTypeID}; + +/// Returns the reference type reflected by this class object. +#[jdwp_command(TaggedReferenceTypeID, 17, 1)] +#[derive(Debug, Clone, JdwpWritable)] +pub struct ReflectedType { + /// The class object + class_object_id: ClassObjectID, +} diff --git a/src/commands/class_type.rs b/src/commands/class_type.rs index ad791d0..3d5f47c 100644 --- a/src/commands/class_type.rs +++ b/src/commands/class_type.rs @@ -10,7 +10,7 @@ use super::{JdwpReadable, JdwpWritable}; /// /// The return is null if the class is java.lang.Object. #[jdwp_command(Option, 3, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Superclass { /// The class type ID. class_id: ClassID, @@ -31,7 +31,7 @@ pub struct Superclass { /// For object values, there must exist a widening reference conversion from the /// value's type to thefield's type and the field's type must be loaded. #[jdwp_command((), 3, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SetValues { /// The class type ID. class_id: ClassID, @@ -88,7 +88,7 @@ pub struct SetValues { /// method invocation continues. #[jdwp_command(3, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct InvokeMethod { /// The class type ID. class_id: ClassID, @@ -103,7 +103,7 @@ pub struct InvokeMethod { } #[jdwp_command(3, 4)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct NewInstance { /// The class type ID. class_id: ClassID, diff --git a/src/commands/event_request.rs b/src/commands/event_request.rs index 3bd201c..369e097 100644 --- a/src/commands/event_request.rs +++ b/src/commands/event_request.rs @@ -18,7 +18,7 @@ use crate::{ /// which are automatically generated events - see /// [Composite](super::event::Composite) command for further details. #[jdwp_command(RequestID, 15, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Set { /// Event kind to request. Some events may require a capability in order to /// be requested. @@ -61,7 +61,7 @@ pub struct Set { /// Automatically generated events do not have a corresponding event request /// and may not be cleared using this command. #[jdwp_command((), 15, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Clear { /// Event kind to clear event_kind: EventKind, @@ -71,5 +71,5 @@ pub struct Clear { /// Removes all set breakpoints, a no-op if there are no breakpoints set. #[jdwp_command((), 15, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ClearAllBreakpoints; diff --git a/src/commands/interface_type.rs b/src/commands/interface_type.rs new file mode 100644 index 0000000..642b8b2 --- /dev/null +++ b/src/commands/interface_type.rs @@ -0,0 +1,67 @@ +use jdwp_macros::{jdwp_command, JdwpWritable}; + +use crate::{ + enums::InvokeOptions, + types::{InterfaceID, InvokeMethodReply, MethodID, ThreadID, Value}, +}; + +/// Invokes a static method. The method must not be a static initializer. +/// The method must be a member of the interface type. +/// +/// Since JDWP version 1.8 +/// +/// The method invocation will occur in the specified thread. Method +/// invocation can occur only if the specified thread has been suspended by +/// an event. Method invocation is not supported when the target VM has been +/// suspended by the front-end. +/// +/// The specified method is invoked with the arguments in the specified +/// argument list. The method invocation is synchronous; the reply packet is +/// not sent until the invoked method returns in the target VM. The return +/// value (possibly the void value) is included in the reply packet. If the +/// invoked method throws an exception, the exception object ID is set in +/// the reply packet; otherwise, the exception object ID is null. +/// +/// For primitive arguments, the argument value's type must match the +/// argument's type exactly. For object arguments, there must exist a +/// widening reference conversion from the argument value's type to the +/// argument's type and the argument's type must be loaded. +/// +/// By default, all threads in the target VM are resumed while the method is +/// being invoked if they were previously suspended by an event or by a +/// command. This is done to prevent the deadlocks that will occur if any of +/// the threads own monitors that will be needed by the invoked method. It +/// is possible that breakpoints or other events might occur during the +/// invocation. Note, however, that this implicit resume acts exactly like +/// the ThreadReference resume command, so if the thread's suspend count is +/// greater than 1, it will remain in a suspended state during the +/// invocation. By default, when the invocation completes, all threads in +/// the target VM are suspended, regardless their state before the +/// invocation. +/// +/// The resumption of other threads during the invoke can be prevented by +/// specifying the SINGLE_THREADED bit flag in the options field; +/// however, there is no protection against or recovery from the deadlocks +/// described above, so this option should be used with great caution. Only +/// the specified thread will be resumed (as described for all threads +/// above). Upon completion of a single threaded invoke, the invoking thread +/// will be suspended once again. Note that any threads started during the +/// single threaded invocation will not be suspended when the invocation +/// completes. + +// If the target VM is disconnected during the invoke (for example, through the VirtualMachine +// [Dispose](super::virtual_machine::Dispose) command) the method invocation continues. +#[jdwp_command(5, 1)] +#[derive(Debug, Clone, JdwpWritable)] +pub struct InvokeMethod { + /// The interface type ID + interface_id: InterfaceID, + /// The thread in which to invoke + thread_id: ThreadID, + /// The method to invoke + method_id: MethodID, + /// The argument values + arguments: Vec, + /// Invocation options + options: InvokeOptions, +} diff --git a/src/commands/method.rs b/src/commands/method.rs index 81f1e59..fd30bb8 100644 --- a/src/commands/method.rs +++ b/src/commands/method.rs @@ -17,7 +17,7 @@ use super::jdwp_command; /// The line number information is constant unless a new class definition is /// installed using [RedefineClasses](super::virtual_machine::RedefineClasses). #[jdwp_command(6, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct LineTable { /// The class. reference_type_id: ReferenceTypeID, @@ -51,7 +51,7 @@ pub struct Line { /// For instance methods, the "this" reference is included in the table. Also, /// synthetic variables may be present. #[jdwp_command(6, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct VariableTable { /// The class. reference_type_id: ReferenceTypeID, @@ -95,7 +95,7 @@ pub struct Variable { /// Requires `canGetBytecodes` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command(Vec, 6, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Bytecodes { /// The class. reference_type_id: ReferenceTypeID, @@ -111,7 +111,7 @@ pub struct Bytecodes { /// their bytecodes are the same except for indices into the constant pool and /// the referenced constants are equal. #[jdwp_command(bool, 6, 4)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct IsObsolete { /// The class. reference_type_id: ReferenceTypeID, @@ -129,7 +129,7 @@ pub struct IsObsolete { /// /// Since JDWP version 1.5. #[jdwp_command(6, 5)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct VariableTableWithGeneric { /// The class. reference_type_id: ReferenceTypeID, diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 541e80b..afb0838 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -1,121 +1,30 @@ -use std::fmt::Debug; - use jdwp_macros::jdwp_command; use crate::{ codec::{JdwpReadable, JdwpWritable}, - types::ClassObjectID, CommandId, }; -use crate::{enums::Tag, types::*}; - -pub mod array_type { - use super::*; - - /// Creates a new array object of this type with a given length. - #[jdwp_command(4, 1)] - #[derive(Debug, JdwpWritable)] - pub struct NewInstance { - /// The array type of the new instance - array_type_id: ArrayTypeID, - /// The length of the array - length: i32, - } - - #[derive(Debug, JdwpReadable)] - pub struct NewInstanceReply { - // should always be Tag::Array - _tag: Tag, - /// The newly created array object - pub new_array: ArrayID, - } -} - -pub mod class_loader_reference { - use super::*; - - /// Returns a list of all classes which this class loader has been requested - /// to load. - /// - /// This class loader is considered to be an initiating class loader for - /// each class in the returned list. The list contains each reference - /// type defined by this loader and any types for which loading was - /// delegated by this class loader to another class loader. - /// - /// The visible class list has useful properties with respect to the type - /// namespace. - /// - /// A particular type name will occur at most once in the list. - /// - /// Each field or variable declared with that type name in a class defined - /// by this class loader must be resolved to that single type. - /// - /// No ordering of the returned list is guaranteed. - #[jdwp_command(Vec, 14, 1)] - #[derive(Debug, JdwpWritable)] - pub struct VisibleClasses { - /// The class loader object ID - class_loader_id: ClassLoaderID, - } -} - -pub mod class_object_reference { - use super::*; - - /// Returns the reference type reflected by this class object. - #[jdwp_command(TaggedReferenceTypeID, 17, 1)] - #[derive(Debug, JdwpWritable)] - pub struct ReflectedType { - /// The class object - class_object_id: ClassObjectID, - } -} - -pub mod string_reference { - use super::*; - - /// Returns the characters contained in the string. - #[jdwp_command(String, 10, 1)] - #[derive(Debug, JdwpWritable)] - pub struct Value { - /// The String object ID - string_object: ObjectID, - } -} - -/// This module is defined to mirror the JDWP command set, which is empty -pub mod field {} - -pub mod interface_type { - use super::*; - - #[jdwp_command(5, 1)] - #[derive(Debug, JdwpWritable)] - pub struct InvokeMethod { - /// The interface type ID - interface_id: InterfaceID, - /// The thread in which to invoke - thread_id: ThreadID, - /// The method to invoke - method_id: MethodID, - /// Invocation options - arguments: Vec, - } -} - pub mod array_reference; +pub mod array_type; +pub mod class_loader_reference; +pub mod class_object_reference; pub mod class_type; pub mod event; pub mod event_request; +pub mod interface_type; pub mod method; pub mod object_reference; pub mod reference_type; pub mod stack_frame; +pub mod string_reference; pub mod thread_group_reference; pub mod thread_reference; pub mod virtual_machine; +/// This module is defined to mirror the JDWP command set, which is empty +pub mod field {} + pub trait Command { const ID: CommandId; diff --git a/src/commands/object_reference.rs b/src/commands/object_reference.rs index b7e21e5..8e2b665 100644 --- a/src/commands/object_reference.rs +++ b/src/commands/object_reference.rs @@ -9,7 +9,7 @@ use crate::{ }; #[jdwp_command(TaggedReferenceTypeID, 9, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ReferenceType { /// The object ID object: ObjectID, @@ -21,7 +21,7 @@ pub struct ReferenceType { /// superinterfaces, or implemented interfaces. Access control is not enforced; /// for example, the values of private fields can be obtained. #[jdwp_command(Vec, 9, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct GetValues { /// The object ID object: ObjectID, @@ -38,7 +38,7 @@ pub struct GetValues { /// there must be a widening reference conversion from the value's type to the /// field's type and the field's type must be loaded. #[jdwp_command((), 9, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SetValues { /// The object ID object: ObjectID, @@ -53,7 +53,7 @@ pub struct SetValues { /// Requires `can_get_monitor_info` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command(9, 5)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct MonitorInfo { /// The object ID object: ObjectID, @@ -114,7 +114,7 @@ pub struct MonitorInfoReply { /// If the target VM is disconnected during the invoke (for example, through the /// VirtualMachine dispose command) the method invocation continues. #[jdwp_command(9, 6)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct InvokeMethod { /// The object ID object: ObjectID, @@ -146,7 +146,7 @@ pub struct InvokeMethod { /// collection in the target VM and, consequently, may result in application /// behavior under the debugger that differs from its non-debugged behavior. #[jdwp_command((), 9, 7)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct DisableCollection { /// The object ID object: ObjectID, @@ -159,7 +159,7 @@ pub struct DisableCollection { /// only if garbage collection was previously disabled with the /// [DisableCollection] command. #[jdwp_command((), 9, 8)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct EnableCollection { /// The object ID object: ObjectID, @@ -167,7 +167,7 @@ pub struct EnableCollection { /// Determines whether an object has been garbage collected in the target VM. #[jdwp_command(bool, 9, 9)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct IsCollected { /// The object ID object: ObjectID, @@ -184,7 +184,7 @@ pub struct IsCollected { /// Requires `can_get_instance_info` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command(Vec, 9, 10)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ReferringObjects { /// The object ID object: ObjectID, diff --git a/src/commands/reference_type.rs b/src/commands/reference_type.rs index a4b8751..77b0fc8 100644 --- a/src/commands/reference_type.rs +++ b/src/commands/reference_type.rs @@ -20,7 +20,7 @@ use crate::{ /// corresponding primitive type; for example, "I" is returned as the signature /// of the class represented by `java.lang.Integer.TYPE`. #[jdwp_command(String, 2, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Signature { /// The reference type ID ref_type: ReferenceTypeID, @@ -32,7 +32,7 @@ pub struct Signature { /// If the reference type was loaded by the system class loader, the returned /// object ID is null. #[jdwp_command(Option, 2, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ClassLoader { /// The reference type ID ref_type: ReferenceTypeID, @@ -46,7 +46,7 @@ pub struct ClassLoader { /// If the reference type is an array or a primitive class (for example, /// `java.lang.Integer.TYPE`), the value of the returned bit mask is undefined. #[jdwp_command(TypeModifiers, 2, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Modifiers { ref_type: ReferenceTypeID, } @@ -59,7 +59,7 @@ pub struct Modifiers { /// /// Fields are returned in the order they occur in the class file. #[jdwp_command(Vec, 2, 4)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Fields { ref_type: ReferenceTypeID, } @@ -94,7 +94,7 @@ pub struct Field { /// /// Methods are returned in the order they occur in the class file. #[jdwp_command(Vec, 2, 5)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Methods { ref_type: ReferenceTypeID, } @@ -126,7 +126,7 @@ pub struct Method { /// Access control is not enforced; for example, the values of private fields /// can be obtained. #[jdwp_command(Vec, 2, 6)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct GetValues { pub ref_type: ReferenceTypeID, pub fields: Vec, @@ -134,7 +134,7 @@ pub struct GetValues { /// Returns the source file name in which a reference type was declared. #[jdwp_command(String, 2, 7)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SourceFile { /// The reference type ID ref_type: ReferenceTypeID, @@ -143,7 +143,7 @@ pub struct SourceFile { /// Returns the classes and interfaces directly nested within this type. Types /// further nested within those types are not included. #[jdwp_command(Vec, 2, 8)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct NestedTypes { /// The reference type ID ref_type: ReferenceTypeID, @@ -167,7 +167,7 @@ pub struct NestedTypes { /// The returned status bits are undefined for array types and for primitive /// classes (such as java.lang.Integer.TYPE). #[jdwp_command(ClassStatus, 2, 9)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Status { /// The reference type ID ref_type: ReferenceTypeID, @@ -178,7 +178,7 @@ pub struct Status { /// Interfaces indirectly implemented (extended by the implemented interface or /// implemented by a superclass) are not included. #[jdwp_command(Vec, 2, 10)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Interfaces { /// The reference type ID ref_type: ReferenceTypeID, @@ -186,7 +186,7 @@ pub struct Interfaces { /// Returns the class object corresponding to this type. #[jdwp_command(ClassObjectID, 2, 11)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ClassObject { /// The reference type ID ref_type: ReferenceTypeID, @@ -197,7 +197,7 @@ pub struct ClassObject { /// Since JDWP version 1.4. Requires canGetSourceDebugExtension capability - /// see [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command(String, 2, 12)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SourceDebugExtension { /// The reference type ID ref_type: ReferenceTypeID, @@ -211,7 +211,7 @@ pub struct SourceDebugExtension { /// /// Since JDWP version 1.5. #[jdwp_command(2, 13)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SignatureWithGeneric { /// The reference type ID ref_type: ReferenceTypeID, @@ -241,7 +241,7 @@ pub struct SignatureWithGenericReply { /// Since JDWP version 1.5. #[jdwp_command(Vec, 2, 14)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct FieldsWithGeneric { /// The reference type ID ref_type: ReferenceTypeID, @@ -280,7 +280,7 @@ pub struct FieldWithGeneric { /// /// Since JDWP version 1.5. #[jdwp_command(Vec, 2, 15)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct MethodsWithGeneric { /// The reference type ID ref_type: ReferenceTypeID, @@ -312,7 +312,7 @@ pub struct MethodWithGeneric { /// Only instances that are reachable for the purposes of garbage collection are /// returned. #[jdwp_command(Vec, 2, 16)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Instances { /// The reference type ID ref_type: ReferenceTypeID, @@ -326,7 +326,7 @@ pub struct Instances { /// Returns the class object corresponding to this type. #[jdwp_command(2, 17)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ClassFileVersion { /// The class ref_type: ReferenceTypeID, @@ -347,7 +347,7 @@ pub struct ClassFileVersionReply { /// Since JDWP version 1.6. Requires canGetConstantPool capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command(2, 18)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ConstantPool { /// The class ref_type: ReferenceTypeID, diff --git a/src/commands/stack_frame.rs b/src/commands/stack_frame.rs index a2f1537..6e64e93 100644 --- a/src/commands/stack_frame.rs +++ b/src/commands/stack_frame.rs @@ -15,7 +15,7 @@ use super::jdwp_command; /// (Typically, this index can be determined for method arguments from the /// method signature without access to the local variable table information.) #[jdwp_command(Vec, 16, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct GetValues { /// The frame's thread. pub thread_id: ThreadID, @@ -37,7 +37,7 @@ pub struct GetValues { /// (Typically, thisindex can be determined for method arguments from the method /// signature without access to the local variable table information.) #[jdwp_command((), 16, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SetValues { /// The frame's thread. pub thread_id: ThreadID, @@ -52,7 +52,7 @@ pub struct SetValues { /// If the frame's method is static or native, the reply will contain the null /// object reference. #[jdwp_command(Option, 16, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ThisObject { /// The frame's thread. pub thread_id: ThreadID, @@ -73,7 +73,7 @@ pub struct ThisObject { /// Requires `canPopFrames` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command((), 16, 4)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct PopFrames { /// The frame's thread. pub thread_id: ThreadID, diff --git a/src/commands/string_reference.rs b/src/commands/string_reference.rs new file mode 100644 index 0000000..4c4c216 --- /dev/null +++ b/src/commands/string_reference.rs @@ -0,0 +1,11 @@ +use jdwp_macros::{jdwp_command, JdwpWritable}; + +use crate::types::ObjectID; + +/// Returns the characters contained in the string. +#[jdwp_command(String, 10, 1)] +#[derive(Debug, Clone, JdwpWritable)] +pub struct Value { + /// The String object ID + string_object: ObjectID, +} diff --git a/src/commands/thread_group_reference.rs b/src/commands/thread_group_reference.rs index a78c31c..e7fa11d 100644 --- a/src/commands/thread_group_reference.rs +++ b/src/commands/thread_group_reference.rs @@ -6,7 +6,7 @@ use crate::{ /// Returns the thread group name. #[jdwp_command(String, 12, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Name { /// The thread group object ID group: ThreadGroupID, @@ -14,7 +14,7 @@ pub struct Name { /// Returns the thread group, if any, which contains a given thread group. #[jdwp_command(Option, 12, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Parent { /// The thread group object ID group: ThreadGroupID, @@ -29,7 +29,7 @@ pub struct Parent { /// /// See `java.lang.ThreadGroup` for information about active ThreadGroups. #[jdwp_command(12, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Children { /// The thread group object ID group: ThreadGroupID, diff --git a/src/commands/thread_reference.rs b/src/commands/thread_reference.rs index bb28400..cd12864 100644 --- a/src/commands/thread_reference.rs +++ b/src/commands/thread_reference.rs @@ -1,14 +1,14 @@ use jdwp_macros::jdwp_command; use crate::{ - codec::{JdwpReadable, JdwpWritable}, + codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, enums::{SuspendStatus, ThreadStatus}, types::{FrameID, Location, TaggedObjectID, ThreadGroupID, ThreadID, Value}, }; /// Returns the thread name. #[jdwp_command(String, 11, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Name { /// The thread object ID. pub thread: ThreadID, @@ -33,7 +33,7 @@ pub struct Name { /// [ThreadStatus] command.) For example, if it was Running, it will still /// appear running to other threads. #[jdwp_command((), 11, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Suspend { /// The thread object ID. pub thread: ThreadID, @@ -46,7 +46,7 @@ pub struct Suspend { /// thread is decremented. If it is decremented to 0, the thread will continue /// to execute. #[jdwp_command((), 11, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Resume { /// The thread object ID. pub thread: ThreadID, @@ -58,7 +58,7 @@ pub struct Resume { /// running. the suspend status provides information on the thread's suspension, /// if any. #[jdwp_command((ThreadStatus, SuspendStatus), 11, 4)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Status { /// The thread object ID. pub thread: ThreadID, @@ -66,7 +66,7 @@ pub struct Status { /// Returns the thread group that contains a given thread. #[jdwp_command(ThreadGroupID, 11, 5)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ThreadGroup { /// The thread object ID. pub thread: ThreadID, @@ -78,14 +78,31 @@ pub struct ThreadGroup { /// by its caller, and so on. The thread must be suspended, and the returned /// frameID is valid only while the thread is suspended. #[jdwp_command(Vec<(FrameID, Location)>, 11, 6)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Frames { /// The thread object ID. pub thread: ThreadID, /// The index of the first frame to retrieve. pub start_frame: u32, - /// The count of frames to retrieve (-1 means all remaining). - pub length: i32, + /// The amount of frames to retrieve. + pub limit: FrameLimit, +} + +/// A nice readable enum to be used in place of raw `i32` with a special meaning +/// for -1. +#[derive(Debug, Clone)] +pub enum FrameLimit { + Limit(u32), + AllRemaining, +} + +impl JdwpWritable for FrameLimit { + fn write(&self, write: &mut JdwpWriter) -> std::io::Result<()> { + match self { + FrameLimit::Limit(n) => n.write(write), + FrameLimit::AllRemaining => (-1i32).write(write), + } + } } /// Returns the count of frames on this thread's stack. @@ -96,7 +113,7 @@ pub struct Frames { /// Returns [ThreadNotSuspended](crate::enums::ErrorCode::ThreadNotSuspended) if /// not suspended. #[jdwp_command(u32, 11, 7)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct FrameCount { /// The thread object ID. pub thread: ThreadID, @@ -110,7 +127,7 @@ pub struct FrameCount { /// Requires `can_get_owned_monitor_info` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command(Vec, 11, 8)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct OwnedMonitors { /// The thread object ID. pub thread: ThreadID, @@ -126,7 +143,7 @@ pub struct OwnedMonitors { /// Requires `can_get_current_contended_monitor` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command(Option, 11, 9)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct CurrentContendedMonitor { /// The thread object ID. pub thread: ThreadID, @@ -135,7 +152,7 @@ pub struct CurrentContendedMonitor { /// Stops the thread with an asynchronous exception, as if done by /// `java.lang.Thread.stop` #[jdwp_command((), 11, 10)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Stop { /// The thread object ID. pub thread: ThreadID, @@ -147,7 +164,7 @@ pub struct Stop { /// Interrupt the thread, as if done by `java.lang.Thread.interrupt` #[jdwp_command((), 11, 11)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Interrupt { /// The thread object ID. pub thread: ThreadID, @@ -159,39 +176,44 @@ pub struct Interrupt { /// through the thread-level or VM-level suspend commands without a /// corresponding resume #[jdwp_command(u32, 11, 12)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SuspendCount { /// The thread object ID. pub thread: ThreadID, - /// The number of times the thread has been suspended. - pub suspend_count: u32, } /// Returns monitor objects owned by the thread, along with stack depth at which /// the monitor was acquired. /// -/// Returns stack depth of -1 if the implementation cannot determine the stack -/// depth (e.g., for monitors acquired by JNI MonitorEnter). The thread must be -/// suspended, and the returned information is relevant only while the thread is -/// suspended. +/// Stack depth can be unknown (e.g., for monitors acquired by JNI +/// MonitorEnter). The thread must be suspended, and the returned information is +/// relevant only while the thread is suspended. /// /// Requires `can_get_monitor_frame_info` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). /// /// Since JDWP version 1.6. -#[jdwp_command(Vec, 11, 13)] -#[derive(Debug, JdwpWritable)] +#[jdwp_command(Vec<(TaggedObjectID, StackDepth)>, 11, 13)] +#[derive(Debug, Clone, JdwpWritable)] pub struct OwnedMonitorsStackDepthInfo { /// The thread object ID. pub thread: ThreadID, } -#[derive(Debug, JdwpReadable)] -pub struct Monitor { - /// An owned monitor - pub monitor: TaggedObjectID, - /// Stack depth location where monitor was acquired - pub stack_depth: i32, +#[derive(Debug, Clone)] +pub enum StackDepth { + Depth(u32), + Unknown, +} + +impl JdwpReadable for StackDepth { + fn read(read: &mut JdwpReader) -> std::io::Result { + let depth = match i32::read(read)? { + -1 => StackDepth::Unknown, + n => StackDepth::Depth(n as u32), + }; + Ok(depth) + } } /// Force a method to return before it reaches a return statement. @@ -230,12 +252,10 @@ pub struct Monitor { /// Since JDWP version 1.6. Requires `can_force_early_return` capability - see /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). #[jdwp_command((), 11, 14)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ForceEarlyReturn { /// The thread object ID. pub thread: ThreadID, - /// The frame object ID. - pub frame: FrameID, /// The value to return. pub value: Value, } diff --git a/src/commands/virtual_machine.rs b/src/commands/virtual_machine.rs index e459c1d..fb33930 100644 --- a/src/commands/virtual_machine.rs +++ b/src/commands/virtual_machine.rs @@ -12,7 +12,7 @@ use super::jdwp_command; /// /// The version string format is implementation dependent. #[jdwp_command(1, 1)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Version; #[derive(Debug, JdwpReadable)] @@ -38,7 +38,7 @@ pub struct VersionReply { /// The search is confined to loaded classes only; no attempt is made to load a /// class of the given signature. #[jdwp_command(Vec, 1, 2)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ClassesBySignature { /// JNI signature of the class to find (for example, "Ljava/lang/String;") signature: String, @@ -54,7 +54,7 @@ pub struct UnnamedClass { /// Returns reference types for all classes currently loaded by the target VM. #[jdwp_command(Vec, 1, 3)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct AllClasses; #[derive(Debug, JdwpReadable)] @@ -76,14 +76,14 @@ pub struct Class { /// Threads that have not yet been started and threads that have completed /// their execution are not included in the returned list. #[jdwp_command(Vec, 1, 4)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct AllThreads; /// Returns all thread groups that do not have a parent. This command may be /// used as the first step in building a tree (or trees) of the existing thread /// groups. #[jdwp_command(Vec, 1, 5)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct TopLevelThreadGroups; /// Invalidates this virtual machine mirror. @@ -104,7 +104,7 @@ pub struct TopLevelThreadGroups; /// Resources originating in this VirtualMachine (ObjectReferences, /// ReferenceTypes, etc.) will become invalid. #[jdwp_command((), 1, 6)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Dispose; /// Returns the sizes of variably-sized data types in the target VM. @@ -112,7 +112,7 @@ pub struct Dispose; /// The returned values indicate the number of bytes used by the identifiers in /// command and reply packets. #[jdwp_command(IDSizeInfo, 1, 7)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct IDSizes; #[derive(Debug, Clone, JdwpReadable)] @@ -137,7 +137,7 @@ pub struct IDSizeInfo { /// be resumed through the VM-level resume command or the thread-level resume /// command the same number of times it has been suspended. #[jdwp_command((), 1, 8)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Suspend; /// Resumes execution of the application after the suspend command or an event @@ -148,7 +148,7 @@ pub struct Suspend; /// If a particular thread is suspended n times, it must resumed n times before /// it will continue. #[jdwp_command((), 1, 9)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Resume; /// Terminates the target VM with the given exit code. @@ -162,14 +162,14 @@ pub struct Resume; /// /// A thread death exception is not thrown and finally blocks are not run. #[jdwp_command((), 1, 10)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Exit { exit_code: i32, } /// Creates a new string object in the target VM and returns its id. #[jdwp_command(StringID, 1, 11)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct CreateString { /// UTF-8 characters to use in the created string string: String, @@ -183,7 +183,7 @@ pub struct CreateString { /// The commands associated with each capability will return the /// NOT_IMPLEMENTED error if the capability is not available. #[jdwp_command(1, 12)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct Capabilities; #[derive(Debug, JdwpReadable)] @@ -214,7 +214,7 @@ pub struct CapabilitiesReply { /// /// If the bootclasspath is not defined returns an empty list. #[jdwp_command(1, 13)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ClassPaths; #[derive(Debug, JdwpReadable)] @@ -259,7 +259,7 @@ pub struct ClassPathsReply { /// This description assumes reference counting, a back-end may use any /// implementation which operates equivalently. #[jdwp_command((), 1, 14)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct DisposeObjects { requests: Vec<(ObjectID, u32)>, } @@ -277,7 +277,7 @@ pub struct DisposeObjects { /// Responses to commands are never held and are not affected by this command. /// If events are already being held, this command is ignored. #[jdwp_command((), 1, 15)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct HoldEvents; /// Tells the target VM to continue sending events. @@ -287,7 +287,7 @@ pub struct HoldEvents; /// If there is no current HoldEvents command in effect, this command is /// ignored. #[jdwp_command((), 1, 16)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct ReleaseEvents; /// Retrieve all of this VM's capabilities. @@ -300,7 +300,7 @@ pub struct ReleaseEvents; /// /// Since JDWP version 1.4. #[jdwp_command(1, 17)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct CapabilitiesNew; #[derive(JdwpReadable)] @@ -421,7 +421,7 @@ impl Debug for CapabilitiesNewReply { /// or the `can_unrestrictedly_redefine_classes` to redefine classes in /// arbitrary ways. #[jdwp_command((), 1, 18)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct RedefineClasses { classes: Vec<(ReferenceTypeID, Vec)>, } @@ -429,7 +429,7 @@ pub struct RedefineClasses { /// Set the default stratum. Requires `can_set_default_stratum` capability - /// see [CapabilitiesNew]. #[jdwp_command((), 1, 19)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct SetDefaultStratum { /// default stratum, or empty string to use reference type default. stratum_id: String, @@ -445,7 +445,7 @@ pub struct SetDefaultStratum { /// /// Since JDWP version 1.5. #[jdwp_command(Vec, 1, 20)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct AllClassesWithGeneric; #[derive(Debug, JdwpReadable)] @@ -472,7 +472,7 @@ pub struct GenericClass { /// Since JDWP version 1.6. Requires canGetInstanceInfo capability - see /// [CapabilitiesNew]. #[jdwp_command(Vec, 1, 21)] -#[derive(Debug, JdwpWritable)] +#[derive(Debug, Clone, JdwpWritable)] pub struct InstanceCounts { /// A list of reference type IDs. ref_types: Vec, diff --git a/src/enums.rs b/src/enums.rs index 0ba96cc..cb74cd1 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -162,6 +162,7 @@ readable_enum! { readable_enum! { SuspendStatus: u32, + NotSuspended = 0, Suspended = 1, } diff --git a/src/types.rs b/src/types.rs index 4b7e992..47a3f73 100644 --- a/src/types.rs +++ b/src/types.rs @@ -561,9 +561,9 @@ tagged_io! { /// initializer of an interface. #[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] pub struct Location { - reference_id: TaggedReferenceTypeID, - method_id: MethodID, - index: u64, + pub reference_id: TaggedReferenceTypeID, + pub method_id: MethodID, + pub index: u64, } macro_rules! optional_impl { diff --git a/tests/common.rs b/tests/common.rs index 460b84b..053f326 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -1,5 +1,6 @@ use std::{ error::Error, + fmt::Debug, format, io::{BufRead, BufReader, ErrorKind}, net::TcpListener, @@ -14,8 +15,8 @@ pub type Result = std::result::Result>; #[derive(Debug)] pub struct JvmHandle { - jdwp_client: JdwpClient, pub jvm_process: Child, + jdwp_client: JdwpClient, port: u16, } diff --git a/tests/fixtures/Basic.java b/tests/fixtures/Basic.java index 7c042bf..23edaa6 100644 --- a/tests/fixtures/Basic.java +++ b/tests/fixtures/Basic.java @@ -1,6 +1,7 @@ import java.util.HashMap; +import java.util.function.IntSupplier; -class Basic implements Runnable { +class Basic implements IntSupplier { static int staticInt = 42; public static Basic secondInstance = new Basic(); @@ -14,7 +15,7 @@ public void tick() { } @Override - public void run() { + public int getAsInt() { try { // make sure nested is absolutely totally surely loaded Class.forName("Basic$NestedClass"); @@ -36,10 +37,11 @@ public void run() { break; } } + return 0; } public static void main(String[] args) throws Exception { - new Basic().run(); + System.exit(new Basic().getAsInt()); } private static void ping(Object ignored) { diff --git a/tests/reference_type.rs b/tests/reference_type.rs index 3e1461c..c5795e3 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -212,6 +212,14 @@ fn methods() -> Result { 0x0, ), }, + Method { + method_id: [opaque_id], + name: "getAsInt", + signature: "()I", + mod_bits: MethodModifiers( + PUBLIC, + ), + }, Method { method_id: [opaque_id], name: "main", @@ -228,14 +236,6 @@ fn methods() -> Result { PRIVATE | STATIC, ), }, - Method { - method_id: [opaque_id], - name: "run", - signature: "()V", - mod_bits: MethodModifiers( - PUBLIC, - ), - }, Method { method_id: [opaque_id], name: "tick", @@ -398,7 +398,7 @@ fn interfaces() -> Result { assert_snapshot!(interfaces, @r###" [ - "Ljava/lang/Runnable;", + "Ljava/util/function/IntSupplier;", ] "###); @@ -517,11 +517,11 @@ fn constant_pool() -> Result { "Class(\"java/lang/Class\")", "Class(\"java/lang/Exception\")", "Class(\"java/lang/Object\")", - "Class(\"java/lang/Runnable\")", "Class(\"java/lang/RuntimeException\")", "Class(\"java/lang/System\")", "Class(\"java/lang/Thread\")", "Class(\"java/util/HashMap\")", + "Class(\"java/util/function/IntSupplier\")", "Fieldref(Ref { class: \"Basic\", name: \"secondInstance\", descriptor: \"LBasic;\" })", "Fieldref(Ref { class: \"Basic\", name: \"staticInt\", descriptor: \"I\" })", "Fieldref(Ref { class: \"Basic\", name: \"ticks\", descriptor: \"J\" })", @@ -529,8 +529,8 @@ fn constant_pool() -> Result { "Fieldref(Ref { class: \"java/lang/System\", name: \"out\", descriptor: \"Ljava/io/PrintStream;\" })", "Long(50)", "Methodref(Ref { class: \"Basic\", name: \"\", descriptor: \"()V\" })", + "Methodref(Ref { class: \"Basic\", name: \"getAsInt\", descriptor: \"()I\" })", "Methodref(Ref { class: \"Basic\", name: \"ping\", descriptor: \"(Ljava/lang/Object;)V\" })", - "Methodref(Ref { class: \"Basic\", name: \"run\", descriptor: \"()V\" })", "Methodref(Ref { class: \"Basic\", name: \"tick\", descriptor: \"()V\" })", "Methodref(Ref { class: \"java/io/PrintStream\", name: \"println\", descriptor: \"(Ljava/lang/String;)V\" })", "Methodref(Ref { class: \"java/lang/Class\", name: \"forName\", descriptor: \"(Ljava/lang/String;)Ljava/lang/Class;\" })", @@ -538,16 +538,18 @@ fn constant_pool() -> Result { "Methodref(Ref { class: \"java/lang/Object\", name: \"\", descriptor: \"()V\" })", "Methodref(Ref { class: \"java/lang/Object\", name: \"getClass\", descriptor: \"()Ljava/lang/Class;\" })", "Methodref(Ref { class: \"java/lang/RuntimeException\", name: \"\", descriptor: \"(Ljava/lang/Throwable;)V\" })", + "Methodref(Ref { class: \"java/lang/System\", name: \"exit\", descriptor: \"(I)V\" })", "Methodref(Ref { class: \"java/lang/Thread\", name: \"sleep\", descriptor: \"(J)V\" })", "NameAndType(NameAndType { name: \"\", descriptor: \"()V\" })", "NameAndType(NameAndType { name: \"\", descriptor: \"(Ljava/lang/Throwable;)V\" })", + "NameAndType(NameAndType { name: \"exit\", descriptor: \"(I)V\" })", "NameAndType(NameAndType { name: \"forName\", descriptor: \"(Ljava/lang/String;)Ljava/lang/Class;\" })", + "NameAndType(NameAndType { name: \"getAsInt\", descriptor: \"()I\" })", "NameAndType(NameAndType { name: \"getClass\", descriptor: \"()Ljava/lang/Class;\" })", "NameAndType(NameAndType { name: \"getClasses\", descriptor: \"()[Ljava/lang/Class;\" })", "NameAndType(NameAndType { name: \"out\", descriptor: \"Ljava/io/PrintStream;\" })", "NameAndType(NameAndType { name: \"ping\", descriptor: \"(Ljava/lang/Object;)V\" })", "NameAndType(NameAndType { name: \"println\", descriptor: \"(Ljava/lang/String;)V\" })", - "NameAndType(NameAndType { name: \"run\", descriptor: \"()V\" })", "NameAndType(NameAndType { name: \"secondInstance\", descriptor: \"LBasic;\" })", "NameAndType(NameAndType { name: \"sleep\", descriptor: \"(J)V\" })", "NameAndType(NameAndType { name: \"staticInt\", descriptor: \"I\" })", @@ -557,9 +559,11 @@ fn constant_pool() -> Result { "String(\"Basic$NestedClass\")", "String(\"hello\")", "String(\"up\")", + "Utf8(\"()I\")", "Utf8(\"()Ljava/lang/Class;\")", "Utf8(\"()V\")", "Utf8(\"()[Ljava/lang/Class;\")", + "Utf8(\"(I)V\")", "Utf8(\"(J)V\")", "Utf8(\"(Ljava/lang/Object;)V\")", "Utf8(\"(Ljava/lang/String;)Ljava/lang/Class;\")", @@ -586,7 +590,9 @@ fn constant_pool() -> Result { "Utf8(\"NestedInterface\")", "Utf8(\"SourceFile\")", "Utf8(\"StackMapTable\")", + "Utf8(\"exit\")", "Utf8(\"forName\")", + "Utf8(\"getAsInt\")", "Utf8(\"getClass\")", "Utf8(\"getClasses\")", "Utf8(\"hello\")", @@ -596,16 +602,15 @@ fn constant_pool() -> Result { "Utf8(\"java/lang/Exception\")", "Utf8(\"java/lang/InterruptedException\")", "Utf8(\"java/lang/Object\")", - "Utf8(\"java/lang/Runnable\")", "Utf8(\"java/lang/RuntimeException\")", "Utf8(\"java/lang/System\")", "Utf8(\"java/lang/Thread\")", "Utf8(\"java/util/HashMap\")", + "Utf8(\"java/util/function/IntSupplier\")", "Utf8(\"main\")", "Utf8(\"out\")", "Utf8(\"ping\")", "Utf8(\"println\")", - "Utf8(\"run\")", "Utf8(\"secondInstance\")", "Utf8(\"sleep\")", "Utf8(\"staticInt\")", diff --git a/tests/thread_reference.rs b/tests/thread_reference.rs new file mode 100644 index 0000000..58a04d5 --- /dev/null +++ b/tests/thread_reference.rs @@ -0,0 +1,263 @@ +use std::{assert_eq, fmt::Debug}; + +use common::Result; +use jdwp::{ + client::JdwpClient, + codec::{JdwpReadable, JdwpWritable}, + commands::{ + reference_type::{Methods, Signature}, + thread_group_reference, + thread_reference::{ + CurrentContendedMonitor, ForceEarlyReturn, FrameCount, FrameLimit, Frames, Name, + OwnedMonitors, OwnedMonitorsStackDepthInfo, Resume, Status, Suspend, SuspendCount, + ThreadGroup, + }, + virtual_machine::AllThreads, + Command, + }, + types::{TaggedReferenceTypeID, ThreadID, Value}, +}; + +mod common; + +fn get_main_thread(client: &mut JdwpClient) -> Result { + Ok(client + .send(AllThreads)? + .into_iter() + .map(|id| Ok((id, client.send(Name::new(id))?))) + .collect::>>()? + .iter() + .find(|(_, name)| name == "main") + .expect("Didn't find main thread") + .0) +} + +fn test_host_not_suspended( + client: &mut JdwpClient, + thread: ThreadID, + command: C, +) -> Result +where + C: Command + JdwpWritable + Clone + Debug, + C::Output: JdwpReadable + Debug, +{ + let result = client.send(command.clone()); + + assert_snapshot!(result, @r###" + Err( + HostError( + ThreadNotSuspended, + ), + ) + "###); + + client.send(Suspend::new(thread))?; + + Ok(client.send(command)?) +} + +#[test] +fn suspend_resume_status_and_count() -> Result { + let mut client = common::launch_and_attach("basic")?; + + let main = get_main_thread(&mut client)?; + + let suspend_count = client.send(SuspendCount::new(main))?; + assert_eq!(suspend_count, 0); + + client.send(Suspend::new(main))?; + + let status = client.send(Status::new(main))?; + assert_snapshot!(status, @r###" + ( + Running, + Suspended, + ) + "###); + + client.send(Suspend::new(main))?; + client.send(Suspend::new(main))?; + client.send(Suspend::new(main))?; + + let suspend_count = client.send(SuspendCount::new(main))?; + assert_eq!(suspend_count, 4); + + client.send(Resume::new(main))?; + client.send(Resume::new(main))?; + client.send(Resume::new(main))?; + client.send(Resume::new(main))?; + + let status = client.send(Status::new(main))?; + assert_snapshot!(status, @r###" + ( + Sleeping, + NotSuspended, + ) + "###); + + Ok(()) +} + +#[test] +fn thread_group() -> Result { + let mut client = common::launch_and_attach("basic")?; + let main = get_main_thread(&mut client)?; + + let thread_group = client.send(ThreadGroup::new(main))?; + let name = client.send(thread_group_reference::Name::new(thread_group))?; + + assert_eq!(name, "main"); + + Ok(()) +} + +#[test] +fn frames() -> Result { + let mut client = common::launch_and_attach("basic")?; + let main = get_main_thread(&mut client)?; + + let frames = test_host_not_suspended( + &mut client, + main, + Frames::new(main, 0, FrameLimit::Limit(3)), + )?; + + let mut frame_info = vec![]; + + for (frame_id, location) in frames { + if let TaggedReferenceTypeID::Class(class_id) = location.reference_id { + let signature = client.send(Signature::new(*class_id))?; + + // meh + let method = client + .send(Methods::new(*class_id))? + .into_iter() + .find(|m| m.method_id == location.method_id) + .expect("Didn't find the location method"); + + frame_info.push(( + frame_id, + signature, + method.name, + method.signature, + location.index, + )); + } else { + panic!( + "Unexpected type of reference id: {:?}", + location.reference_id + ) + } + } + + // thread.sleep is native, not sure if it's location index is stable, CI will + // tell + assert_snapshot!(frame_info, @r###" + [ + ( + FrameID(0), + "Ljava/lang/Thread;", + "sleep", + "(J)V", + 18446744073709551615, + ), + ( + FrameID(1), + "LBasic;", + "getAsInt", + "()I", + 52, + ), + ( + FrameID(2), + "LBasic;", + "main", + "([Ljava/lang/String;)V", + 7, + ), + ] + "###); + + Ok(()) +} + +#[test] +fn frame_count() -> Result { + let mut client = common::launch_and_attach("basic").unwrap(); + let main = get_main_thread(&mut client).unwrap(); + + let frame_count = test_host_not_suspended(&mut client, main, FrameCount::new(main))?; + + assert_eq!(frame_count, 3); + + Ok(()) +} + +// todo: make a separate fixture with monitors +#[test] +fn owned_monitors() -> Result { + let mut client = common::launch_and_attach("basic")?; + let main = get_main_thread(&mut client)?; + + let owned_monitors = test_host_not_suspended(&mut client, main, OwnedMonitors::new(main))?; + + assert_snapshot!(owned_monitors, @"[]"); + + Ok(()) +} + +#[test] +fn current_contended_monitor() -> Result { + let mut client = common::launch_and_attach("basic")?; + let main = get_main_thread(&mut client)?; + + let current_contended_monitor = + test_host_not_suspended(&mut client, main, CurrentContendedMonitor::new(main))?; + + assert_snapshot!(current_contended_monitor, @r###" + Some( + Object( + [opaque_id], + ), + ) + "###); + + Ok(()) +} + +#[test] +fn owned_monitors_stack_depth_info() -> Result { + let mut client = common::launch_and_attach("basic")?; + let main = get_main_thread(&mut client)?; + + let owned_monitors_stack_depth_info = + test_host_not_suspended(&mut client, main, OwnedMonitorsStackDepthInfo::new(main))?; + + assert_snapshot!(owned_monitors_stack_depth_info, @"[]"); + + Ok(()) +} + +#[test] +fn force_early_return() -> Result { + let mut client = common::launch_and_attach("basic")?; + let main = get_main_thread(&mut client)?; + + // we stop at thread.sleep which is a native method + // todo: make a better test where we stop with an event in a place where we can + // actually force the return + let err = test_host_not_suspended( + &mut client, + main, + ForceEarlyReturn::new(main, Value::Int(42)), + ); + assert_snapshot!(err, @r###" + Err( + HostError( + OpaqueFrame, + ), + ) + "###); + + Ok(()) +} From 7fbd2c101c4d14a472696d47504b08df01a94ad3 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Wed, 31 May 2023 02:33:57 +0300 Subject: [PATCH 4/9] A whole bunch of rust shenanigans, likely would still fail on CI --- jdwp-macros/src/lib.rs | 160 +++++++++++++++++++++---------- src/codec.rs | 73 +++++++++++--- src/commands/array_reference.rs | 4 +- src/commands/array_type.rs | 9 ++ src/commands/class_type.rs | 48 +++++----- src/commands/event_request.rs | 4 +- src/commands/interface_type.rs | 4 +- src/commands/method.rs | 2 +- src/commands/object_reference.rs | 15 +-- src/commands/reference_type.rs | 11 ++- src/commands/stack_frame.rs | 11 ++- src/commands/virtual_machine.rs | 33 +++++-- src/functional.rs | 111 +++++++++++++++++++++ src/lib.rs | 1 + src/types.rs | 39 +++----- tests/event.rs | 14 +-- tests/fixtures/Basic.java | 5 +- tests/reference_type.rs | 34 ++++--- tests/thread_reference.rs | 2 +- tests/virtual_machine.rs | 80 +++++++--------- 20 files changed, 439 insertions(+), 221 deletions(-) create mode 100644 src/functional.rs diff --git a/jdwp-macros/src/lib.rs b/jdwp-macros/src/lib.rs index 4e099bb..7c70747 100644 --- a/jdwp-macros/src/lib.rs +++ b/jdwp-macros/src/lib.rs @@ -1,11 +1,25 @@ use proc_macro::TokenStream; -use quote::quote; + +use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, + punctuated::Punctuated, spanned::Spanned, - Data, Error, Fields, Index, ItemStruct, LitInt, PathArguments, Token, Type, + token::Comma, + Data, Error, Fields, GenericParam, Index, LitInt, Token, Type, }; +fn get_generic_names(generic_params: &Punctuated) -> proc_macro2::TokenStream { + use GenericParam::*; + + let generics = generic_params.iter().map(|param| match param { + Type(type_param) => type_param.ident.to_token_stream(), + Lifetime(lifetime_def) => lifetime_def.lifetime.to_token_stream(), + Const(const_param) => const_param.ident.to_token_stream(), + }); + quote!(#(#generics,)*) +} + #[proc_macro_derive(JdwpReadable, attributes(skip))] pub fn jdwp_readable(item: TokenStream) -> TokenStream { let derive_input = syn::parse_macro_input!(item as syn::DeriveInput); @@ -14,6 +28,7 @@ pub fn jdwp_readable(item: TokenStream) -> TokenStream { Data::Struct(struct_data) => { let ident = derive_input.ident; let generic_params = derive_input.generics.params; + let generic_names = get_generic_names(&generic_params); let generics_where = derive_input.generics.where_clause; let read = match &struct_data.fields { Fields::Unit => quote!(Ok(Self)), @@ -31,7 +46,7 @@ pub fn jdwp_readable(item: TokenStream) -> TokenStream { } }; let tokens = quote! { - impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_params> #generics_where { + impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_names> #generics_where { fn read(read: &mut ::jdwp::codec::JdwpReader) -> ::std::io::Result { #read } @@ -39,12 +54,61 @@ pub fn jdwp_readable(item: TokenStream) -> TokenStream { }; tokens.into() } - Data::Enum(enum_data) => Error::new( - enum_data.enum_token.span, - "Can derive JdwpReadable only for structs", - ) - .to_compile_error() - .into(), + Data::Enum(enum_data) => { + let fields = enum_data + .variants + .iter() + .map(|variant| match variant.fields { + Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { + Some((&variant.ident, &fields.unnamed[0].ty)) + } + _ => None, + }) + .collect::>>(); + + if let Some(fields) = fields { + let ident = derive_input.ident; + + let mut locals = Vec::with_capacity(fields.len()); + let mut match_arms = Vec::with_capacity(fields.len()); + + for (i, (variant, ty)) in fields.iter().enumerate() { + locals.push(quote! { + <::core::option::Option::<#ty> as ::jdwp::codec::JdwpReadable>::read(read)? + }); + let mut match_arm = Vec::with_capacity(fields.len()); + for j in 0..fields.len() { + match_arm.push(if j == i { + quote!(::core::option::Option::Some(matched)) + } else { + quote!(::core::option::Option::None) + }) + } + match_arms.push(quote! { + (#(#match_arm),*) => ::std::result::Result::Ok(#ident::#variant(matched)) + }); + } + + let tokens = quote! { + impl ::jdwp::codec::JdwpReadable for #ident { + fn read(read: &mut ::jdwp::codec::JdwpReader) -> ::std::io::Result { + match (#(#locals),*) { + #(#match_arms,)* + _ => ::std::result::Result::Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Multiple values in response")), + } + } + } + }; + tokens.into() + } else { + Error::new( + enum_data.enum_token.span, + "Can derive JdwpReadable only for enums with all variants having a single unnamed field", + ) + .to_compile_error() + .into() + } + } Data::Union(union_data) => Error::new( union_data.union_token.span, "Can derive JdwpReadable only for structs", @@ -62,7 +126,9 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream { Data::Struct(struct_data) => { let ident = derive_input.ident; let generic_params = derive_input.generics.params; + let generic_names = get_generic_names(&generic_params); let generics_where = derive_input.generics.where_clause; + let write = match &struct_data.fields { Fields::Unit => quote!(), Fields::Named(named) => { @@ -81,7 +147,7 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream { } }; let tokens = quote! { - impl<#generic_params> ::jdwp::codec::JdwpWritable for #ident<#generic_params> #generics_where { + impl<#generic_params> ::jdwp::codec::JdwpWritable for #ident<#generic_names> #generics_where { fn write(&self, write: &mut ::jdwp::codec::JdwpWriter) -> ::std::io::Result<()> { #write Ok(()) @@ -166,10 +232,14 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream { }; let ident = &item.ident; + let generic_params = &item.generics.params; + let generic_names = get_generic_names(generic_params); + let generics_where = &item.generics.where_clause; let new = if item.fields.is_empty() { quote!() } else { + let mut docs = Vec::with_capacity(item.fields.len()); let mut typed_idents = Vec::with_capacity(item.fields.len()); let mut idents = Vec::with_capacity(item.fields.len()); for f in &item.fields { @@ -178,15 +248,32 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream { let ty = &f.ty; // this is very cringe but also very simple - let string_magic = quote!(#ty).to_string() == "String"; + let stype = quote!(#ty).to_string(); + let string_magic = stype == "String"; + let phantom = stype.starts_with("PhantomData "); + + if !phantom { + typed_idents.push(if string_magic { + quote!(#ident: impl Into) + } else { + quote!(#ident: #ty) + }); + } + + docs.push(f.attrs.iter().find(|a| a.path.is_ident("doc")).map(|a| { + let tokens = &a.tokens; + quote! { + #[doc = stringify!(#ident)] + #[doc = " - "] + #[doc #tokens] + #[doc = "\n"] + } + })); - typed_idents.push(if string_magic { - quote!(#ident: impl Into) - } else { - quote!(#ident: #ty) - }); idents.push(if string_magic { quote!(#ident: #ident.into()) + } else if phantom { + quote!(#ident: ::std::marker::PhantomData) } else { quote!(#ident) }); @@ -198,59 +285,26 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream { } } } - let of = try_generate_of_constructor(&item); quote! { - impl #ident { + impl<#generic_params> #ident<#generic_names> #generics_where { /// Autogenerated constructor to create the command + /// ### Arguments: + #(#docs)* pub fn new(#(#typed_idents,)*) -> Self { Self { #(#idents,)* } } - #of } } }; let tokens = quote! { #item - #new - impl ::jdwp::commands::Command for #ident { + impl<#generic_params> ::jdwp::commands::Command for #ident<#generic_names> #generics_where { const ID: ::jdwp::CommandId = ::jdwp::CommandId::new(#command_set, #command_id); type Output = #reply_type; } }; tokens.into() } - -fn try_generate_of_constructor(item: &ItemStruct) -> proc_macro2::TokenStream { - let field = &match item.fields { - Fields::Named(ref named) => &named.named[0], - Fields::Unnamed(ref unnamed) => &unnamed.unnamed[0], - _ => unreachable!(), - }; - - match &field.ty { - Type::Path(tp) => { - if tp.path.segments.len() == 1 { - let first = &tp.path.segments[0]; - let vec = &first.ident; - if vec.to_string() == "Vec" { - if let PathArguments::AngleBracketed(args) = &first.arguments { - let f_ident = &field.ident; - let tpe = &args.args[0]; - return quote! { - /// Autogenerated shortcut to create a command with a single value in the list - pub fn of(single: #tpe) -> Self { - Self { #f_ident: vec![single] } - } - }; - } - } - } - } - _ => {} - } - - quote!() -} diff --git a/src/codec.rs b/src/codec.rs index f4f6dcc..5fbea7f 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -1,5 +1,7 @@ use std::{ + fmt::Debug, io::{self, Error, ErrorKind, Read, Write}, + marker::PhantomData, ops::{Deref, DerefMut}, }; @@ -8,7 +10,7 @@ use paste::paste; pub use jdwp_macros::{JdwpReadable, JdwpWritable}; -use crate::commands::virtual_machine::IDSizeInfo; +use crate::{commands::virtual_machine::IDSizeInfo, functional::Coll}; #[derive(Debug)] pub struct JdwpWriter { @@ -52,19 +54,19 @@ impl JdwpReader { } } - pub(crate) fn peek_u8(&mut self) -> io::Result { + pub(crate) fn peek_tag_byte(&mut self) -> io::Result { let b = self.read.read_u8()?; let prev = self.buffered_byte.replace(b); assert!(prev.is_none(), "Already contained unconsumed tag byte"); Ok(b) } - /// This wins over read_u8 from the deref-ed [Writer]. - /// It exists to first consume the peeked byte after calling [peek_u8]. + /// This exists to first consume the peeked byte after calling + /// [peek_tag_byte]. /// - /// Other read methods do not consume the buffered byte, but peek_u8 - /// is only called before reading a tag byte. - pub(crate) fn read_u8(&mut self) -> io::Result { + /// Other read methods do not consume the buffered byte, but peek_tag_byte + /// is only called before reading a u8 tag. + pub(crate) fn read_tag_byte(&mut self) -> io::Result { match self.buffered_byte.take() { Some(b) => Ok(b), None => self.read.read_u8(), @@ -108,10 +110,24 @@ impl JdwpWritable for () { } } +impl JdwpReadable for PhantomData { + #[inline] + fn read(_: &mut JdwpReader) -> io::Result { + Ok(PhantomData) + } +} + +impl JdwpWritable for PhantomData { + #[inline] + fn write(&self, _: &mut JdwpWriter) -> io::Result<()> { + Ok(()) + } +} + impl JdwpReadable for bool { #[inline] fn read(read: &mut JdwpReader) -> io::Result { - read.read_u8().map(|n| n != 0) + read.read_tag_byte().map(|n| n != 0) } } @@ -141,7 +157,7 @@ impl JdwpWritable for i8 { impl JdwpReadable for u8 { #[inline] fn read(read: &mut JdwpReader) -> io::Result { - read.read_u8() + read.read_tag_byte() } } @@ -194,21 +210,37 @@ impl JdwpWritable for String { } } -impl JdwpReadable for Vec { +impl JdwpReadable for C +where + C: Coll + TryFrom>, + C::Item: JdwpReadable, +{ fn read(read: &mut JdwpReader) -> io::Result { let len = u32::read(read)?; + if let Some(static_size) = C::STATIC_SIZE { + if len != static_size.get() as u32 { + return Err(Error::from(ErrorKind::InvalidData)); + } + } let mut res = Vec::with_capacity(len as usize); for _ in 0..len { - res.push(T::read(read)?); + res.push(C::Item::read(read)?); } - Ok(res) + // SAFETY: we just checked above, so this unwrap must be a no-op + // `Coll` is a crate-private trait only implemented for types for which that + // check is sufficient + Ok(unsafe { res.try_into().unwrap_unchecked() }) } } -impl JdwpWritable for Vec { +impl JdwpWritable for C +where + C: Coll, + C::Item: JdwpWritable, +{ fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - (self.len() as u32).write(write)?; - for item in self { + (self.size() as u32).write(write)?; + for item in self.iter() { item.write(write)?; } Ok(()) @@ -227,3 +259,14 @@ impl JdwpWritable for (A, B) { self.1.write(write) } } + +// only writable to allow using slices as command arguments +impl JdwpWritable for &[T] { + fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { + (self.len() as u32).write(write)?; + for item in *self { + item.write(write)?; + } + Ok(()) + } +} diff --git a/src/commands/array_reference.rs b/src/commands/array_reference.rs index 8e80c40..5a25e67 100644 --- a/src/commands/array_reference.rs +++ b/src/commands/array_reference.rs @@ -39,11 +39,11 @@ pub struct GetValues { /// be loaded. #[jdwp_command((), 13, 3)] #[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues { +pub struct SetValues<'a> { /// The array object ID array_id: ArrayID, /// The first index to set first_index: i32, /// Values to set - values: Vec, + values: &'a [UntaggedValue], } diff --git a/src/commands/array_type.rs b/src/commands/array_type.rs index 85ad7e8..ecd1c46 100644 --- a/src/commands/array_type.rs +++ b/src/commands/array_type.rs @@ -1,4 +1,5 @@ use jdwp_macros::JdwpReadable; +use std::ops::Deref; use super::jdwp_command; @@ -25,3 +26,11 @@ pub struct NewInstanceReply { /// The newly created array object pub new_array: ArrayID, } + +impl Deref for NewInstanceReply { + type Target = ArrayID; + + fn deref(&self) -> &Self::Target { + &self.new_array + } +} diff --git a/src/commands/class_type.rs b/src/commands/class_type.rs index 3d5f47c..1874360 100644 --- a/src/commands/class_type.rs +++ b/src/commands/class_type.rs @@ -1,8 +1,6 @@ -use std::io::{self, Read}; - use jdwp_macros::jdwp_command; -use crate::{codec::JdwpReader, enums::InvokeOptions, types::*}; +use crate::{enums::InvokeOptions, types::*}; use super::{JdwpReadable, JdwpWritable}; @@ -32,11 +30,11 @@ pub struct Superclass { /// value's type to thefield's type and the field's type must be loaded. #[jdwp_command((), 3, 2)] #[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues { +pub struct SetValues<'a> { /// The class type ID. class_id: ClassID, /// Fields to set and their values. - values: Vec<(FieldID, UntaggedValue)>, + values: &'a [(FieldID, UntaggedValue)], } /// Invokes a static method. The method must be member of the class type or one @@ -89,7 +87,7 @@ pub struct SetValues { #[jdwp_command(3, 3)] #[derive(Debug, Clone, JdwpWritable)] -pub struct InvokeMethod { +pub struct InvokeMethod<'a> { /// The class type ID. class_id: ClassID, /// The thread in which to invoke. @@ -97,14 +95,14 @@ pub struct InvokeMethod { /// The method to invoke. method_id: MethodID, /// Arguments to the method. - arguments: Vec, + arguments: &'a [Value], // Invocation options options: InvokeOptions, } #[jdwp_command(3, 4)] #[derive(Debug, Clone, JdwpWritable)] -pub struct NewInstance { +pub struct NewInstance<'a> { /// The class type ID. class_id: ClassID, /// The thread in which to invoke the constructor. @@ -112,12 +110,12 @@ pub struct NewInstance { /// The constructor to invoke. method_id: MethodID, /// Arguments for the constructor method. - arguments: Vec, + arguments: &'a [Value], // Constructor invocation options options: InvokeOptions, } -#[derive(Debug)] +#[derive(Debug, JdwpReadable)] pub enum NewInstanceReply { /// The newly created object. NewObject(TaggedObjectID), @@ -125,19 +123,19 @@ pub enum NewInstanceReply { Exception(TaggedObjectID), } -// better types everyone -impl JdwpReadable for NewInstanceReply { - fn read(read: &mut JdwpReader) -> io::Result { - let new_object = Option::::read(read)?; - let exception = Option::::read(read)?; +// // better types everyone +// impl JdwpReadable for NewInstanceReply { +// fn read(read: &mut JdwpReader) -> io::Result { +// let new_object = Option::::read(read)?; +// let exception = Option::::read(read)?; - match (new_object, exception) { - (Some(new_object), None) => Ok(NewInstanceReply::NewObject(new_object)), - (None, Some(exception)) => Ok(NewInstanceReply::Exception(exception)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - "Invalid NewInstance reply", - )), - } - } -} +// match (new_object, exception) { +// (Some(new_object), None) => +// Ok(NewInstanceReply::NewObject(new_object)), (None, +// Some(exception)) => Ok(NewInstanceReply::Exception(exception)), _ +// => Err(io::Error::new( io::ErrorKind::InvalidData, +// "Invalid NewInstance reply", +// )), +// } +// } +// } diff --git a/src/commands/event_request.rs b/src/commands/event_request.rs index 369e097..a5a026e 100644 --- a/src/commands/event_request.rs +++ b/src/commands/event_request.rs @@ -19,7 +19,7 @@ use crate::{ /// [Composite](super::event::Composite) command for further details. #[jdwp_command(RequestID, 15, 1)] #[derive(Debug, Clone, JdwpWritable)] -pub struct Set { +pub struct Set<'a> { /// Event kind to request. Some events may require a capability in order to /// be requested. event_kind: EventKind, @@ -45,7 +45,7 @@ pub struct Set { /// /// Filtering can improve debugger performance dramatically by reducing the /// amount of event traffic sent from the target VM to the debugger VM. - modifiers: Vec, + modifiers: &'a [Modifier], } /// Clear an event request. diff --git a/src/commands/interface_type.rs b/src/commands/interface_type.rs index 642b8b2..f45142f 100644 --- a/src/commands/interface_type.rs +++ b/src/commands/interface_type.rs @@ -53,7 +53,7 @@ use crate::{ // [Dispose](super::virtual_machine::Dispose) command) the method invocation continues. #[jdwp_command(5, 1)] #[derive(Debug, Clone, JdwpWritable)] -pub struct InvokeMethod { +pub struct InvokeMethod<'a> { /// The interface type ID interface_id: InterfaceID, /// The thread in which to invoke @@ -61,7 +61,7 @@ pub struct InvokeMethod { /// The method to invoke method_id: MethodID, /// The argument values - arguments: Vec, + arguments: &'a [Value], /// Invocation options options: InvokeOptions, } diff --git a/src/commands/method.rs b/src/commands/method.rs index fd30bb8..b8fa8d1 100644 --- a/src/commands/method.rs +++ b/src/commands/method.rs @@ -33,7 +33,7 @@ pub struct LineTableReply { /// Highest valid code index for the method, >=0, or -1 if the method is /// native pub end: i64, - /// The number of entries in the line table for this method. + /// The entries of the line table for this method. pub lines: Vec, } diff --git a/src/commands/object_reference.rs b/src/commands/object_reference.rs index 8e2b665..12ad8b9 100644 --- a/src/commands/object_reference.rs +++ b/src/commands/object_reference.rs @@ -2,6 +2,7 @@ use super::jdwp_command; use crate::{ codec::{JdwpReadable, JdwpWritable}, enums::InvokeOptions, + functional::Coll, types::{ ClassID, FieldID, InvokeMethodReply, ObjectID, TaggedObjectID, TaggedReferenceTypeID, ThreadID, UntaggedValue, Value, @@ -20,13 +21,13 @@ pub struct ReferenceType { /// Each field must be member of the object's type or one of its superclasses, /// superinterfaces, or implemented interfaces. Access control is not enforced; /// for example, the values of private fields can be obtained. -#[jdwp_command(Vec, 9, 2)] +#[jdwp_command(C::Map, 9, 2)] #[derive(Debug, Clone, JdwpWritable)] -pub struct GetValues { +pub struct GetValues> { /// The object ID object: ObjectID, /// Fields to get - fields: Vec, + fields: C, } /// Sets the value of one or more instance fields. @@ -39,11 +40,11 @@ pub struct GetValues { /// field's type and the field's type must be loaded. #[jdwp_command((), 9, 3)] #[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues { +pub struct SetValues<'a> { /// The object ID object: ObjectID, /// Fields and the values to set them to - fields: Vec<(FieldID, UntaggedValue)>, + fields: &'a [(FieldID, UntaggedValue)], } /// Returns monitor information for an object. @@ -115,7 +116,7 @@ pub struct MonitorInfoReply { /// VirtualMachine dispose command) the method invocation continues. #[jdwp_command(9, 6)] #[derive(Debug, Clone, JdwpWritable)] -pub struct InvokeMethod { +pub struct InvokeMethod<'a> { /// The object ID object: ObjectID, /// The thread in which to invoke @@ -125,7 +126,7 @@ pub struct InvokeMethod { /// The method to invoke method: FieldID, /// The arguments - arguments: Vec, + arguments: &'a [Value], /// Invocation options options: InvokeOptions, } diff --git a/src/commands/reference_type.rs b/src/commands/reference_type.rs index 77b0fc8..fc2326e 100644 --- a/src/commands/reference_type.rs +++ b/src/commands/reference_type.rs @@ -4,6 +4,7 @@ use super::jdwp_command; use crate::{ codec::{JdwpReadable, JdwpWritable}, enums::ClassStatus, + functional::Coll, jvm::{FieldModifiers, MethodModifiers, TypeModifiers}, types::{ ClassLoaderID, ClassObjectID, FieldID, InterfaceID, MethodID, ReferenceTypeID, @@ -125,11 +126,13 @@ pub struct Method { /// /// Access control is not enforced; for example, the values of private fields /// can be obtained. -#[jdwp_command(Vec, 2, 6)] +#[jdwp_command(C::Map, 2, 6)] #[derive(Debug, Clone, JdwpWritable)] -pub struct GetValues { +pub struct GetValues> { + /// The reference type ID pub ref_type: ReferenceTypeID, - pub fields: Vec, + /// Field IDs of fields to get + pub fields: C, } /// Returns the source file name in which a reference type was declared. @@ -360,7 +363,7 @@ pub struct ConstantPoolReply { /// This corresponds to the constant_pool_count item of the Class File /// Format in The Java™ Virtual Machine Specification. pub count: u32, - /// Raw bytes of constant pool + /// Raw bytes of the constant pool pub bytes: Vec, } diff --git a/src/commands/stack_frame.rs b/src/commands/stack_frame.rs index 6e64e93..92180f6 100644 --- a/src/commands/stack_frame.rs +++ b/src/commands/stack_frame.rs @@ -1,6 +1,7 @@ use crate::{ codec::JdwpWritable, enums::Tag, + functional::Coll, types::{FrameID, TaggedObjectID, ThreadID, Value}, }; @@ -14,15 +15,15 @@ use super::jdwp_command; /// if the front-end is able to determine the correct local variable index. /// (Typically, this index can be determined for method arguments from the /// method signature without access to the local variable table information.) -#[jdwp_command(Vec, 16, 1)] +#[jdwp_command(C::Map, 16, 1)] #[derive(Debug, Clone, JdwpWritable)] -pub struct GetValues { +pub struct GetValues> { /// The frame's thread. pub thread_id: ThreadID, /// The frame ID. pub frame_id: FrameID, /// Local variable indices and types to get. - pub slots: Vec<(u32, Tag)>, + pub slots: C, } /// Sets the value of one or more local variables. @@ -38,13 +39,13 @@ pub struct GetValues { /// signature without access to the local variable table information.) #[jdwp_command((), 16, 2)] #[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues { +pub struct SetValues<'a> { /// The frame's thread. pub thread_id: ThreadID, /// The frame ID. pub frame_id: FrameID, /// Local variable indices and values to set. - pub slots: Vec<(u32, Value)>, + pub slots: &'a [(u32, Value)], } /// Returns the value of the 'this' reference for this frame. diff --git a/src/commands/virtual_machine.rs b/src/commands/virtual_machine.rs index fb33930..2717a2d 100644 --- a/src/commands/virtual_machine.rs +++ b/src/commands/virtual_machine.rs @@ -1,8 +1,9 @@ -use std::fmt::Debug; +use std::{fmt::Debug, marker::PhantomData}; use crate::{ codec::{JdwpReadable, JdwpWritable}, enums::ClassStatus, + functional::{Coll, Single}, types::{ObjectID, ReferenceTypeID, StringID, TaggedReferenceTypeID, ThreadGroupID, ThreadID}, }; @@ -37,13 +38,25 @@ pub struct VersionReply { /// /// The search is confined to loaded classes only; no attempt is made to load a /// class of the given signature. -#[jdwp_command(Vec, 1, 2)] +#[jdwp_command(C, 1, 2)] #[derive(Debug, Clone, JdwpWritable)] -pub struct ClassesBySignature { +pub struct ClassesBySignatureGeneric> { /// JNI signature of the class to find (for example, "Ljava/lang/String;") signature: String, + _phantom: PhantomData, } +/// This is needed because inference cannot guess what you need since there are +/// no parameters +/// And the Single helper type is in a private jdwp module +pub type ClassBySignature = ClassesBySignatureGeneric>; + +/// The inference is able to figure out N by the destructuring pattern +pub type ClassesBySignatureStatic = ClassesBySignatureGeneric<[UnnamedClass; N]>; + +/// The 'standard' variant with a vector +pub type ClassesBySignature = ClassesBySignatureGeneric>; + #[derive(Debug, JdwpReadable)] pub struct UnnamedClass { /// Matching loaded reference type @@ -260,8 +273,8 @@ pub struct ClassPathsReply { /// implementation which operates equivalently. #[jdwp_command((), 1, 14)] #[derive(Debug, Clone, JdwpWritable)] -pub struct DisposeObjects { - requests: Vec<(ObjectID, u32)>, +pub struct DisposeObjects<'a> { + requests: &'a [(ObjectID, u32)], } /// Tells the target VM to stop sending events. Events are not discarded; they @@ -422,8 +435,8 @@ impl Debug for CapabilitiesNewReply { /// arbitrary ways. #[jdwp_command((), 1, 18)] #[derive(Debug, Clone, JdwpWritable)] -pub struct RedefineClasses { - classes: Vec<(ReferenceTypeID, Vec)>, +pub struct RedefineClasses<'a> { + classes: &'a [(ReferenceTypeID, Vec)], } /// Set the default stratum. Requires `can_set_default_stratum` capability - @@ -471,9 +484,9 @@ pub struct GenericClass { /// /// Since JDWP version 1.6. Requires canGetInstanceInfo capability - see /// [CapabilitiesNew]. -#[jdwp_command(Vec, 1, 21)] +#[jdwp_command(C::Map, 1, 21)] #[derive(Debug, Clone, JdwpWritable)] -pub struct InstanceCounts { +pub struct InstanceCounts> { /// A list of reference type IDs. - ref_types: Vec, + ref_types: C, } diff --git a/src/functional.rs b/src/functional.rs new file mode 100644 index 0000000..e1d8c84 --- /dev/null +++ b/src/functional.rs @@ -0,0 +1,111 @@ +use std::{iter, num::NonZeroUsize}; + +use std::ops::Deref; + +mod sealed { + pub trait Sealed {} +} + +/// This trait exists to abstract over Vec and [T; N]. +/// +/// It makes it convenient to call certain commands where the input list size +/// matches the output list size, for example +/// [GetValues](crate::commands::object_reference::GetValues). +pub trait Coll: sealed::Sealed { + type Item; + type Map; + type Iter<'a>: Iterator + where + Self: 'a; + + const STATIC_SIZE: Option; + + fn size(&self) -> usize; + + fn iter(&self) -> Self::Iter<'_>; +} + +/// This is a "single element collection" wrapper type, in terms of the Coll +/// trait implementation and usage. +/// +/// It's only really usable when the command return contains a collection that +/// is not matched by the input argument, when the input argument is matched the +/// easier syntax is to use a 1-sized static array and a descruturing let. +/// +/// The best syntax for that would be to have some IntoColl trait that is +/// implemented for T to make Single, but for it to handle [T; N] and Vec +/// we need specialization +#[repr(transparent)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Single(pub T); + +impl Deref for Single { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl TryFrom> for Single { + type Error = Vec; + + fn try_from(mut vec: Vec) -> Result { + if vec.len() == 1 { + Ok(Single(vec.remove(0))) + } else { + Err(vec) + } + } +} + +impl sealed::Sealed for Single {} +impl Coll for Single { + type Item = T; + type Map = Single; + type Iter<'a> = iter::Once<&'a T> where T: 'a; + + const STATIC_SIZE: Option = NonZeroUsize::new(1); + + fn size(&self) -> usize { + 1 + } + + fn iter(&self) -> Self::Iter<'_> { + iter::once(&self.0) + } +} + +impl sealed::Sealed for [T; N] {} +impl Coll for [T; N] { + type Item = T; + type Map = [U; N]; + type Iter<'a> = <&'a Self as IntoIterator>::IntoIter where T: 'a; + + const STATIC_SIZE: Option = NonZeroUsize::new(N); + + fn size(&self) -> usize { + N + } + + fn iter(&self) -> Self::Iter<'_> { + <&Self as IntoIterator>::into_iter(self) + } +} + +impl sealed::Sealed for Vec {} +impl Coll for Vec { + type Item = T; + type Map = Vec; + type Iter<'a> = <&'a Self as IntoIterator>::IntoIter where T: 'a; + + const STATIC_SIZE: Option = None; + + fn size(&self) -> usize { + self.len() + } + + fn iter(&self) -> Self::Iter<'_> { + <&Self as IntoIterator>::into_iter(self) + } +} diff --git a/src/lib.rs b/src/lib.rs index 2c696d3..a215cdc 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,6 +21,7 @@ pub mod enums; pub mod jvm; pub mod types; +mod functional; mod xorshift; #[derive(Copy, Clone, Debug, PartialEq, Eq, JdwpReadable, JdwpWritable)] diff --git a/src/types.rs b/src/types.rs index 47a3f73..286b836 100644 --- a/src/types.rs +++ b/src/types.rs @@ -322,7 +322,7 @@ impl JdwpWritable for UntaggedValue { } } -macro_rules! tagged_io { +macro_rules! tagged_enum { ($enum:ident <-> $tag:ident, $($tpe:ident),* { $($read_extras:tt)* } { $($write_extras:tt)* }) => { impl JdwpReadable for $enum { fn read(read: &mut JdwpReader) -> io::Result { @@ -345,9 +345,10 @@ macro_rules! tagged_io { } } }; + ($($tt:tt)*) => { tagged_enum!($($tt)* {} {}); } } -tagged_io! { +tagged_enum! { Value <-> Tag, Byte, Boolean, Char, Int, Short, Long, Float, Double, Object { @@ -411,7 +412,7 @@ impl Deref for TaggedObjectID { } } -tagged_io! { +tagged_enum! { TaggedObjectID <-> Tag, Array, Object, String, Thread, ThreadGroup, ClassLoader, ClassObject { _ => Err(io::Error::from(io::ErrorKind::InvalidData)) } @@ -479,7 +480,7 @@ impl ArrayRegion { } } -tagged_io! { +tagged_enum! { ArrayRegion <-> Tag, Byte, Boolean, Char, Short, Int, Long, Float, Double, Object { _ => Err(io::Error::from(io::ErrorKind::InvalidData)) } @@ -530,10 +531,9 @@ impl Deref for TaggedReferenceTypeID { } } -tagged_io! { +tagged_enum! { TaggedReferenceTypeID <-> TypeTag, Class, Interface, Array - {} {} } /// An executable location. @@ -571,10 +571,10 @@ macro_rules! optional_impl { $( impl JdwpReadable for Option<$tpe> { fn read(read: &mut JdwpReader) -> io::Result { - if read.peek_u8()? != 0 { + if read.peek_tag_byte()? != 0 { JdwpReadable::read(read).map(Some) } else { - read.read_u8()?; // consume it + read.read_tag_byte()?; // consume it Ok(None) } } @@ -829,33 +829,16 @@ pub enum Modifier { SourceNameMatch(SourceNameMatch), } -tagged_io! { +tagged_enum! { Modifier <-> ModifierKind, Count, Conditional, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, LocationOnly, ExceptionOnly, FieldOnly, Step, InstanceOnly, SourceNameMatch - {} {} } /// A response from 3 types of invoke method commands: virtual, static and /// interface static. The response is either a value or an exception - we model -/// it with a enum with a custom read implementation for better ergonomics. -#[derive(Debug)] +/// it with a enum for better ergonomics. +#[derive(Debug, JdwpReadable)] pub enum InvokeMethodReply { Value(Value), Exception(TaggedObjectID), } - -impl JdwpReadable for InvokeMethodReply { - fn read(read: &mut JdwpReader) -> io::Result { - let new_object = Option::::read(read)?; - let exception = Option::::read(read)?; - - match (new_object, exception) { - (Some(new_object), None) => Ok(InvokeMethodReply::Value(new_object)), - (None, Some(exception)) => Ok(InvokeMethodReply::Exception(exception)), - _ => Err(io::Error::new( - io::ErrorKind::InvalidData, - "Invalid InvokeMethod reply", - )), - } - } -} diff --git a/tests/event.rs b/tests/event.rs index aac01db..3c9b2dc 100644 --- a/tests/event.rs +++ b/tests/event.rs @@ -1,6 +1,6 @@ use jdwp::{ commands::{ - event::Event, event_request, reference_type::Fields, virtual_machine::ClassesBySignature, + event::Event, event_request, reference_type::Fields, virtual_machine::ClassBySignature, }, enums::{EventKind, SuspendPolicy}, types::{FieldOnly, Modifier, Value}, @@ -14,7 +14,7 @@ use common::Result; fn field_modification() -> Result { let mut client = common::launch_and_attach("basic")?; - let type_id = client.send(ClassesBySignature::new("LBasic;"))?[0].type_id; + let type_id = client.send(ClassBySignature::new("LBasic;"))?.type_id; let ticks = &client .send(Fields::new(*type_id))? @@ -22,13 +22,15 @@ fn field_modification() -> Result { .find(|f| f.name == "ticks") .unwrap(); + let field_only = Modifier::FieldOnly(FieldOnly { + declaring: *type_id, + field_id: ticks.field_id, + }); + let request_id = client.send(event_request::Set::new( EventKind::FieldModification, SuspendPolicy::None, - vec![Modifier::FieldOnly(FieldOnly { - declaring: *type_id, - field_id: ticks.field_id, - })], + &[field_only], ))?; match &client.host_events().recv()?.events[..] { diff --git a/tests/fixtures/Basic.java b/tests/fixtures/Basic.java index 23edaa6..dbd764a 100644 --- a/tests/fixtures/Basic.java +++ b/tests/fixtures/Basic.java @@ -4,6 +4,7 @@ class Basic implements IntSupplier { static int staticInt = 42; + public static Basic running = new Basic(); public static Basic secondInstance = new Basic(); public long ticks = 0; @@ -32,7 +33,7 @@ public int getAsInt() { while (true) { tick(); try { - Thread.sleep(50L); + Thread.sleep(500L); } catch (InterruptedException e) { break; } @@ -41,7 +42,7 @@ public int getAsInt() { } public static void main(String[] args) throws Exception { - System.exit(new Basic().getAsInt()); + System.exit(running.getAsInt()); } private static void ping(Object ignored) { diff --git a/tests/reference_type.rs b/tests/reference_type.rs index c5795e3..dffa674 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -9,7 +9,7 @@ use jdwp::{ ClassFileVersion, ClassLoader, ClassObject, ConstantPool, Fields, GetValues, Instances, Interfaces, Methods, Modifiers, NestedTypes, Signature, SourceFile, Status, }, - virtual_machine::ClassesBySignature, + virtual_machine::ClassBySignature, Command, }, jvm::{ConstantPoolItem, ConstantPoolValue, FieldModifiers}, @@ -37,7 +37,7 @@ where signatures .iter() .map(|item| { - let id = client.send(ClassesBySignature::new(*item))?[0].type_id; + let id = client.send(ClassBySignature::new(*item))?.type_id; Ok(client.send(new(*id))?) }) .collect() @@ -140,7 +140,7 @@ fn modifiers() -> Result { fn fields() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let mut fields = client.send(Fields::new(*id))?; fields.sort_by_key(|f| f.name.clone()); @@ -189,7 +189,7 @@ fn fields() -> Result { fn methods() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let mut methods = client.send(Methods::new(*id))?; methods.sort_by_key(|f| f.name.clone()); @@ -254,7 +254,7 @@ fn methods() -> Result { fn get_values() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let mut fields = client.send(Fields::new(*id))?; fields.sort_by_key(|f| f.name.clone()); @@ -302,7 +302,7 @@ fn source_file() -> Result { ] "###); - let id = client.send(ClassesBySignature::new(ARRAY_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(ARRAY_CLS))?.type_id; let array_source_file = client.send(SourceFile::new(*id)); assert_snapshot!(array_source_file, @r###" @@ -320,7 +320,7 @@ fn source_file() -> Result { fn nested_types() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let mut nested_types = client.send(NestedTypes::new(*id))?; nested_types.sort_by_key(|t| t.tag() as u8); @@ -334,7 +334,9 @@ fn nested_types() -> Result { ] "###); - let id = client.send(ClassesBySignature::new("Ljava/util/HashMap;"))?[0].type_id; + let id = client + .send(ClassBySignature::new("Ljava/util/HashMap;"))? + .type_id; let mut nested_types = client.send(NestedTypes::new(*id))?; nested_types.sort_by_key(|t| t.tag() as u8); @@ -392,7 +394,7 @@ fn status() -> Result { fn interfaces() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let interfaces = client.send(Interfaces::new(*id))?; let interfaces = get_signatures(&mut client, interfaces)?; @@ -402,7 +404,9 @@ fn interfaces() -> Result { ] "###); - let id = client.send(ClassesBySignature::new("Ljava/util/ArrayList;"))?[0].type_id; + let id = client + .send(ClassBySignature::new("Ljava/util/ArrayList;"))? + .type_id; let interfaces = client.send(Interfaces::new(*id))?; let interfaces = get_signatures(&mut client, interfaces)?; @@ -422,7 +426,7 @@ fn interfaces() -> Result { fn class_object() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let class_object = client.send(ClassObject::new(*id))?; let ref_id = client.send(ReflectedType::new(class_object))?; @@ -435,7 +439,7 @@ fn class_object() -> Result { fn instances() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let instances = client.send(Instances::new(*id, 10))?; // the running instance and the one in the static field @@ -457,7 +461,7 @@ fn instances() -> Result { fn class_file_version() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let version = client.send(ClassFileVersion::new(*id))?; let expected = match common::java_version() { @@ -480,7 +484,7 @@ fn class_file_version() -> Result { fn constant_pool() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new(OUR_CLS))?[0].type_id; + let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; let constant_pool = client.send(ConstantPool::new(*id))?; let mut reader = Cursor::new(constant_pool.bytes); @@ -527,7 +531,7 @@ fn constant_pool() -> Result { "Fieldref(Ref { class: \"Basic\", name: \"ticks\", descriptor: \"J\" })", "Fieldref(Ref { class: \"Basic\", name: \"unused\", descriptor: \"Ljava/lang/String;\" })", "Fieldref(Ref { class: \"java/lang/System\", name: \"out\", descriptor: \"Ljava/io/PrintStream;\" })", - "Long(50)", + "Long(500)", "Methodref(Ref { class: \"Basic\", name: \"\", descriptor: \"()V\" })", "Methodref(Ref { class: \"Basic\", name: \"getAsInt\", descriptor: \"()I\" })", "Methodref(Ref { class: \"Basic\", name: \"ping\", descriptor: \"(Ljava/lang/Object;)V\" })", diff --git a/tests/thread_reference.rs b/tests/thread_reference.rs index 58a04d5..d4ef85e 100644 --- a/tests/thread_reference.rs +++ b/tests/thread_reference.rs @@ -70,7 +70,7 @@ fn suspend_resume_status_and_count() -> Result { let status = client.send(Status::new(main))?; assert_snapshot!(status, @r###" ( - Running, + Sleeping, Suspended, ) "###); diff --git a/tests/virtual_machine.rs b/tests/virtual_machine.rs index 3913d5a..1a953fa 100644 --- a/tests/virtual_machine.rs +++ b/tests/virtual_machine.rs @@ -37,51 +37,43 @@ fn class_by_signature() -> Result { let classes = CASES .iter() - .map(|&signature| Ok(client.send(ClassesBySignature::new(signature))?)) + .map(|&signature| Ok(client.send(ClassBySignature::new(signature))?.0)) .collect::>>()?; assert_snapshot!(classes, @r###" [ - [ - UnnamedClass { - type_id: Class( - [opaque_id], - ), - status: ClassStatus( - VERIFIED | PREPARED | INITIALIZED, - ), - }, - ], - [ - UnnamedClass { - type_id: Interface( - [opaque_id], - ), - status: ClassStatus( - VERIFIED | PREPARED | INITIALIZED, - ), - }, - ], - [ - UnnamedClass { - type_id: Array( - [opaque_id], - ), - status: ClassStatus( - 0x0, - ), - }, - ], - [ - UnnamedClass { - type_id: Array( - [opaque_id], - ), - status: ClassStatus( - 0x0, - ), - }, - ], + UnnamedClass { + type_id: Class( + [opaque_id], + ), + status: ClassStatus( + VERIFIED | PREPARED | INITIALIZED, + ), + }, + UnnamedClass { + type_id: Interface( + [opaque_id], + ), + status: ClassStatus( + VERIFIED | PREPARED | INITIALIZED, + ), + }, + UnnamedClass { + type_id: Array( + [opaque_id], + ), + status: ClassStatus( + 0x0, + ), + }, + UnnamedClass { + type_id: Array( + [opaque_id], + ), + status: ClassStatus( + 0x0, + ), + }, ] "###); @@ -346,8 +338,10 @@ fn set_default_stratum() -> Result { fn instance_counts() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassesBySignature::new("LBasic;"))?[0].type_id; - let id2 = client.send(ClassesBySignature::new("LBasic$NestedClass;"))?[0].type_id; + let id = client.send(ClassBySignature::new("LBasic;"))?.type_id; + let id2 = client + .send(ClassBySignature::new("LBasic$NestedClass;"))? + .type_id; let counts = client.send(InstanceCounts::new(vec![*id, *id2]))?; From 39ea914a181415bbf31aa2f65c0b3d9eb19e3886 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Wed, 31 May 2023 03:01:41 +0300 Subject: [PATCH 5/9] Update snapshots --- tests/reference_type.rs | 14 ++++++++++++++ tests/thread_reference.rs | 2 +- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/reference_type.rs b/tests/reference_type.rs index dffa674..9e74c52 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -147,6 +147,14 @@ fn fields() -> Result { assert_snapshot!(fields, @r###" [ + Field { + field_id: [opaque_id], + name: "running", + signature: "LBasic;", + mod_bits: FieldModifiers( + PUBLIC | STATIC, + ), + }, Field { field_id: [opaque_id], name: "secondInstance", @@ -272,6 +280,9 @@ fn get_values() -> Result { assert_snapshot!(values, @r###" [ + Object( + [opaque_id], + ), Object( [opaque_id], ), @@ -526,6 +537,7 @@ fn constant_pool() -> Result { "Class(\"java/lang/Thread\")", "Class(\"java/util/HashMap\")", "Class(\"java/util/function/IntSupplier\")", + "Fieldref(Ref { class: \"Basic\", name: \"running\", descriptor: \"LBasic;\" })", "Fieldref(Ref { class: \"Basic\", name: \"secondInstance\", descriptor: \"LBasic;\" })", "Fieldref(Ref { class: \"Basic\", name: \"staticInt\", descriptor: \"I\" })", "Fieldref(Ref { class: \"Basic\", name: \"ticks\", descriptor: \"J\" })", @@ -554,6 +566,7 @@ fn constant_pool() -> Result { "NameAndType(NameAndType { name: \"out\", descriptor: \"Ljava/io/PrintStream;\" })", "NameAndType(NameAndType { name: \"ping\", descriptor: \"(Ljava/lang/Object;)V\" })", "NameAndType(NameAndType { name: \"println\", descriptor: \"(Ljava/lang/String;)V\" })", + "NameAndType(NameAndType { name: \"running\", descriptor: \"LBasic;\" })", "NameAndType(NameAndType { name: \"secondInstance\", descriptor: \"LBasic;\" })", "NameAndType(NameAndType { name: \"sleep\", descriptor: \"(J)V\" })", "NameAndType(NameAndType { name: \"staticInt\", descriptor: \"I\" })", @@ -615,6 +628,7 @@ fn constant_pool() -> Result { "Utf8(\"out\")", "Utf8(\"ping\")", "Utf8(\"println\")", + "Utf8(\"running\")", "Utf8(\"secondInstance\")", "Utf8(\"sleep\")", "Utf8(\"staticInt\")", diff --git a/tests/thread_reference.rs b/tests/thread_reference.rs index d4ef85e..60f78fb 100644 --- a/tests/thread_reference.rs +++ b/tests/thread_reference.rs @@ -173,7 +173,7 @@ fn frames() -> Result { "LBasic;", "main", "([Ljava/lang/String;)V", - 7, + 3, ), ] "###); From 4c3fc44d8d97f42a9d3600db70019f9249ce3c00 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Wed, 31 May 2023 03:34:27 +0300 Subject: [PATCH 6/9] Maybe fix the CI insta issue --- tests/thread_group_reference.rs | 6 ++-- tests/thread_reference.rs | 63 +++++++++++++-------------------- 2 files changed, 28 insertions(+), 41 deletions(-) diff --git a/tests/thread_group_reference.rs b/tests/thread_group_reference.rs index 562f8cb..3c62c51 100644 --- a/tests/thread_group_reference.rs +++ b/tests/thread_group_reference.rs @@ -35,7 +35,10 @@ fn system_tree_names() -> Result { .child_groups .iter() .map(|id| Ok(client.send(Name::new(*id))?)) - .collect::>>()?; + .collect::>>()? + .into_iter() + .filter(|name| name != "InnocuousThreadGroup") // not present on jdk8 + .collect::>(); let thread_names = children .child_threads @@ -57,7 +60,6 @@ fn system_tree_names() -> Result { }, [ "main", - "InnocuousThreadGroup", ], ) "###); diff --git a/tests/thread_reference.rs b/tests/thread_reference.rs index 60f78fb..0e90afb 100644 --- a/tests/thread_reference.rs +++ b/tests/thread_reference.rs @@ -1,9 +1,8 @@ -use std::{assert_eq, fmt::Debug}; +use std::assert_eq; use common::Result; use jdwp::{ client::JdwpClient, - codec::{JdwpReadable, JdwpWritable}, commands::{ reference_type::{Methods, Signature}, thread_group_reference, @@ -13,7 +12,6 @@ use jdwp::{ ThreadGroup, }, virtual_machine::AllThreads, - Command, }, types::{TaggedReferenceTypeID, ThreadID, Value}, }; @@ -32,28 +30,22 @@ fn get_main_thread(client: &mut JdwpClient) -> Result { .0) } -fn test_host_not_suspended( - client: &mut JdwpClient, - thread: ThreadID, - command: C, -) -> Result -where - C: Command + JdwpWritable + Clone + Debug, - C::Output: JdwpReadable + Debug, -{ - let result = client.send(command.clone()); - - assert_snapshot!(result, @r###" - Err( - HostError( - ThreadNotSuspended, - ), - ) - "###); - - client.send(Suspend::new(thread))?; - - Ok(client.send(command)?) +// a macro so that same assert_snapshot is not called from multiple places as +// insta seems to hate that +macro_rules! check_host_suspended { + ($client:expr, $thread:expr, $command:expr) => {{ + let result = $client.send($command); + assert_snapshot!(result, @r###" + Err( + HostError( + ThreadNotSuspended, + ), + ) + "###); + + $client.send(Suspend::new($thread)) + .and_then(|_| $client.send($command)) + }}; } #[test] @@ -116,11 +108,7 @@ fn frames() -> Result { let mut client = common::launch_and_attach("basic")?; let main = get_main_thread(&mut client)?; - let frames = test_host_not_suspended( - &mut client, - main, - Frames::new(main, 0, FrameLimit::Limit(3)), - )?; + let frames = check_host_suspended!(client, main, Frames::new(main, 0, FrameLimit::Limit(3)))?; let mut frame_info = vec![]; @@ -186,7 +174,7 @@ fn frame_count() -> Result { let mut client = common::launch_and_attach("basic").unwrap(); let main = get_main_thread(&mut client).unwrap(); - let frame_count = test_host_not_suspended(&mut client, main, FrameCount::new(main))?; + let frame_count = check_host_suspended!(client, main, FrameCount::new(main))?; assert_eq!(frame_count, 3); @@ -199,7 +187,7 @@ fn owned_monitors() -> Result { let mut client = common::launch_and_attach("basic")?; let main = get_main_thread(&mut client)?; - let owned_monitors = test_host_not_suspended(&mut client, main, OwnedMonitors::new(main))?; + let owned_monitors = check_host_suspended!(client, main, OwnedMonitors::new(main))?; assert_snapshot!(owned_monitors, @"[]"); @@ -212,7 +200,7 @@ fn current_contended_monitor() -> Result { let main = get_main_thread(&mut client)?; let current_contended_monitor = - test_host_not_suspended(&mut client, main, CurrentContendedMonitor::new(main))?; + check_host_suspended!(client, main, CurrentContendedMonitor::new(main))?; assert_snapshot!(current_contended_monitor, @r###" Some( @@ -231,7 +219,7 @@ fn owned_monitors_stack_depth_info() -> Result { let main = get_main_thread(&mut client)?; let owned_monitors_stack_depth_info = - test_host_not_suspended(&mut client, main, OwnedMonitorsStackDepthInfo::new(main))?; + check_host_suspended!(client, main, OwnedMonitorsStackDepthInfo::new(main))?; assert_snapshot!(owned_monitors_stack_depth_info, @"[]"); @@ -246,11 +234,8 @@ fn force_early_return() -> Result { // we stop at thread.sleep which is a native method // todo: make a better test where we stop with an event in a place where we can // actually force the return - let err = test_host_not_suspended( - &mut client, - main, - ForceEarlyReturn::new(main, Value::Int(42)), - ); + let err = check_host_suspended!(client, main, ForceEarlyReturn::new(main, Value::Int(42))); + assert_snapshot!(err, @r###" Err( HostError( From cc7204cda697dae370cf543125bf824b457120cf Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Thu, 1 Jun 2023 01:07:15 +0300 Subject: [PATCH 7/9] Way better enums, remove lookahead hack, other improvements --- flake.nix | 2 + jdwp-macros/src/lib.rs | 173 ++++--- src/codec.rs | 134 ++---- src/commands/class_type.rs | 33 +- src/commands/event.rs | 800 ++++++++++++++----------------- src/commands/event_request.rs | 7 +- src/commands/reference_type.rs | 2 - src/commands/thread_reference.rs | 4 +- src/commands/virtual_machine.rs | 39 +- src/enums.rs | 63 ++- src/event_modifier.rs | 192 ++++++++ src/lib.rs | 44 +- src/types.rs | 434 ++++++----------- tests/derive_coverage.rs | 296 ++++++++++++ tests/event.rs | 55 ++- tests/reference_type.rs | 62 ++- tests/virtual_machine.rs | 40 +- 17 files changed, 1354 insertions(+), 1026 deletions(-) create mode 100644 src/event_modifier.rs create mode 100644 tests/derive_coverage.rs diff --git a/flake.nix b/flake.nix index 3c79047..e6c07a5 100644 --- a/flake.nix +++ b/flake.nix @@ -28,8 +28,10 @@ jdk17 (rust-bin.selectLatestNightlyWith (toolchain: toolchain.rustfmt)) rust-analyzer + cargo-nextest cargo-insta + cargo-llvm-cov ]; INSTA_TEST_RUNNER = "nextest"; diff --git a/jdwp-macros/src/lib.rs b/jdwp-macros/src/lib.rs index 7c70747..7976d75 100644 --- a/jdwp-macros/src/lib.rs +++ b/jdwp-macros/src/lib.rs @@ -1,5 +1,6 @@ use proc_macro::TokenStream; +use proc_macro2::Ident; use quote::{quote, ToTokens}; use syn::{ parse::{Parse, ParseStream}, @@ -55,63 +56,61 @@ pub fn jdwp_readable(item: TokenStream) -> TokenStream { tokens.into() } Data::Enum(enum_data) => { - let fields = enum_data - .variants + let Some(repr) = derive_input + .attrs .iter() - .map(|variant| match variant.fields { - Fields::Unnamed(ref fields) if fields.unnamed.len() == 1 => { - Some((&variant.ident, &fields.unnamed[0].ty)) - } - _ => None, - }) - .collect::>>(); - - if let Some(fields) = fields { - let ident = derive_input.ident; - - let mut locals = Vec::with_capacity(fields.len()); - let mut match_arms = Vec::with_capacity(fields.len()); + .find(|attr| attr.path.is_ident("repr")) else { + return Error::new(enum_data.enum_token.span, "No explicit repr") + .to_compile_error() + .into(); + }; + let repr = repr.parse_args::().expect("TODO"); - for (i, (variant, ty)) in fields.iter().enumerate() { - locals.push(quote! { - <::core::option::Option::<#ty> as ::jdwp::codec::JdwpReadable>::read(read)? - }); - let mut match_arm = Vec::with_capacity(fields.len()); - for j in 0..fields.len() { - match_arm.push(if j == i { - quote!(::core::option::Option::Some(matched)) - } else { - quote!(::core::option::Option::None) - }) - } - match_arms.push(quote! { - (#(#match_arm),*) => ::std::result::Result::Ok(#ident::#variant(matched)) - }); - } + let mut match_arms = Vec::with_capacity(enum_data.variants.len()); - let tokens = quote! { - impl ::jdwp::codec::JdwpReadable for #ident { - fn read(read: &mut ::jdwp::codec::JdwpReader) -> ::std::io::Result { - match (#(#locals),*) { - #(#match_arms,)* - _ => ::std::result::Result::Err(::std::io::Error::new(::std::io::ErrorKind::InvalidData, "Multiple values in response")), - } - } - } - }; - tokens.into() - } else { - Error::new( - enum_data.enum_token.span, - "Can derive JdwpReadable only for enums with all variants having a single unnamed field", + for v in &enum_data.variants { + let Some((_, ref d)) = v.discriminant else { + return Error::new( + v.span(), + "No explicit discriminant", ) .to_compile_error() .into() + }; + let name = &v.ident; + let constructor = match &v.fields { + Fields::Named(named) => { + let fields = named.named.iter().map(|f| f.ident.as_ref().unwrap()); + quote!( { #(#fields: ::jdwp::codec::JdwpReadable::read(read)?,)* } ) + } + Fields::Unnamed(unnamed) => { + let fields = unnamed + .unnamed + .iter() + .map(|_| quote!(::jdwp::codec::JdwpReadable::read(read)?)); + quote!( ( #(#fields),* ) ) + } + Fields::Unit => quote!(), + }; + match_arms.push(quote!(x if x == (#d) => Self::#name #constructor)); } + let ident = &derive_input.ident; + let tokens = quote! { + impl ::jdwp::codec::JdwpReadable for #ident { + fn read(read: &mut ::jdwp::codec::JdwpReader) -> ::std::io::Result { + let res = match #repr::read(read)? { + #(#match_arms,)* + _ => return Err(::std::io::Error::from(::std::io::ErrorKind::InvalidData)), + }; + Ok(res) + } + } + }; + tokens.into() } Data::Union(union_data) => Error::new( union_data.union_token.span, - "Can derive JdwpReadable only for structs", + "Can derive JdwpReadable only for structs and enums with explicit discriminants", ) .to_compile_error() .into(), @@ -124,11 +123,6 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream { match &derive_input.data { Data::Struct(struct_data) => { - let ident = derive_input.ident; - let generic_params = derive_input.generics.params; - let generic_names = get_generic_names(&generic_params); - let generics_where = derive_input.generics.where_clause; - let write = match &struct_data.fields { Fields::Unit => quote!(), Fields::Named(named) => { @@ -146,6 +140,10 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream { quote!(#(#fields;)*) } }; + let ident = derive_input.ident; + let generic_params = derive_input.generics.params; + let generic_names = get_generic_names(&generic_params); + let generics_where = derive_input.generics.where_clause; let tokens = quote! { impl<#generic_params> ::jdwp::codec::JdwpWritable for #ident<#generic_names> #generics_where { fn write(&self, write: &mut ::jdwp::codec::JdwpWriter) -> ::std::io::Result<()> { @@ -156,15 +154,72 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream { }; tokens.into() } - Data::Enum(enum_data) => Error::new( - enum_data.enum_token.span, - "Can derive JdwpWritable only for structs", - ) - .to_compile_error() - .into(), + Data::Enum(enum_data) => { + let Some(repr) = derive_input + .attrs + .iter() + .find(|attr| attr.path.is_ident("repr")) else { + return Error::new(enum_data.enum_token.span, "No explicit repr") + .to_compile_error() + .into(); + }; + let repr = repr.parse_args::().expect("TODO"); + + let mut match_arms = Vec::with_capacity(enum_data.variants.len()); + + for v in &enum_data.variants { + let Some((_, ref d)) = v.discriminant else { + return Error::new( + v.span(), + "No explicit discriminant", + ) + .to_compile_error() + .into() + }; + + let (destruct, writes) = match &v.fields { + Fields::Named(named) => { + let names = named + .named + .iter() + .map(|f| f.ident.as_ref().unwrap()) + .collect::>(); + (quote!({ #(#names),* }), quote!(#(#names.write(write)?;)*)) + } + Fields::Unnamed(unnamed) => { + let names = (0..unnamed.unnamed.len()) + .map(|i| Ident::new(&format!("case_{i}"), unnamed.span())) + .collect::>(); + (quote!((#(#names),*)), quote!(#(#names.write(write)?;)*)) + } + Fields::Unit => (quote!(), quote!()), + }; + + let name = &v.ident; + + match_arms.push(quote! { + Self::#name #destruct => { + #repr::write(&(#d), write)?; + #writes + } + }); + } + let ident = derive_input.ident; + let tokens = quote! { + impl ::jdwp::codec::JdwpWritable for #ident { + fn write(&self, write: &mut ::jdwp::codec::JdwpWriter) -> ::std::io::Result<()> { + match self { + #(#match_arms)* + } + Ok(()) + } + } + }; + tokens.into() + } Data::Union(union_data) => Error::new( union_data.union_token.span, - "Can derive JdwpWritable only for structs", + "Can derive JdwpWritable only for structs and enums with explicit discriminants", ) .to_compile_error() .into(), diff --git a/src/codec.rs b/src/codec.rs index 5fbea7f..52e5c21 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -13,18 +13,18 @@ pub use jdwp_macros::{JdwpReadable, JdwpWritable}; use crate::{commands::virtual_machine::IDSizeInfo, functional::Coll}; #[derive(Debug)] -pub struct JdwpWriter { +pub struct JdwpWriter { write: W, pub(crate) id_sizes: IDSizeInfo, } -impl JdwpWriter { +impl JdwpWriter { pub(crate) fn new(write: W, id_sizes: IDSizeInfo) -> Self { Self { write, id_sizes } } } -impl Deref for JdwpWriter { +impl Deref for JdwpWriter { type Target = W; fn deref(&self) -> &Self::Target { @@ -32,49 +32,25 @@ impl Deref for JdwpWriter { } } -impl DerefMut for JdwpWriter { +impl DerefMut for JdwpWriter { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } } #[derive(Debug)] -pub struct JdwpReader { +pub struct JdwpReader { read: R, - buffered_byte: Option, pub(crate) id_sizes: IDSizeInfo, } -impl JdwpReader { +impl JdwpReader { pub(crate) fn new(read: R, id_sizes: IDSizeInfo) -> Self { - Self { - read, - buffered_byte: None, - id_sizes, - } - } - - pub(crate) fn peek_tag_byte(&mut self) -> io::Result { - let b = self.read.read_u8()?; - let prev = self.buffered_byte.replace(b); - assert!(prev.is_none(), "Already contained unconsumed tag byte"); - Ok(b) - } - - /// This exists to first consume the peeked byte after calling - /// [peek_tag_byte]. - /// - /// Other read methods do not consume the buffered byte, but peek_tag_byte - /// is only called before reading a u8 tag. - pub(crate) fn read_tag_byte(&mut self) -> io::Result { - match self.buffered_byte.take() { - Some(b) => Ok(b), - None => self.read.read_u8(), - } + Self { read, id_sizes } } } -impl Deref for JdwpReader { +impl Deref for JdwpReader { type Target = R; fn deref(&self) -> &Self::Target { @@ -82,7 +58,7 @@ impl Deref for JdwpReader { } } -impl DerefMut for JdwpReader { +impl DerefMut for JdwpReader { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.read } @@ -127,7 +103,7 @@ impl JdwpWritable for PhantomData { impl JdwpReadable for bool { #[inline] fn read(read: &mut JdwpReader) -> io::Result { - read.read_tag_byte().map(|n| n != 0) + read.read_u8().map(|n| n != 0) } } @@ -139,33 +115,22 @@ impl JdwpWritable for bool { } // read/write + i8/u8 methods do not have the endianness generic, eh - -impl JdwpReadable for i8 { - #[inline] - fn read(read: &mut JdwpReader) -> io::Result { - read.read_i8() - } -} - -impl JdwpWritable for i8 { - #[inline] - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - write.write_i8(*self) - } -} - -impl JdwpReadable for u8 { - #[inline] - fn read(read: &mut JdwpReader) -> io::Result { - read.read_tag_byte() - } -} - -impl JdwpWritable for u8 { - #[inline] - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - write.write_u8(*self) - } +macro_rules! big_endian_hack { + ($f:ident, $prefix:ident, i8, $arg:tt) => { + paste! { + $f.[<$prefix i8>] $arg + } + }; + ($f:ident, $prefix:ident, u8, $arg:tt) => { + paste! { + $f.[<$prefix u8>] $arg + } + }; + ($f:ident, $prefix:ident, $t:ident, $arg:tt) => { + paste! { + $f.[<$prefix $t>]:: $arg + } + }; } macro_rules! int_io { @@ -174,25 +139,21 @@ macro_rules! int_io { impl JdwpReadable for $types { #[inline] fn read(reader: &mut JdwpReader) -> io::Result { - paste! { - reader.[]::() - } + big_endian_hack!(reader, read_, $types, ()) } } impl JdwpWritable for $types { #[inline] fn write(&self, writer: &mut JdwpWriter) -> io::Result<()> { - paste! { - writer.[]::(*self) - } + big_endian_hack!(writer, write_, $types, (*self)) } } )* }; } -int_io![i16, u16, i32, u32, i64, u64, f32, f64]; +int_io![i8, u8, i16, u16, i32, u32, i64, u64, f32, f64]; impl JdwpReadable for String { #[inline] @@ -247,26 +208,37 @@ where } } -impl JdwpReadable for (A, B) { +// only writable to allow using slices as command arguments +impl JdwpWritable for &[T] +where + T: JdwpWritable, +{ + fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { + (self.len() as u32).write(write)?; + for item in *self { + item.write(write)?; + } + Ok(()) + } +} + +impl JdwpReadable for (A, B) +where + A: JdwpReadable, + B: JdwpReadable, +{ fn read(read: &mut JdwpReader) -> io::Result { Ok((A::read(read)?, B::read(read)?)) } } -impl JdwpWritable for (A, B) { +impl JdwpWritable for (A, B) +where + A: JdwpWritable, + B: JdwpWritable, +{ fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { self.0.write(write)?; self.1.write(write) } } - -// only writable to allow using slices as command arguments -impl JdwpWritable for &[T] { - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - (self.len() as u32).write(write)?; - for item in *self { - item.write(write)?; - } - Ok(()) - } -} diff --git a/src/commands/class_type.rs b/src/commands/class_type.rs index 1874360..c87b71c 100644 --- a/src/commands/class_type.rs +++ b/src/commands/class_type.rs @@ -1,6 +1,8 @@ +use std::io::{self, Read}; + use jdwp_macros::jdwp_command; -use crate::{enums::InvokeOptions, types::*}; +use crate::{codec::JdwpReader, enums::InvokeOptions, types::*}; use super::{JdwpReadable, JdwpWritable}; @@ -115,7 +117,7 @@ pub struct NewInstance<'a> { options: InvokeOptions, } -#[derive(Debug, JdwpReadable)] +#[derive(Debug)] pub enum NewInstanceReply { /// The newly created object. NewObject(TaggedObjectID), @@ -123,19 +125,16 @@ pub enum NewInstanceReply { Exception(TaggedObjectID), } -// // better types everyone -// impl JdwpReadable for NewInstanceReply { -// fn read(read: &mut JdwpReader) -> io::Result { -// let new_object = Option::::read(read)?; -// let exception = Option::::read(read)?; +// better types everyone +impl JdwpReadable for NewInstanceReply { + fn read(read: &mut JdwpReader) -> io::Result { + let new_object = Option::::read(read)?; + let exception = Option::::read(read)?; -// match (new_object, exception) { -// (Some(new_object), None) => -// Ok(NewInstanceReply::NewObject(new_object)), (None, -// Some(exception)) => Ok(NewInstanceReply::Exception(exception)), _ -// => Err(io::Error::new( io::ErrorKind::InvalidData, -// "Invalid NewInstance reply", -// )), -// } -// } -// } + match (new_object, exception) { + (Some(new_object), None) => Ok(NewInstanceReply::NewObject(new_object)), + (None, Some(exception)) => Ok(NewInstanceReply::Exception(exception)), + _ => Err(io::Error::from(io::ErrorKind::InvalidData)), + } + } +} diff --git a/src/commands/event.rs b/src/commands/event.rs index d699f77..d135fe1 100644 --- a/src/commands/event.rs +++ b/src/commands/event.rs @@ -1,7 +1,5 @@ -use std::io::{self, Read, Write}; - use crate::{ - codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, + codec::JdwpReadable, enums::{ClassStatus, EventKind, SuspendPolicy}, types::{ FieldID, Location, ReferenceTypeID, RequestID, TaggedObjectID, TaggedReferenceTypeID, @@ -11,446 +9,368 @@ use crate::{ use super::jdwp_command; -/// Notification of initialization of a target VM. -/// -/// This event is received before the main thread is started and before any -/// application code has been executed. -/// -/// Before this event occurs a significant amount of system code has -/// executed and a number of system classes have been loaded. -/// -/// This event is always generated by the target VM, even if not explicitly -/// requested. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct VmStart { - /// Request that generated event (or 0 if this event is automatically - /// generated) - pub request_id: i32, - /// Initial thread - pub thread_id: ThreadID, -} - -/// Notification of step completion in the target VM. -/// -/// The step event is generated before the code at its location is executed. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct SingleStep { - /// Request that generated event - pub request_id: i32, - /// Stepped thread - pub thread: ThreadID, - /// Location stepped to - pub location: Location, -} - -/// Notification of a breakpoint in the target VM. -/// -/// The breakpoint event is generated before the code at its location is -/// executed. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct Breakpoint { - /// Request that generated event - pub request_id: i32, - /// Thread which hit breakpoint - pub thread: ThreadID, - /// Location hit - pub location: Location, -} - -/// Notification of a method invocation in the target VM. -/// -/// This event is generated before any code in the invoked method has executed. -/// -/// Method entry events are generated for both native and non-native methods. -/// -/// In some VMs method entry events can occur for a particular thread before -/// its thread start event occurs if methods are called as part of the thread's -/// initialization. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct MethodEntry { - /// Request that generated event - pub request_id: i32, - /// Thread which entered method - pub thread: ThreadID, - /// The initial executable location in the method - pub location: Location, -} - -/// Notification of a method return in the target VM. -/// -/// This event is generated after all code in the method has executed, but the -/// location of this event is the last executed location in the method. -/// -/// Method exit events are generated for both native and non-native methods. -/// -/// Method exit events are not generated if the method terminates with a thrown -/// exception. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct MethodExit { - /// Request that generated event - pub request_id: i32, - /// Thread which exited method - pub thread: ThreadID, - /// Location of exit - pub location: Location, -} - -/// Notification of a method return in the target VM. -/// -/// This event is generated after all code in the method has executed, but the -/// location of this event is the last executed location in the method. -/// -/// Method exit events are generated for both native and non-native methods. -/// -/// Method exit events are not generated if the method terminates with a thrown -/// exception. -/// -/// Since JDWP version 1.6. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct MethodExitWithReturnValue { - /// Request that generated event - pub request_id: i32, - /// Thread which exited method - pub thread: ThreadID, - /// Location of exit - pub location: Location, - /// Value that will be returned by the method - pub value: Value, -} - -/// Notification that a thread in the target VM is attempting to enter a -/// monitor that is already acquired by another thread. -/// -/// Requires `can_request_monitor_events` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -/// -/// Since JDWP version 1.6. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct MonitorContendedEnter { - /// Request that generated event - pub request_id: i32, - /// Thread which is trying to enter the monitor - pub thread: ThreadID, - /// Monitor object reference - pub object: TaggedObjectID, - /// Location of contended monitor enter - pub location: Location, -} - -/// Notification of a thread in the target VM is entering a monitor after -/// waiting for it to be released by another thread. -/// -/// Requires `can_request_monitor_events` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -/// -/// Since JDWP version 1.6. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct MonitorContendedEntered { - /// Request that generated event - pub request_id: i32, - /// Thread which entered monitor - pub thread: ThreadID, - /// Monitor object reference - pub object: TaggedObjectID, - /// Location of contended monitor enter - pub location: Location, -} - -/// Notification of a thread about to wait on a monitor object. -/// -/// Requires `can_request_monitor_events` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -/// -/// Since JDWP version 1.6. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct MonitorWait { - /// Request that generated event - pub request_id: i32, - /// Thread which is about to wait - pub thread: ThreadID, - /// Monitor object reference - pub object: TaggedObjectID, - /// Location at which the wait will occur - pub location: Location, - /// Thread wait time in milliseconds - pub timeout: i64, -} - -/// Notification that a thread in the target VM has finished waiting on a -/// monitor object. -/// -/// Requires `can_request_monitor_events` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -/// -/// Since JDWP version 1.6. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct MonitorWaited { - /// Request that generated event - pub request_id: i32, - /// Thread which waited - pub thread: ThreadID, - /// Monitor object reference - pub object: TaggedObjectID, - /// Location at which the wait occurred - pub location: Location, - /// True if timed out - pub timed_out: bool, -} - -/// Notification of an exception in the target VM. -/// -/// If the exception is thrown from a non-native method, the exception event is -/// generated at the location where the exception is thrown. -/// -/// If the exception is thrown from a native method, the exception event is -/// generated at the first non-native location reached after the exception is -/// thrown. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct Exception { - /// Request that generated event - pub request_id: i32, - /// Thread with exception - pub thread: ThreadID, - /// Location of exception throw (or first non-native location after throw if - /// thrown from a native method) - pub location: Location, - /// Thrown exception - pub exception: TaggedObjectID, - /// Location of catch if caught. - /// - /// An exception is considered to be caught if, at the point of the throw, - /// the current location is dynamically enclosed in a try statement that - /// handles the exception. (See the JVM specification for details). - /// If there is such a try statement, the catch location is the first - /// location in the appropriate catch clause. - /// - /// If there are native methods in the call stack at the time of the - /// exception, there are important restrictions to note about the returned - /// catch location. - /// - /// In such cases, it is not possible to predict whether an exception will - /// be handled by some native method on the call stack. - /// - /// Thus, it is possible that exceptions considered uncaught here will, in - /// fact, be handled by a native method and not cause termination of the - /// target VM. - /// - /// Furthermore, it cannot be assumed that the catch location returned here - /// will ever be reached by the throwing thread. If there is a native frame - /// between the current location and the catch location, the exception - /// might be handled and cleared in that native method instead. - /// - /// Note that compilers can generate try-catch blocks in some cases where - /// they are not explicit in the source code; for example, the code - /// generated for synchronized and finally blocks can contain implicit - /// try-catch blocks. - /// - /// If such an implicitly generated try-catch is present on the call stack - /// at the time of the throw, the exception will be considered caught even - /// though it appears to be uncaught from examination of the source code. - pub catch_location: Option, -} - -/// Notification of a new running thread in the target VM. -/// -/// The new thread can be the result of a call to `java.lang.Thread.start` or -/// the result of attaching a new thread to the VM though JNI. -/// -/// The notification is generated by the new thread some time before its -/// execution starts. -/// -/// Because of this timing, it is possible to receive other events for the -/// thread before this event is received. -/// -/// (Notably, Method Entry Events and Method Exit Events might occur during -/// thread initialization. -/// -/// It is also possible for the -/// [AllThreads](super::virtual_machine::AllThreads) command to return a thread -/// before its thread start event is received. -/// -/// Note that this event gives no information about the creation of the thread -/// object which may have happened much earlier, depending on the VM being -/// debugged. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct ThreadStart { - /// Request that generated event - pub request_id: i32, - /// Started thread - pub thread: ThreadID, -} - -/// Notification of a completed thread in the target VM. -/// -/// The notification is generated by the dying thread before it terminates. -/// -/// Because of this timing, it is possible for -/// [AllThreads](super::virtual_machine::AllThreads) to return this thread -/// after this event is received. -/// -/// Note that this event gives no information about the lifetime of the thread -/// object. -/// -/// It may or may not be collected soon depending on what references exist in -/// the target VM. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct ThreadDeath { - /// Request that generated event - pub request_id: i32, - /// Ending thread - pub thread: ThreadID, -} - -/// Notification of a class prepare in the target VM. -/// -/// See the JVM specification for a definition of class preparation. -/// -/// Class prepare events are not generated for primitive classes -/// (for example, `java.lang.Integer.TYPE`). -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct ClassPrepare { - /// Request that generated event - pub request_id: i32, - /// Preparing thread. - /// - /// In rare cases, this event may occur in a debugger system thread within - /// the target VM. - /// - /// Debugger threads take precautions to prevent these events, but they - /// cannot be avoided under some conditions, especially for some subclasses - /// of `java.lang.Error`. - /// - /// If the event was generated by a debugger system thread, the value - /// returned by this method is null, and if the requested suspend policy - /// for the event was [EventThread](SuspendPolicy::EventThread) - /// all threads will be suspended instead, and the composite event's - /// suspend policy will reflect this change. - /// - /// Note that the discussion above does not apply to system threads created - /// by the target VM during its normal (non-debug) operation. - pub thread: ThreadID, - /// Type being prepared - pub ref_type_id: TaggedReferenceTypeID, - /// Type signature - pub signature: String, - /// Status of type - pub status: ClassStatus, -} - -/// Notification of a class unload in the target VM. -/// -/// There are severe constraints on the debugger back-end during garbage -/// collection, so unload information is greatly limited. -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct ClassUnload { - /// Request that generated event - pub request_id: i32, - /// Type signature - pub signature: String, -} - -/// Notification of a field access in the target VM. -/// -/// Field modifications are not considered field accesses. -/// -/// Requires `can_watch_field_access` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct FieldAccess { - /// Request that generated event - pub request_id: i32, - /// Accessing thread - pub thread: ThreadID, - /// Location of access - pub location: Location, - /// Type of field - pub ref_type_id: ReferenceTypeID, - /// Field being accessed - pub field_id: FieldID, - /// Object being accessed (None for statics) - pub object: Option, -} - -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct FieldModification { - /// Request that generated event - pub request_id: RequestID, - /// Modifying thread - pub thread: ThreadID, - /// Location of modify - pub location: Location, - /// Type of field - pub ref_type_id: TaggedReferenceTypeID, - /// Field being modified - pub field_id: FieldID, - /// Object being modified (None for statics) - pub object: Option, - /// Value to be assigned - pub value: Value, -} - -#[derive(Debug, JdwpReadable, JdwpWritable)] -pub struct VmDeath { - /// Request that generated event - pub request_id: i32, -} - -macro_rules! event_io { - ($($events:ident),* $(,)?) => { - - #[derive(Debug)] - pub enum Event { - $($events($events),)* - } - - impl JdwpReadable for Event { - fn read(read: &mut JdwpReader) -> io::Result { - match EventKind::read(read)? { - $(EventKind::$events => Ok(Event::$events($events::read(read)?)),)* - _ => Err(io::Error::from(io::ErrorKind::InvalidData)) - } - } - } - - impl JdwpWritable for Event { - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - match self { - $( - Event::$events(e) => { - EventKind::$events.write(write)?; - e.write(write)?; - } - )* - } - Ok(()) - } - } - }; -} - -event_io! { - VmStart, - SingleStep, - Breakpoint, - MethodEntry, - MethodExit, - MethodExitWithReturnValue, - MonitorContendedEnter, - MonitorContendedEntered, - MonitorWait, - MonitorWaited, - Exception, - ThreadStart, - ThreadDeath, - ClassPrepare, - ClassUnload, - FieldAccess, - FieldModification, - VmDeath, +#[derive(Debug, Clone, JdwpReadable)] +#[repr(u8)] +pub enum Event { + /// Notification of step completion in the target VM. + /// + /// The step event is generated before the code at its location is executed. + SingleStep( + /// Request that generated the event + RequestID, + /// Stepped thread + ThreadID, + /// Location stepped to + Location, + ) = EventKind::SingleStep as u8, + /// Notification of a breakpoint in the target VM. + /// + /// The breakpoint event is generated before the code at its location is + /// executed. + Breakpoint( + /// Request that generated the event + RequestID, + /// Thread which hit breakpoint + ThreadID, + /// Location hit + Location, + ) = EventKind::Breakpoint as u8, + /// Notification of a method invocation in the target VM. + /// + /// This event is generated before any code in the invoked method has + /// executed. + /// + /// Method entry events are generated for both native and non-native + /// methods. + /// + /// In some VMs method entry events can occur for a particular thread before + /// its thread start event occurs if methods are called as part of the + /// thread's initialization. + MethodEntry( + /// Request that generated the event + RequestID, + /// Thread which entered method + ThreadID, + /// The initial executable location in the method + Location, + ) = EventKind::MethodEntry as u8, + /// Notification of a method return in the target VM. + /// + /// This event is generated after all code in the method has executed, but + /// the location of this event is the last executed location in the + /// method. + /// + /// Method exit events are generated for both native and non-native methods. + /// + /// Method exit events are not generated if the method terminates with a + /// thrown exception. + MethodExit( + /// Request that generated the event + RequestID, + /// Thread which exited method + ThreadID, + /// Location of exit + Location, + ) = EventKind::MethodExit as u8, + /// Notification of a method return in the target VM. + /// + /// This event is generated after all code in the method has executed, but + /// the location of this event is the last executed location in the + /// method. + /// + /// Method exit events are generated for both native and non-native methods. + /// + /// Method exit events are not generated if the method terminates with a + /// thrown exception. + /// + /// Since JDWP version 1.6. + MethodExitWithReturnValue( + /// Request that generated the event + RequestID, + /// Thread which exited method + ThreadID, + /// Location of exit + Location, + /// Value that will be returned by the method + Value, + ) = EventKind::MethodExitWithReturnValue as u8, + /// Notification that a thread in the target VM is attempting to enter a + /// monitor that is already acquired by another thread. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorContendedEnter( + /// Request that generated the event + RequestID, + /// Thread which is trying to enter the monitor + ThreadID, + /// Monitor object reference + TaggedObjectID, + /// Location of contended monitor enter + Location, + ) = EventKind::MonitorContendedEnter as u8, + /// Notification of a thread in the target VM is entering a monitor after + /// waiting for it to be released by another thread. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorContendedEntered( + /// Request that generated the event + RequestID, + /// Thread which entered monitor + ThreadID, + /// Monitor object reference + TaggedObjectID, + /// Location of contended monitor enter + Location, + ) = EventKind::MonitorContendedEntered as u8, + /// Notification of a thread about to wait on a monitor object. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorWait( + /// Request that generated the event + RequestID, + /// Thread which is about to wait + ThreadID, + /// Monitor object reference + TaggedObjectID, + /// Location at which the wait will occur + Location, + /// Thread wait time in milliseconds + u64, + ) = EventKind::MonitorWait as u8, + /// Notification that a thread in the target VM has finished waiting on a + /// monitor object. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorWaited( + /// Request that generated the event + RequestID, + /// Thread which waited + ThreadID, + /// Monitor object reference + TaggedObjectID, + /// Location at which the wait occurred + Location, + /// True if timed out + bool, + ) = EventKind::MonitorWaited as u8, + /// Notification of an exception in the target VM. + /// + /// If the exception is thrown from a non-native method, the exception event + /// is generated at the location where the exception is thrown. + /// + /// If the exception is thrown from a native method, the exception event is + /// generated at the first non-native location reached after the exception + /// is thrown. + Exception( + /// Request that generated the event + RequestID, + /// Thread with exception + ThreadID, + /// Location of exception throw (or first non-native location after + /// throw if thrown from a native method) + Location, + /// Thrown exception + TaggedObjectID, + /// Location of catch if caught. + /// + /// An exception is considered to be caught if, at the point of the + /// throw, the current location is dynamically enclosed in a try + /// statement that handles the exception. (See the JVM + /// specification for details). If there is such a try + /// statement, the catch location is the first location in the + /// appropriate catch clause. + /// + /// If there are native methods in the call stack at the time of the + /// exception, there are important restrictions to note about the + /// returned catch location. + /// + /// In such cases, it is not possible to predict whether an exception + /// will be handled by some native method on the call stack. + /// + /// Thus, it is possible that exceptions considered uncaught here will, + /// in fact, be handled by a native method and not cause + /// termination of the target VM. + /// + /// Furthermore, it cannot be assumed that the catch location returned + /// here will ever be reached by the throwing thread. If there + /// is a native frame between the current location and the catch + /// location, the exception might be handled and cleared in that + /// native method instead. + /// + /// Note that compilers can generate try-catch blocks in some cases + /// where they are not explicit in the source code; for example, + /// the code generated for synchronized and finally blocks can + /// contain implicit try-catch blocks. + /// + /// If such an implicitly generated try-catch is present on the call + /// stack at the time of the throw, the exception will be + /// considered caught even though it appears to be uncaught from + /// examination of the source code. + Option, + ) = EventKind::Exception as u8, + /// Notification of a new running thread in the target VM. + /// + /// The new thread can be the result of a call to `java.lang.Thread.start` + /// or the result of attaching a new thread to the VM though JNI. + /// + /// The notification is generated by the new thread some time before its + /// execution starts. + /// + /// Because of this timing, it is possible to receive other events for the + /// thread before this event is received. + /// + /// (Notably, Method Entry Events and Method Exit Events might occur during + /// thread initialization. + /// + /// It is also possible for the + /// [AllThreads](super::virtual_machine::AllThreads) command to return a + /// thread before its thread start event is received. + /// + /// Note that this event gives no information about the creation of the + /// thread object which may have happened much earlier, depending on the + /// VM being debugged. + ThreadStart( + /// Request that generated the event + RequestID, + /// Started thread + ThreadID, + ) = EventKind::ThreadStart as u8, + /// Notification of a completed thread in the target VM. + /// + /// The notification is generated by the dying thread before it terminates. + /// + /// Because of this timing, it is possible for + /// [AllThreads](super::virtual_machine::AllThreads) to return this thread + /// after this event is received. + /// + /// Note that this event gives no information about the lifetime of the + /// thread object. + /// + /// It may or may not be collected soon depending on what references exist + /// in the target VM. + ThreadDeath( + /// Request that generated the event + RequestID, + /// Ending thread + ThreadID, + ) = EventKind::ThreadDeath as u8, + /// Notification of a class prepare in the target VM. + /// + /// See the JVM specification for a definition of class preparation. + /// + /// Class prepare events are not generated for primitive classes + /// (for example, `java.lang.Integer.TYPE`). + ClassPrepare( + /// Request that generated the event + RequestID, + /// Preparing thread. + /// + /// In rare cases, this event may occur in a debugger system thread + /// within the target VM. + /// + /// Debugger threads take precautions to prevent these events, but they + /// cannot be avoided under some conditions, especially for some + /// subclasses of `java.lang.Error`. + /// + /// If the event was generated by a debugger system thread, the value + /// returned by this method is null, and if the requested suspend policy + /// for the event was [EventThread](SuspendPolicy::EventThread) + /// all threads will be suspended instead, and the composite event's + /// suspend policy will reflect this change. + /// + /// Note that the discussion above does not apply to system threads + /// created by the target VM during its normal (non-debug) + /// operation. + ThreadID, + /// Type being prepared + TaggedReferenceTypeID, + /// Type signature + String, + /// Status of type + ClassStatus, + ) = EventKind::ClassPrepare as u8, + /// Notification of a class unload in the target VM. + /// + /// There are severe constraints on the debugger back-end during garbage + /// collection, so unload information is greatly limited. + ClassUnload( + /// Request that generated the event + RequestID, + /// Type signature + String, + ) = EventKind::ClassUnload as u8, + /// Notification of a field access in the target VM. + /// + /// Field modifications are not considered field accesses. + /// + /// Requires `can_watch_field_access` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + FieldAccess( + /// Request that generated the event + RequestID, + /// Accessing thread + ThreadID, + /// Location of access + Location, + /// Field being accessed + (ReferenceTypeID, FieldID), + /// Object being accessed (None for statics) + Option, + ) = EventKind::FieldAccess as u8, + /// Notification of a field modification in the target VM. Requires + /// `can_watch_field_modification` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + FieldModification( + /// Request that generated the event + RequestID, + /// Modifying thread + ThreadID, + /// Location of modify + Location, + /// Field being modified + (TaggedReferenceTypeID, FieldID), + /// Object being modified (None for statics) + Option, + /// Value to be assigned + Value, + ) = EventKind::FieldModification as u8, + /// Notification of initialization of a target VM. + /// + /// This event is received before the main thread is started and before any + /// application code has been executed. + /// + /// Before this event occurs a significant amount of system code has + /// executed and a number of system classes have been loaded. + /// + /// This event is always generated by the target VM, even if not explicitly + /// requested. + VmStart( + /// Request that generated the event (or None if this event is + /// automatically generated) + Option, + /// Initial thread + ThreadID, + ) = EventKind::VmStart as u8, + VmDeath( + /// Request that generated the event + RequestID, + ) = EventKind::VmDeath as u8, } #[jdwp_command((), 64, 100)] -#[derive(Debug, JdwpReadable)] +#[derive(Debug, Clone, JdwpReadable)] pub struct Composite { pub suspend_policy: SuspendPolicy, pub events: Vec, diff --git a/src/commands/event_request.rs b/src/commands/event_request.rs index a5a026e..37499bc 100644 --- a/src/commands/event_request.rs +++ b/src/commands/event_request.rs @@ -3,7 +3,8 @@ use super::jdwp_command; use crate::{ codec::JdwpWritable, enums::{EventKind, SuspendPolicy}, - types::{Modifier, RequestID}, + event_modifier::Modifier, + types::RequestID, }; /// Set an event request. @@ -38,13 +39,13 @@ pub struct Set<'a> { /// is placed in the event queue. /// /// Events are filtered by applying each modifier to an event in the order - /// they are specified in this collection Only events that satisfy all + /// they are specified in this collection. Only events that satisfy all /// modifiers are reported. /// /// An empty list means there are no modifiers in the request. /// /// Filtering can improve debugger performance dramatically by reducing the - /// amount of event traffic sent from the target VM to the debugger VM. + /// amount of event traffic sent from the target VM to the debugger. modifiers: &'a [Modifier], } diff --git a/src/commands/reference_type.rs b/src/commands/reference_type.rs index fc2326e..88fc38f 100644 --- a/src/commands/reference_type.rs +++ b/src/commands/reference_type.rs @@ -321,8 +321,6 @@ pub struct Instances { ref_type: ReferenceTypeID, /// Maximum number of instances to return. /// - /// Must be non-negative. - /// /// If zero, all instances are returned. max_instances: u32, } diff --git a/src/commands/thread_reference.rs b/src/commands/thread_reference.rs index cd12864..fd52b14 100644 --- a/src/commands/thread_reference.rs +++ b/src/commands/thread_reference.rs @@ -237,8 +237,8 @@ impl JdwpReadable for StackDepth { /// method are released. Note: this does not apply to JNI locks or /// java.util.concurrent.locks locks. /// -/// Events, such as [MethodExit](super::event::MethodExit), are generated as -/// they would be in a normal return. +/// Events, such as [MethodExit](super::event::Event::MethodExit), are generated +/// as they would be in a normal return. /// /// The called method must be a non-native Java programming language method. /// Forcing return on a thread with only one frame on the stack causes the diff --git a/src/commands/virtual_machine.rs b/src/commands/virtual_machine.rs index 2717a2d..ef33f3e 100644 --- a/src/commands/virtual_machine.rs +++ b/src/commands/virtual_machine.rs @@ -39,8 +39,8 @@ pub struct VersionReply { /// The search is confined to loaded classes only; no attempt is made to load a /// class of the given signature. #[jdwp_command(C, 1, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ClassesBySignatureGeneric> { +#[derive(Clone, JdwpWritable)] +pub struct ClassesBySignatureGeneric> { /// JNI signature of the class to find (for example, "Ljava/lang/String;") signature: String, _phantom: PhantomData, @@ -49,20 +49,37 @@ pub struct ClassesBySignatureGeneric> { /// This is needed because inference cannot guess what you need since there are /// no parameters /// And the Single helper type is in a private jdwp module -pub type ClassBySignature = ClassesBySignatureGeneric>; +pub type ClassBySignature = ClassesBySignatureGeneric>; + +impl Debug for ClassBySignature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClassBySignature") + .field("signature", &self.signature) + .finish() + } +} /// The inference is able to figure out N by the destructuring pattern -pub type ClassesBySignatureStatic = ClassesBySignatureGeneric<[UnnamedClass; N]>; +pub type ClassesBySignatureStatic = + ClassesBySignatureGeneric<[(TaggedReferenceTypeID, ClassStatus); N]>; + +impl Debug for ClassesBySignatureStatic { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(&format!("ClassesBySignatureStatic<{}>", N)) // hope this const-optimizes?. + .field("signature", &self.signature) + .finish() + } +} /// The 'standard' variant with a vector -pub type ClassesBySignature = ClassesBySignatureGeneric>; +pub type ClassesBySignature = ClassesBySignatureGeneric>; -#[derive(Debug, JdwpReadable)] -pub struct UnnamedClass { - /// Matching loaded reference type - pub type_id: TaggedReferenceTypeID, - /// The current class status - pub status: ClassStatus, +impl Debug for ClassesBySignature { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClassesBySignature") + .field("signature", &self.signature) + .finish() + } } /// Returns reference types for all classes currently loaded by the target VM. diff --git a/src/enums.rs b/src/enums.rs index cb74cd1..38bd44d 100644 --- a/src/enums.rs +++ b/src/enums.rs @@ -7,7 +7,7 @@ use bitflags::bitflags; use crate::codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}; -macro_rules! readable_enum { +macro_rules! jdwp_enum { ($e:ident: $repr:ident, $($name:ident = $id:literal | $string:literal),* $(,)?) => { #[repr($repr)] #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -19,21 +19,21 @@ macro_rules! readable_enum { )* } - impl $e { - pub fn from(n: $repr) -> Option { - match n { - $($id => Some($e::$name),)* - _ => None + impl TryFrom<$repr> for $e { + type Error = $repr; + + fn try_from(value: $repr) -> Result { + match value { + $($id => Ok($e::$name),)* + other => Err(other), } } } impl JdwpReadable for $e { fn read(read: &mut JdwpReader) -> std::io::Result { - match $repr::read(read)? { - $($id => Ok($e::$name),)* - _ => Err(Error::from(ErrorKind::InvalidData)) - } + Self::try_from($repr::read(read)?) + .map_err(|_| Error::from(ErrorKind::InvalidData)) } } @@ -44,10 +44,10 @@ macro_rules! readable_enum { } }; ($e:ident: $repr:ident, $($name:ident = $id:literal),* $(,)?) => { - readable_enum!($e: $repr, $($name = $id | "",)*); + jdwp_enum!($e: $repr, $($name = $id | "",)*); }; ($e:ident: $repr:ident | Display, $($name:ident = $id:literal | $string:literal),* $(,)?) => { - readable_enum!($e: $repr, $($name = $id | $string,)*); + jdwp_enum!($e: $repr, $($name = $id | $string,)*); impl Display for $e { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -59,7 +59,7 @@ macro_rules! readable_enum { }; } -readable_enum! { +jdwp_enum! { ErrorCode: u16 | Display, None = 0 | "No error has occurred", @@ -121,7 +121,7 @@ readable_enum! { InvalidCount = 512 | "The count is invalid", } -readable_enum! { +jdwp_enum! { EventKind: u8, SingleStep = 1, @@ -149,7 +149,7 @@ readable_enum! { VmDisconnected = 100, } -readable_enum! { +jdwp_enum! { ThreadStatus: u32, Zombie = 0, @@ -159,7 +159,7 @@ readable_enum! { Wait = 4, } -readable_enum! { +jdwp_enum! { SuspendStatus: u32, NotSuspended = 0, @@ -190,7 +190,7 @@ impl JdwpWritable for ClassStatus { } } -readable_enum! { +jdwp_enum! { TypeTag: u8, Class = 1 | "ReferenceType is a class", @@ -198,7 +198,16 @@ readable_enum! { Array = 3 | "ReferenceType is an array", } -readable_enum! { +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + Ok(match u8::read(read)? { + 0 => None, + raw => Some(TypeTag::try_from(raw).map_err(|_| Error::from(ErrorKind::InvalidData))?), + }) + } +} + +jdwp_enum! { Tag: u8, Array = 91 | "'[' - an array object ([ObjectID](crate::types::ObjectID) size).", @@ -219,7 +228,16 @@ readable_enum! { ClassObject = 99 | "'c' - a class object object ([ObjectID](crate::types::ObjectID) size).", } -readable_enum! { +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + Ok(match u8::read(read)? { + 0 => None, + raw => Some(Tag::try_from(raw).map_err(|_| Error::from(ErrorKind::InvalidData))?), + }) + } +} + +jdwp_enum! { StepDepth: u32, Into = 0 | "Step into any method calls that occur before the end of the step", @@ -227,14 +245,14 @@ readable_enum! { Out = 2 | "Step out of the current method", } -readable_enum! { +jdwp_enum! { StepSize: u32, Min = 0 | "Step by the minimum possible amount (often a byte code instruction)", Line = 1 | "Step to the next source line unless there is no line number information in which case a MIN step is done instead", } -readable_enum! { +jdwp_enum! { SuspendPolicy: u8, None = 0 | "Suspend no threads when this event is encountered", @@ -245,6 +263,7 @@ readable_enum! { bitflags! { #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub struct InvokeOptions: u32 { + const NONE = 0x00; /// otherwise, all threads started const SINGLE_THREADED = 0x01; /// otherwise, normal virtual invoke (instance methods only) @@ -264,7 +283,7 @@ impl JdwpWritable for InvokeOptions { } } -readable_enum! { +jdwp_enum! { ModifierKind: u8, Count = 1 | "Limit the requested event to be reported at most once after a given number of occurrences. The event is not reported the first count - 1 times this filter is reached. To request a one-off event, call this method with a count of 1.", diff --git a/src/event_modifier.rs b/src/event_modifier.rs new file mode 100644 index 0000000..636dd54 --- /dev/null +++ b/src/event_modifier.rs @@ -0,0 +1,192 @@ +use crate::{ + codec::JdwpWritable, + enums::{ModifierKind, StepDepth, StepSize}, + types::{FieldID, Location, ObjectID, ReferenceTypeID, ThreadID}, +}; + +#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpWritable)] +#[repr(u8)] +pub enum Modifier { + /// Limit the requested event to be reported at most once after a given + /// number of occurrences. + /// + /// The event is not reported the first count - 1 times this filter is + /// reached. + /// + /// To request a one-off event, call this method with a count of 1. + /// + /// Once the count reaches 0, any subsequent filters in this request are + /// applied. + /// + /// If none of those filters cause the event to be suppressed, the event is + /// reported. + /// + /// Otherwise, the event is not reported. + /// + /// In either case subsequent events are never reported for this request. + /// + /// This modifier can be used with any event kind. + Count( + /// Count before event. One for one-off + i32, + ) = ModifierKind::Count as u8, + /// Conditional on expression + Conditional { + /// For the future + expr_id: i32, + } = ModifierKind::Conditional as u8, + /// Restricts reported events to those in the given thread. + /// This modifier can be used with any event kind except for class unload. + ThreadOnly( + /// Required thread + ThreadID, + ) = ModifierKind::ThreadOnly as u8, + /// For class prepare events, restricts the events generated by this request + /// to be the preparation of the given reference type and any subtypes. + /// + /// For monitor wait and waited events, restricts the events generated by + /// this request to those whose monitor object is of the given reference + /// type or any of its subtypes. + /// + /// For other events, restricts the events generated by this request to + /// those whose location is in the given reference type or any of its + /// subtypes. + /// + /// An event will be generated for any location in a reference type that can + /// be safely cast to the given reference type. + /// + /// This modifier can be used with any event kind except class unload, + /// thread start, and thread end. + ClassOnly( + /// Required class + ReferenceTypeID, + ) = ModifierKind::ClassOnly as u8, + /// Restricts reported events to those for classes whose name matches the + /// given restricted regular expression. + /// + /// For class prepare events, the prepared class name is matched. + /// + /// For class unload events, the unloaded class name is matched. + /// + /// For monitor wait and waited events, the name of the class of the monitor + /// object is matched. + /// + /// For other events, the class name of the event's location is matched. + /// + /// This modifier can be used with any event kind except thread start and + /// thread end. + ClassMatch( + /// Required class pattern. + /// + /// Matches are limited to exact matches of the given class pattern and + /// matches of patterns that begin or end with `*`; for example, `*.Foo` + /// or `java.*`. + String, + ) = ModifierKind::ClassMatch as u8, + /// Restricts reported events to those for classes whose name does not match + /// the given restricted regular expression. + /// + /// For class prepare events, the prepared class name is matched. + /// + /// For class unload events, the unloaded class name is matched. + /// + /// For monitor wait and waited events, the name of the class of the monitor + /// object is matched. + /// + /// For other events, the class name of the event's location is matched. + /// + /// This modifier can be used with any event kind except thread start and + /// thread end. + ClassExclude( + /// Disallowed class pattern. + /// + /// Matches are limited to exact matches of the given class pattern and + /// matches of patterns that begin or end with `*`; for example, `*.Foo` + /// or `java.*`. + String, + ) = ModifierKind::ClassExclude as u8, + /// Restricts reported events to those that occur at the given location. + /// + /// This modifier can be used with breakpoint, field access, field + /// modification, step, and exception event kinds. + LocationOnly( + /// Required location + Location, + ) = ModifierKind::LocationOnly as u8, + /// Restricts reported exceptions by their class and whether they are caught + /// or uncaught. + /// + /// This modifier can be used with exception event kinds only. + ExceptionOnly { + /// Exception to report. `None` means report exceptions of all types. + /// + /// A non-null type restricts the reported exception events to + /// exceptions of the given type or any of its subtypes. + exception: Option, + /// Report caught exceptions + uncaught: bool, + /// Report uncaught exceptions. + /// + /// Note that it is not always possible to determine whether an + /// exception is caught or uncaught at the time it is thrown. + /// + /// See the exception event catch location under composite events for + /// more information. + caught: bool, + } = ModifierKind::ExceptionOnly as u8, + /// Restricts reported events to those that occur for a given field. + /// + /// This modifier can be used with field access and field modification event + /// kinds only. + FieldOnly( + /// Type in which field is declared + ReferenceTypeID, + /// Required field + FieldID, + ) = ModifierKind::FieldOnly as u8, + /// Restricts reported step events to those which satisfy depth and size + /// constraints. + /// + /// This modifier can be used with step event kinds only. + Step( + /// Thread in which to step + ThreadID, + /// Size of each step + StepSize, + /// Relative call stack limit + StepDepth, + ) = ModifierKind::Step as u8, + /// Restricts reported events to those whose active 'this' object is the + /// given object. + /// + /// Match value is the null object for static methods. + /// + /// This modifier can be used with any event kind except class prepare, + /// class unload, thread start, and thread end. + /// + /// Introduced in JDWP version 1.4. + InstanceOnly( + /// Required 'this' object + ObjectID, + ) = ModifierKind::InstanceOnly as u8, + /// Restricts reported class prepare events to those for reference types + /// which have a source name which matches the given restricted regular + /// expression. + /// + /// The source names are determined by the reference type's + /// SourceDebugExtension. + /// + /// This modifier can only be used with class prepare events. + /// + /// Since JDWP version 1.6. + /// + /// Requires the `can_use_source_name_filters` capability - see + /// [CapabilitiesNew](crate::commands::virtual_machine::CapabilitiesNew). + SourceNameMatch( + /// Required source name pattern. + /// Matches are limited to exact matches of the given pattern and + /// matches of patterns that begin or end with `*`; for example, + /// `*.Foo` or `java.*` + String, + ) = ModifierKind::SourceNameMatch as u8, +} diff --git a/src/lib.rs b/src/lib.rs index a215cdc..0ba64a1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,12 +2,7 @@ extern crate self as jdwp; -use std::{ - fmt::{Display, Formatter}, - io::{Error, ErrorKind, Read, Write}, -}; - -use byteorder::WriteBytesExt; +use std::fmt::{Display, Formatter}; use crate::{ codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, @@ -18,6 +13,7 @@ pub mod client; pub mod codec; pub mod commands; pub mod enums; +pub mod event_modifier; pub mod jvm; pub mod types; @@ -45,14 +41,14 @@ impl Display for CommandId { } } +#[derive(Debug, Copy, Clone, JdwpReadable, JdwpWritable)] #[repr(u8)] -#[derive(Debug, Copy, Clone)] enum PacketMeta { Command(CommandId) = 0x00, Reply(ErrorCode) = 0x80, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, JdwpReadable, JdwpWritable)] pub struct PacketHeader { length: u32, id: u32, @@ -62,35 +58,3 @@ pub struct PacketHeader { impl PacketHeader { pub(crate) const JDWP_SIZE: usize = 11; } - -impl JdwpReadable for PacketHeader { - fn read(reader: &mut JdwpReader) -> std::io::Result { - let length = u32::read(reader)?; - let id = u32::read(reader)?; - let meta = match u8::read(reader)? { - 0x00 => PacketMeta::Command(CommandId::read(reader)?), - 0x80 => PacketMeta::Reply(ErrorCode::read(reader)?), - _ => Err(Error::from(ErrorKind::InvalidData))?, - }; - Ok(PacketHeader { length, id, meta }) - } -} - -impl JdwpWritable for PacketHeader { - fn write(&self, writer: &mut JdwpWriter) -> std::io::Result<()> { - self.length.write(writer)?; - self.id.write(writer)?; - match self.meta { - PacketMeta::Command(id) => { - // oh well maybe someday we'll be able to get the enum discriminant - // (or I make the derive work for such enums) - writer.write_u8(0x00)?; - id.write(writer) - } - PacketMeta::Reply(error_code) => { - writer.write_u8(0x80)?; - error_code.write(writer) - } - } - } -} diff --git a/src/types.rs b/src/types.rs index 286b836..b76e43b 100644 --- a/src/types.rs +++ b/src/types.rs @@ -8,9 +8,27 @@ use std::{ ops::Deref, }; -use crate::enums::{ModifierKind, StepDepth, StepSize}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +pub trait JdwpId: Clone + Copy { + type Raw; + + /// Creates an instance of Self from an arbitrary number. + /// + /// This is not unsafe as invalid IDs do not cause UB, but it is up to the + /// caller to ensure that the id is valid for the target JVM. + /// + /// Also nothing prevents you from getting an ID from one JVM and using it + /// for another, or creating it through [JdwpReadable::read]. + fn from_raw(raw: Self::Raw) -> Self; + + /// The underlying raw value. + /// + /// It is opaque and given out by the JVM, but in case you ever need it, + /// here it is. + fn raw(self) -> Self::Raw; +} + /// Uniquely identifies an object in the target VM. /// /// A particular object will be identified by exactly one [ObjectID] in JDWP @@ -27,6 +45,7 @@ use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; /// [DisableCollection](super::commands::object_reference::DisableCollection) /// command, but it is not usually necessary to do so. #[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[repr(transparent)] pub struct ObjectID(u64); /// Uniquely identifies a method in some class in the target VM. @@ -40,6 +59,7 @@ pub struct ObjectID(u64); /// The [ReferenceTypeID] can identify either the declaring type of the method /// or a subtype. #[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[repr(transparent)] pub struct MethodID(u64); /// Uniquely identifies a field in some class in the target VM. @@ -53,6 +73,7 @@ pub struct MethodID(u64); /// The [ReferenceTypeID] can identify either the declaring type of the field /// or a subtype. #[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[repr(transparent)] pub struct FieldID(u64); /// Uniquely identifies a frame in the target VM. @@ -62,6 +83,7 @@ pub struct FieldID(u64); /// /// The [FrameID] need only be valid during the time its thread is suspended. #[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[repr(transparent)] pub struct FrameID(u64); /// Uniquely identifies a reference type in the target VM. @@ -74,22 +96,22 @@ pub struct FrameID(u64); /// reused to identify a different reference type, regardless of whether the /// referenced class has been unloaded. #[derive(Copy, Clone, PartialEq, Eq, Hash)] +#[repr(transparent)] pub struct ReferenceTypeID(u64); macro_rules! ids { ($($id:ident: $tpe:ident),* $(,)?) => { $( - impl $tpe { - #[doc = "Creates a new instance of ["] - #[doc = stringify!($tpe)] - #[doc = "] from an arbitrary number."] - /// # Safety - /// It is up to the caller to ensure that the id does indeed exist and is correct. - /// - /// You can 'safely' obtain it by [JdwpReadable::read], but that will be incorrect obviously. - pub const unsafe fn new(raw: u64) -> Self { + impl JdwpId for $tpe { + type Raw = u64; + + fn from_raw(raw: u64) -> Self { Self(raw) } + + fn raw(self) -> u64 { + self.0 + } } impl Debug for $tpe { @@ -139,11 +161,13 @@ ids! { /// Uniquely identifies an object in the target VM that is known to be a thread. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct ThreadID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a thread /// group. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct ThreadGroupID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a string @@ -151,52 +175,56 @@ pub struct ThreadGroupID(ObjectID); /// /// Note: this is very different from string, which is a value. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct StringID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a class /// loader object. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct ClassLoaderID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be a class /// object. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct ClassObjectID(ObjectID); /// Uniquely identifies an object in the target VM that is known to be an array. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct ArrayID(ObjectID); /// Uniquely identifies a reference type in the target VM that is known to be /// a class type. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct ClassID(ReferenceTypeID); /// Uniquely identifies a reference type in the target VM that is known to be /// an interface type. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct InterfaceID(ReferenceTypeID); /// Uniquely identifies a reference type in the target VM that is known to be /// an array type. #[derive(Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(transparent)] pub struct ArrayTypeID(ReferenceTypeID); macro_rules! wrapper_ids { ($($deref:ident {$($tpe:ident),* $(,)?})*) => { $($( - impl $tpe { - #[doc = "Creates a new instance of ["] - #[doc = stringify!($tpe)] - #[doc = "] from an arbitrary ["] - #[doc = stringify!($deref)] - #[doc = "]."] - /// # Safety - /// It is up to the caller to ensure that the id does indeed correspond to this type. - /// - /// You can 'safely' obtain it by [JdwpReadable::read], but that will be incorrect obviously. - pub const unsafe fn new(id: $deref) -> Self { - Self(id) + impl JdwpId for $tpe { + type Raw = u64; + + fn from_raw(raw: u64) -> Self { + Self($deref::from_raw(raw)) + } + + fn raw(self) -> u64 { + self.0.0 } } @@ -292,10 +320,17 @@ impl Value { /// A writable-only wrapper around [Value] that only writes the value itself /// without a tag. +/// /// Used in places where JDWP specifies an `untagged-value` type and expects /// no tag since it should be derived from context. #[derive(Debug, Copy, Clone, PartialEq)] -pub struct UntaggedValue(Value); +pub struct UntaggedValue(pub Value); + +impl From for UntaggedValue { + fn from(value: Value) -> Self { + Self(value) + } +} impl Deref for UntaggedValue { type Target = Value; @@ -322,7 +357,7 @@ impl JdwpWritable for UntaggedValue { } } -macro_rules! tagged_enum { +macro_rules! tagged_jdwp_io { ($enum:ident <-> $tag:ident, $($tpe:ident),* { $($read_extras:tt)* } { $($write_extras:tt)* }) => { impl JdwpReadable for $enum { fn read(read: &mut JdwpReader) -> io::Result { @@ -345,10 +380,10 @@ macro_rules! tagged_enum { } } }; - ($($tt:tt)*) => { tagged_enum!($($tt)* {} {}); } + ($($tt:tt)*) => { tagged_jdwp_io!($($tt)* {} {}); } } -tagged_enum! { +tagged_jdwp_io! { Value <-> Tag, Byte, Boolean, Char, Int, Short, Long, Float, Double, Object { @@ -412,7 +447,7 @@ impl Deref for TaggedObjectID { } } -tagged_enum! { +tagged_jdwp_io! { TaggedObjectID <-> Tag, Array, Object, String, Thread, ThreadGroup, ClassLoader, ClassObject { _ => Err(io::Error::from(io::ErrorKind::InvalidData)) } @@ -480,7 +515,7 @@ impl ArrayRegion { } } -tagged_enum! { +tagged_jdwp_io! { ArrayRegion <-> Tag, Byte, Boolean, Char, Short, Int, Long, Float, Double, Object { _ => Err(io::Error::from(io::ErrorKind::InvalidData)) } @@ -531,7 +566,7 @@ impl Deref for TaggedReferenceTypeID { } } -tagged_enum! { +tagged_jdwp_io! { TaggedReferenceTypeID <-> TypeTag, Class, Interface, Array } @@ -566,279 +601,118 @@ pub struct Location { pub index: u64, } -macro_rules! optional_impl { - ($($tpe:ident),* $(,)?) => { - $( - impl JdwpReadable for Option<$tpe> { - fn read(read: &mut JdwpReader) -> io::Result { - if read.peek_tag_byte()? != 0 { - JdwpReadable::read(read).map(Some) - } else { - read.read_tag_byte()?; // consume it - Ok(None) - } - } - } - - impl JdwpWritable for Option<$tpe> { - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - match self { - Some(x) => x.write(write), - None => write.write_u8(0), - } - } - } - )* - }; -} - -optional_impl![Value, TaggedObjectID, Location]; - -/// An opaque type for the request id, which is represented in JDWP docs as just -/// a raw integer and exists only here in Rust similar to all the other IDs. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct RequestID(i32); +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + use TaggedReferenceTypeID::*; -impl RequestID { - /// Creates a new instance of [RequestID] from an arbitrary number. - /// # Safety - /// It is up to the caller to ensure that the id does indeed exist and is - /// correct. You can 'safely' obtain it by [JdwpReadable::read], but - /// that will be incorrect obviously. - pub const unsafe fn new(id: i32) -> Self { - Self(id) + let Some(tag) = Option::::read(read)? else { return Ok(None); }; + let id = ReferenceTypeID::read(read)?; + let res = match tag { + TypeTag::Class => Class(ClassID(id)), + TypeTag::Interface => Interface(InterfaceID(id)), + TypeTag::Array => Array(ArrayTypeID(id)), + }; + Ok(Some(res)) } } -/// Limit the requested event to be reported at most once after a given number -/// of occurrences. -/// -/// The event is not reported the first count - 1 times this filter is reached. -/// -/// To request a one-off event, call this method with a count of 1. -/// -/// Once the count reaches 0, any subsequent filters in this request are -/// applied. -/// -/// If none of those filters cause the event to be suppressed, the event is -/// reported. -/// -/// Otherwise, the event is not reported. -/// -/// In either case subsequent events are never reported for this request. -/// -/// This modifier can be used with any event kind. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct Count { - /// Count before event. One for one-off - pub count: i32, -} - -/// Conditional on expression -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct Conditional { - /// For the future - pub expr_id: i32, -} - -/// Restricts reported events to those in the given thread. -/// This modifier can be used with any event kind except for class unload. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct ThreadOnly { - /// Required thread - pub thread: ThreadID, -} - -/// For class prepare events, restricts the events generated by this request to -/// be the preparation of the given reference type and any subtypes. -/// -/// For monitor wait and waited events, restricts the events generated by this -/// request to those whose monitor object is of the given reference type or any -/// of its subtypes. -/// -/// For other events, restricts the events generated by this request to those -/// whose location is in the given reference type or any of its subtypes. -/// -/// An event will be generated for any location in a reference type that can be -/// safely cast to the given reference type. -/// -/// This modifier can be used with any event kind except class unload, thread -/// start, and thread end. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct ClassOnly { - /// Required class - pub class: ReferenceTypeID, +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + let Some(reference_id) = Option::::read(read)? else { return Ok(None); }; + let res = Location { + reference_id, + method_id: JdwpReadable::read(read)?, + index: JdwpReadable::read(read)?, + }; + Ok(Some(res)) + } } -/// Restricts reported events to those for classes whose name matches the given -/// restricted regular expression. -/// -/// For class prepare events, the prepared class name is matched. -/// -/// For class unload events, the unloaded class name is matched. -/// -/// For monitor wait and waited events, the name of the class of the monitor -/// object is matched. -/// -/// For other events, the class name of the event's location is matched. -/// -/// This modifier can be used with any event kind except thread start and -/// thread end. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct ClassMatch { - /// Required class pattern. - /// - /// Matches are limited to exact matches of the given class pattern and - /// matches of patterns that begin or end with `*`; for example, `*.Foo` or - /// `java.*`. - pub class_pattern: String, +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + use JdwpReadable as R; + use Value::*; + + let Some(tag) = Option::::read(read)? else { return Ok(None); }; + let res = match tag { + Tag::Byte => Byte(R::read(read)?), + Tag::Char => Char(R::read(read)?), + Tag::Short => Short(R::read(read)?), + Tag::Int => Int(R::read(read)?), + Tag::Long => Long(R::read(read)?), + Tag::Float => Float(R::read(read)?), + Tag::Double => Double(R::read(read)?), + Tag::Object => Object(R::read(read)?), + Tag::Boolean => Boolean(R::read(read)?), + _ => return Err(io::Error::from(io::ErrorKind::InvalidData)), + }; + Ok(Some(res)) + } } -/// Restricts reported events to those for classes whose name does not match -/// the given restricted regular expression. -/// -/// For class prepare events, the prepared class name is matched. -/// -/// For class unload events, the unloaded class name is matched. -/// -/// For monitor wait and waited events, the name of the class of the monitor -/// object is matched. -/// -/// For other events, the class name of the event's location is matched. -/// -/// This modifier can be used with any event kind except thread start and -/// thread end. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct ClassExclude { - /// Disallowed class pattern. - /// - /// Matches are limited to exact matches of the given class pattern and - /// matches of patterns that begin or end with `*`; for example, `*.Foo` or - /// `java.*`. - pub class_pattern: String, -} +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + use JdwpReadable as R; + use TaggedObjectID::*; -/// Restricts reported events to those that occur at the given location. -/// -/// This modifier can be used with breakpoint, field access, field -/// modification, step, and exception event kinds. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct LocationOnly { - /// Required location - pub location: Location, + let Some(tag) = Option::::read(read)? else { return Ok(None); }; + let res = match tag { + Tag::Array => Array(R::read(read)?), + Tag::Object => Object(R::read(read)?), + Tag::String => String(R::read(read)?), + Tag::Thread => Thread(R::read(read)?), + Tag::ThreadGroup => ThreadGroup(R::read(read)?), + Tag::ClassLoader => ClassLoader(R::read(read)?), + Tag::ClassObject => ClassObject(R::read(read)?), + _ => return Err(io::Error::from(io::ErrorKind::InvalidData)), + }; + Ok(Some(res)) + } } -/// Restricts reported exceptions by their class and whether they are caught or -/// uncaught. -/// -/// This modifier can be used with exception event kinds only. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct ExceptionOnly { - /// Exception to report. `None` means report exceptions of all types. - /// - /// A non-null type restricts the reported exception events to exceptions - /// of the given type or any of its subtypes. - pub exception: Option, - /// Report caught exceptions - pub uncaught: bool, - /// Report uncaught exceptions. - /// - /// Note that it is not always possible to determine whether an exception - /// is caught or uncaught at the time it is thrown. - /// - /// See the exception event catch location under composite events for more - /// information. - pub caught: bool, -} +/// An opaque type for the request id, which is represented in JDWP docs as just +/// a raw integer and exists only here in Rust similar to all the other IDs. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +pub struct RequestID(i32); -/// Restricts reported events to those that occur for a given field. -/// -/// This modifier can be used with field access and field modification event -/// kinds only. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct FieldOnly { - /// Type in which field is declared - pub declaring: ReferenceTypeID, - /// Required field - pub field_id: FieldID, -} +impl JdwpId for RequestID { + type Raw = i32; -/// Restricts reported step events to those which satisfy depth and size -/// constraints. -/// -/// This modifier can be used with step event kinds only. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct Step { - /// Thread in which to step - pub thread: ThreadID, - /// Size of each step - pub size: StepSize, - /// Relative call stack limit - pub depth: StepDepth, -} + fn from_raw(raw: i32) -> Self { + Self(raw) + } -/// Restricts reported events to those whose active 'this' object is the given -/// object. -/// -/// Match value is the null object for static methods. -/// -/// This modifier can be used with any event kind except class prepare, -/// class unload, thread start, and thread end. -/// -/// Introduced in JDWP version 1.4. -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct InstanceOnly { - /// Required 'this' object - pub instance: ObjectID, + fn raw(self) -> i32 { + self.0 + } } -/// Restricts reported class prepare events to those for reference types which -/// have a source name which matches the given restricted regular expression. -/// -/// The source names are determined by the reference type's -/// SourceDebugExtension. -/// -/// This modifier can only be used with class prepare events. -/// -/// Since JDWP version 1.6. -/// -/// Requires the `can_use_source_name_filters` capability - see -/// [CapabilitiesNew](crate::commands::virtual_machine::CapabilitiesNew). -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct SourceNameMatch { - /// Required source name pattern. - /// Matches are limited to exact matches of the given pattern and matches - /// of patterns that begin or end with `*`; for example, `*.Foo` or - /// `java.*` - pub source_name_pattern: String, -} - -#[derive(Debug, Clone, PartialEq, Eq, Hash)] -pub enum Modifier { - Count(Count), - Conditional(Conditional), - ThreadOnly(ThreadOnly), - ClassOnly(ClassOnly), - ClassMatch(ClassMatch), - ClassExclude(ClassExclude), - LocationOnly(LocationOnly), - ExceptionOnly(ExceptionOnly), - FieldOnly(FieldOnly), - Step(Step), - InstanceOnly(InstanceOnly), - SourceNameMatch(SourceNameMatch), -} - -tagged_enum! { - Modifier <-> ModifierKind, - Count, Conditional, ThreadOnly, ClassOnly, ClassMatch, ClassExclude, LocationOnly, ExceptionOnly, FieldOnly, Step, InstanceOnly, SourceNameMatch +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + match i32::read(read)? { + 0 => Ok(None), + x => Ok(Some(RequestID(x))), + } + } } /// A response from 3 types of invoke method commands: virtual, static and /// interface static. The response is either a value or an exception - we model /// it with a enum for better ergonomics. -#[derive(Debug, JdwpReadable)] +#[derive(Debug)] pub enum InvokeMethodReply { Value(Value), Exception(TaggedObjectID), } + +impl JdwpReadable for InvokeMethodReply { + fn read(read: &mut JdwpReader) -> io::Result { + let value = Option::::read(read)?; + let exception = Option::::read(read)?; + match (value, exception) { + (Some(value), None) => Ok(InvokeMethodReply::Value(value)), + (None, Some(exception)) => Ok(InvokeMethodReply::Exception(exception)), + _ => Err(io::Error::from(io::ErrorKind::InvalidData)), + } + } +} diff --git a/tests/derive_coverage.rs b/tests/derive_coverage.rs new file mode 100644 index 0000000..86e356b --- /dev/null +++ b/tests/derive_coverage.rs @@ -0,0 +1,296 @@ +use insta::assert_snapshot; +use jdwp::{ + enums::{EventKind, InvokeOptions, SuspendPolicy, Tag}, + event_modifier::Modifier, + types::{JdwpId, TaggedObjectID, Value}, +}; + +macro_rules! debug_and_clone { + ($($p:ident::{$($e:expr,)*},)*) => {{ + let mut s = String::new(); + $( + s.push_str(stringify!($p)); + s.push_str("::{\n"); + $( + s.push_str(&format!(" {:?}\n", { + use jdwp::commands::$p::*; + $e.clone() + })); + )* + s.push_str("}\n"); + )* + s + }} +} + +fn id() -> T +where + T: JdwpId, + T::Raw: From, + // u16 fits in both u64 and i32, and so has an errorless Into impl for them, lol +{ + T::from_raw(123.into()) +} + +#[test] +fn manually_cover_clone_and_debug() { + let all_commands = debug_and_clone![ + virtual_machine::{ + Version, + ClassBySignature::new("Ljava/lang/Object;"), + ClassesBySignatureStatic::<2>::new("Ljava/lang/Object;"), + ClassesBySignature::new("Ljava/lang/Object;"), + AllClasses, + AllThreads, + TopLevelThreadGroups, + Dispose, + IDSizes, + Suspend, + Resume, + Exit::new(0), + CreateString::new("Hello, world!"), + Capabilities, + ClassPaths, + DisposeObjects::new(&[(id(), 123)]), + HoldEvents, + ReleaseEvents, + CapabilitiesNew, + RedefineClasses::new(&[(id(), vec![1, 2, 3, 123])]), + SetDefaultStratum::new("NotJava"), + AllClassesWithGeneric, + InstanceCounts::new([id()]), + }, + reference_type::{ + Signature::new(id()), + ClassLoader::new(id()), + Modifiers::new(id()), + Fields::new(id()), + Methods::new(id()), + GetValues::new(id(), vec![id(), id()]), + GetValues::new(id(), [id(), id()]), + SourceFile::new(id()), + NestedTypes::new(id()), + Status::new(id()), + Interfaces::new(id()), + ClassObject::new(id()), + SourceDebugExtension::new(id()), + SignatureWithGeneric::new(id()), + FieldsWithGeneric::new(id()), + MethodsWithGeneric::new(id()), + Instances::new(id(), 10), + ClassFileVersion::new(id()), + ConstantPool::new(id()), + }, + class_type::{ + Superclass::new(id()), + SetValues::new(id(), &[(id(), Value::Int(123).into()), (id(), Value::Object(id()).into())]), + InvokeMethod::new(id(), id(), id(), &[Value::Int(123), Value::Object(id())], InvokeOptions::NONE), + NewInstance::new(id(), id(), id(), &[Value::Int(123), Value::Object(id())], InvokeOptions::SINGLE_THREADED), + }, + array_type::{ + NewInstance::new(id(), 10), + }, + interface_type::{ + InvokeMethod::new(id(), id(), id(), &[Value::Int(123), Value::Object(id())], InvokeOptions::NONE), + }, + method::{ + LineTable::new(id(), id()), + VariableTable::new(id(), id()), + Bytecodes::new(id(), id()), + IsObsolete::new(id(), id()), + VariableTableWithGeneric::new(id(), id()), + }, + object_reference::{ + ReferenceType::new(id()), + GetValues::new(id(), vec![id(), id()]), + SetValues::new(id(), &[(id(), Value::Int(123).into()), (id(), Value::Object(id()).into())]), + MonitorInfo::new(id()), + InvokeMethod::new(id(), id(), id(), id(), &[Value::Int(123), Value::Object(id())], InvokeOptions::NONE), + DisableCollection::new(id()), + EnableCollection::new(id()), + IsCollected::new(id()), + ReferringObjects::new(id(), 10), + }, + string_reference::{ + Value::new(id()), + }, + thread_reference::{ + Name::new(id()), + Suspend::new(id()), + Resume::new(id()), + Status::new(id()), + ThreadGroup::new(id()), + Frames::new(id(), 0, FrameLimit::AllRemaining), + Frames::new(id(), 2, FrameLimit::Limit(10)), + FrameCount::new(id()), + OwnedMonitors::new(id()), + CurrentContendedMonitor::new(id()), + Stop::new(id(), TaggedObjectID::Object(id())), + Interrupt::new(id()), + SuspendCount::new(id()), + OwnedMonitorsStackDepthInfo::new(id()), + ForceEarlyReturn::new(id(), Value::Boolean(false)), + }, + thread_group_reference::{ + Name::new(id()), + Parent::new(id()), + Children::new(id()), + }, + array_reference::{ + Length::new(id()), + GetValues::new(id(), 0, 10), + SetValues::new(id(), 0, &[Value::Float(123.0).into(), Value::Float(42.0).into()]), + }, + class_loader_reference::{ + VisibleClasses::new(id()), + }, + event_request::{ + Set::new(EventKind::SingleStep, SuspendPolicy::All, &[Modifier::Count(10), Modifier::ThreadOnly(id())]), + Clear::new(EventKind::Breakpoint, id()), + ClearAllBreakpoints, + }, + stack_frame::{ + GetValues::new(id(), id(), vec![(1, Tag::Object), (2, Tag::Int)]), + GetValues::new(id(), id(), [(1, Tag::Object), (2, Tag::Int)]), + SetValues::new(id(), id(), &[(1, Value::Int(123)), (2, Value::Object(id()))]), + ThisObject::new(id(), id()), + PopFrames::new(id(), id()), + }, + class_object_reference::{ + ReflectedType::new(id()), + }, + event::{ + Composite::new(SuspendPolicy::EventThread, vec![Event::VmStart(Some(id()), id())]), + }, + ]; + + assert_snapshot!(all_commands, @r###" + virtual_machine::{ + Version + ClassBySignature { signature: "Ljava/lang/Object;" } + ClassesBySignatureStatic<2> { signature: "Ljava/lang/Object;" } + ClassesBySignature { signature: "Ljava/lang/Object;" } + AllClasses + AllThreads + TopLevelThreadGroups + Dispose + IDSizes + Suspend + Resume + Exit { exit_code: 0 } + CreateString { string: "Hello, world!" } + Capabilities + ClassPaths + DisposeObjects { requests: [(ObjectID(123), 123)] } + HoldEvents + ReleaseEvents + CapabilitiesNew + RedefineClasses { classes: [(ReferenceTypeID(123), [1, 2, 3, 123])] } + SetDefaultStratum { stratum_id: "NotJava" } + AllClassesWithGeneric + InstanceCounts { ref_types: [ReferenceTypeID(123)] } + } + reference_type::{ + Signature { ref_type: ReferenceTypeID(123) } + ClassLoader { ref_type: ReferenceTypeID(123) } + Modifiers { ref_type: ReferenceTypeID(123) } + Fields { ref_type: ReferenceTypeID(123) } + Methods { ref_type: ReferenceTypeID(123) } + GetValues { ref_type: ReferenceTypeID(123), fields: [FieldID(123), FieldID(123)] } + GetValues { ref_type: ReferenceTypeID(123), fields: [FieldID(123), FieldID(123)] } + SourceFile { ref_type: ReferenceTypeID(123) } + NestedTypes { ref_type: ReferenceTypeID(123) } + Status { ref_type: ReferenceTypeID(123) } + Interfaces { ref_type: ReferenceTypeID(123) } + ClassObject { ref_type: ReferenceTypeID(123) } + SourceDebugExtension { ref_type: ReferenceTypeID(123) } + SignatureWithGeneric { ref_type: ReferenceTypeID(123) } + FieldsWithGeneric { ref_type: ReferenceTypeID(123) } + MethodsWithGeneric { ref_type: ReferenceTypeID(123) } + Instances { ref_type: ReferenceTypeID(123), max_instances: 10 } + ClassFileVersion { ref_type: ReferenceTypeID(123) } + ConstantPool { ref_type: ReferenceTypeID(123) } + } + class_type::{ + Superclass { class_id: ClassID(123) } + SetValues { class_id: ClassID(123), values: [(FieldID(123), UntaggedValue(Int(123))), (FieldID(123), UntaggedValue(Object(ObjectID(123))))] } + InvokeMethod { class_id: ClassID(123), thread_id: ThreadID(123), method_id: MethodID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(0x0) } + NewInstance { class_id: ClassID(123), thread_id: ThreadID(123), method_id: MethodID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(NONE | SINGLE_THREADED) } + } + array_type::{ + NewInstance { array_type_id: ArrayTypeID(123), length: 10 } + } + interface_type::{ + InvokeMethod { interface_id: InterfaceID(123), thread_id: ThreadID(123), method_id: MethodID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(0x0) } + } + method::{ + LineTable { reference_type_id: ReferenceTypeID(123), method_id: MethodID(123) } + VariableTable { reference_type_id: ReferenceTypeID(123), method_id: MethodID(123) } + Bytecodes { reference_type_id: ReferenceTypeID(123), method_id: MethodID(123) } + IsObsolete { reference_type_id: ReferenceTypeID(123), method_id: MethodID(123) } + VariableTableWithGeneric { reference_type_id: ReferenceTypeID(123), method_id: MethodID(123) } + } + object_reference::{ + ReferenceType { object: ObjectID(123) } + GetValues { object: ObjectID(123), fields: [FieldID(123), FieldID(123)] } + SetValues { object: ObjectID(123), fields: [(FieldID(123), UntaggedValue(Int(123))), (FieldID(123), UntaggedValue(Object(ObjectID(123))))] } + MonitorInfo { object: ObjectID(123) } + InvokeMethod { object: ObjectID(123), thread: ThreadID(123), class: ClassID(123), method: FieldID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(0x0) } + DisableCollection { object: ObjectID(123) } + EnableCollection { object: ObjectID(123) } + IsCollected { object: ObjectID(123) } + ReferringObjects { object: ObjectID(123), max_referrers: 10 } + } + string_reference::{ + Value { string_object: ObjectID(123) } + } + thread_reference::{ + Name { thread: ThreadID(123) } + Suspend { thread: ThreadID(123) } + Resume { thread: ThreadID(123) } + Status { thread: ThreadID(123) } + ThreadGroup { thread: ThreadID(123) } + Frames { thread: ThreadID(123), start_frame: 0, limit: AllRemaining } + Frames { thread: ThreadID(123), start_frame: 2, limit: Limit(10) } + FrameCount { thread: ThreadID(123) } + OwnedMonitors { thread: ThreadID(123) } + CurrentContendedMonitor { thread: ThreadID(123) } + Stop { thread: ThreadID(123), throwable: Object(ObjectID(123)) } + Interrupt { thread: ThreadID(123) } + SuspendCount { thread: ThreadID(123) } + OwnedMonitorsStackDepthInfo { thread: ThreadID(123) } + ForceEarlyReturn { thread: ThreadID(123), value: Boolean(false) } + } + thread_group_reference::{ + Name { group: ThreadGroupID(123) } + Parent { group: ThreadGroupID(123) } + Children { group: ThreadGroupID(123) } + } + array_reference::{ + Length { array_id: ArrayID(123) } + GetValues { array_id: ArrayID(123), first_index: 0, length: 10 } + SetValues { array_id: ArrayID(123), first_index: 0, values: [UntaggedValue(Float(123.0)), UntaggedValue(Float(42.0))] } + } + class_loader_reference::{ + VisibleClasses { class_loader_id: ClassLoaderID(123) } + } + event_request::{ + Set { event_kind: SingleStep, suspend_policy: All, modifiers: [Count(10), ThreadOnly(ThreadID(123))] } + Clear { event_kind: Breakpoint, request_id: RequestID(123) } + ClearAllBreakpoints + } + stack_frame::{ + GetValues { thread_id: ThreadID(123), frame_id: FrameID(123), slots: [(1, Object), (2, Int)] } + GetValues { thread_id: ThreadID(123), frame_id: FrameID(123), slots: [(1, Object), (2, Int)] } + SetValues { thread_id: ThreadID(123), frame_id: FrameID(123), slots: [(1, Int(123)), (2, Object(ObjectID(123)))] } + ThisObject { thread_id: ThreadID(123), frame_id: FrameID(123) } + PopFrames { thread_id: ThreadID(123), frame_id: FrameID(123) } + } + class_object_reference::{ + ReflectedType { class_object_id: ClassObjectID(123) } + } + event::{ + Composite { suspend_policy: EventThread, events: [VmStart(Some(RequestID(123)), ThreadID(123))] } + } + "###) +} diff --git a/tests/event.rs b/tests/event.rs index 3c9b2dc..811ff30 100644 --- a/tests/event.rs +++ b/tests/event.rs @@ -1,9 +1,14 @@ use jdwp::{ commands::{ - event::Event, event_request, reference_type::Fields, virtual_machine::ClassBySignature, + event::Event, + event_request, + reference_type::{Fields, Methods}, + thread_reference, + virtual_machine::{AllThreads, ClassBySignature}, }, enums::{EventKind, SuspendPolicy}, - types::{FieldOnly, Modifier, Value}, + event_modifier::Modifier, + types::Value, }; mod common; @@ -14,7 +19,17 @@ use common::Result; fn field_modification() -> Result { let mut client = common::launch_and_attach("basic")?; - let type_id = client.send(ClassBySignature::new("LBasic;"))?.type_id; + let (type_id, _) = *client.send(ClassBySignature::new("LBasic;"))?; + + let main_thread = client + .send(AllThreads)? + .into_iter() + .map(|id| Ok((id, client.send(thread_reference::Name::new(id))?))) + .collect::>>()? + .into_iter() + .find(|(_, name)| name == "main") + .unwrap() + .0; let ticks = &client .send(Fields::new(*type_id))? @@ -22,10 +37,13 @@ fn field_modification() -> Result { .find(|f| f.name == "ticks") .unwrap(); - let field_only = Modifier::FieldOnly(FieldOnly { - declaring: *type_id, - field_id: ticks.field_id, - }); + let tick = &client + .send(Methods::new(*type_id))? + .into_iter() + .find(|m| m.name == "tick") + .unwrap(); + + let field_only = Modifier::FieldOnly(*type_id, ticks.field_id); let request_id = client.send(event_request::Set::new( EventKind::FieldModification, @@ -34,20 +52,25 @@ fn field_modification() -> Result { ))?; match &client.host_events().recv()?.events[..] { - [Event::FieldModification(field_modification)] => { - assert_eq!(field_modification.request_id, request_id); + [Event::FieldModification(req_id, tid, loc, (rid, fid), oid, v)] => { + assert_eq!(*req_id, request_id); + + // should be modified in main thread + assert_eq!(*tid, main_thread); + + // is our field + assert_eq!((*rid, *fid), (type_id, ticks.field_id)); - // not sure if the main thread is always 1 - // assert_eq!(field_modification.thread, unsafe { ThreadID::new(1) }); + // modified from our class + assert_eq!(loc.reference_id, type_id); + // from the tick method + assert_eq!(loc.method_id, tick.method_id); - // should be modified from own class - assert_eq!(field_modification.ref_type_id, type_id); - assert_eq!(field_modification.field_id, ticks.field_id); // field is non-static - assert!(field_modification.object.is_some()); + assert!(oid.is_some()); // check if it was a long and if it did tick - assert!(matches!(field_modification.value, Value::Long(x) if x >= 1)); + assert!(matches!(*v, Value::Long(x) if x >= 1)); } e => panic!("Unexpected event set received: {:#?}", e), } diff --git a/tests/reference_type.rs b/tests/reference_type.rs index 9e74c52..b1740a7 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -37,8 +37,8 @@ where signatures .iter() .map(|item| { - let id = client.send(ClassBySignature::new(*item))?.type_id; - Ok(client.send(new(*id))?) + let (type_id, _) = *client.send(ClassBySignature::new(*item))?; + Ok(client.send(new(*type_id))?) }) .collect() } @@ -140,9 +140,9 @@ fn modifiers() -> Result { fn fields() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - let mut fields = client.send(Fields::new(*id))?; + let mut fields = client.send(Fields::new(*type_id))?; fields.sort_by_key(|f| f.name.clone()); assert_snapshot!(fields, @r###" @@ -197,9 +197,9 @@ fn fields() -> Result { fn methods() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - let mut methods = client.send(Methods::new(*id))?; + let mut methods = client.send(Methods::new(*type_id))?; methods.sort_by_key(|f| f.name.clone()); assert_snapshot!(methods, @r###" @@ -262,9 +262,9 @@ fn methods() -> Result { fn get_values() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - let mut fields = client.send(Fields::new(*id))?; + let mut fields = client.send(Fields::new(*type_id))?; fields.sort_by_key(|f| f.name.clone()); let fields = fields @@ -276,7 +276,7 @@ fn get_values() -> Result { }) .collect::>(); - let values = client.send(GetValues::new(*id, fields))?; + let values = client.send(GetValues::new(*type_id, fields))?; assert_snapshot!(values, @r###" [ @@ -313,8 +313,8 @@ fn source_file() -> Result { ] "###); - let id = client.send(ClassBySignature::new(ARRAY_CLS))?.type_id; - let array_source_file = client.send(SourceFile::new(*id)); + let (type_id, _) = *client.send(ClassBySignature::new(ARRAY_CLS))?; + let array_source_file = client.send(SourceFile::new(*type_id)); assert_snapshot!(array_source_file, @r###" Err( @@ -331,9 +331,9 @@ fn source_file() -> Result { fn nested_types() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - let mut nested_types = client.send(NestedTypes::new(*id))?; + let mut nested_types = client.send(NestedTypes::new(*type_id))?; nested_types.sort_by_key(|t| t.tag() as u8); let nested_types = get_signatures(&mut client, nested_types)?; @@ -345,10 +345,9 @@ fn nested_types() -> Result { ] "###); - let id = client - .send(ClassBySignature::new("Ljava/util/HashMap;"))? - .type_id; - let mut nested_types = client.send(NestedTypes::new(*id))?; + let (type_id, _) = *client.send(ClassBySignature::new("Ljava/util/HashMap;"))?; + + let mut nested_types = client.send(NestedTypes::new(*type_id))?; nested_types.sort_by_key(|t| t.tag() as u8); let nested_types = get_signatures(&mut client, nested_types)?; @@ -405,8 +404,8 @@ fn status() -> Result { fn interfaces() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; - let interfaces = client.send(Interfaces::new(*id))?; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; + let interfaces = client.send(Interfaces::new(*type_id))?; let interfaces = get_signatures(&mut client, interfaces)?; assert_snapshot!(interfaces, @r###" @@ -415,10 +414,9 @@ fn interfaces() -> Result { ] "###); - let id = client - .send(ClassBySignature::new("Ljava/util/ArrayList;"))? - .type_id; - let interfaces = client.send(Interfaces::new(*id))?; + let (type_id, _) = *client.send(ClassBySignature::new("Ljava/util/ArrayList;"))?; + + let interfaces = client.send(Interfaces::new(*type_id))?; let interfaces = get_signatures(&mut client, interfaces)?; assert_snapshot!(interfaces, @r###" @@ -437,11 +435,11 @@ fn interfaces() -> Result { fn class_object() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; - let class_object = client.send(ClassObject::new(*id))?; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; + let class_object = client.send(ClassObject::new(*type_id))?; let ref_id = client.send(ReflectedType::new(class_object))?; - assert_eq!(id, ref_id); + assert_eq!(type_id, ref_id); Ok(()) } @@ -450,8 +448,8 @@ fn class_object() -> Result { fn instances() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; - let instances = client.send(Instances::new(*id, 10))?; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; + let instances = client.send(Instances::new(*type_id, 10))?; // the running instance and the one in the static field assert_snapshot!(instances, @r###" @@ -472,8 +470,8 @@ fn instances() -> Result { fn class_file_version() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; - let version = client.send(ClassFileVersion::new(*id))?; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; + let version = client.send(ClassFileVersion::new(*type_id))?; let expected = match common::java_version() { 8 => (52, 0), @@ -495,8 +493,8 @@ fn class_file_version() -> Result { fn constant_pool() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new(OUR_CLS))?.type_id; - let constant_pool = client.send(ConstantPool::new(*id))?; + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; + let constant_pool = client.send(ConstantPool::new(*type_id))?; let mut reader = Cursor::new(constant_pool.bytes); // pfew lol why did I bother so much diff --git a/tests/virtual_machine.rs b/tests/virtual_machine.rs index 1a953fa..bf39693 100644 --- a/tests/virtual_machine.rs +++ b/tests/virtual_machine.rs @@ -42,38 +42,38 @@ fn class_by_signature() -> Result { assert_snapshot!(classes, @r###" [ - UnnamedClass { - type_id: Class( + ( + Class( [opaque_id], ), - status: ClassStatus( + ClassStatus( VERIFIED | PREPARED | INITIALIZED, ), - }, - UnnamedClass { - type_id: Interface( + ), + ( + Interface( [opaque_id], ), - status: ClassStatus( + ClassStatus( VERIFIED | PREPARED | INITIALIZED, ), - }, - UnnamedClass { - type_id: Array( + ), + ( + Array( [opaque_id], ), - status: ClassStatus( + ClassStatus( 0x0, ), - }, - UnnamedClass { - type_id: Array( + ), + ( + Array( [opaque_id], ), - status: ClassStatus( + ClassStatus( 0x0, ), - }, + ), ] "###); @@ -338,12 +338,10 @@ fn set_default_stratum() -> Result { fn instance_counts() -> Result { let mut client = common::launch_and_attach("basic")?; - let id = client.send(ClassBySignature::new("LBasic;"))?.type_id; - let id2 = client - .send(ClassBySignature::new("LBasic$NestedClass;"))? - .type_id; + let (type_id, _) = *client.send(ClassBySignature::new("LBasic;"))?; + let (type_id2, _) = *client.send(ClassBySignature::new("LBasic$NestedClass;"))?; - let counts = client.send(InstanceCounts::new(vec![*id, *id2]))?; + let counts = client.send(InstanceCounts::new(vec![*type_id, *type_id2]))?; assert_eq!(counts, [2, 0]); From ec3ecffd50d9b7a064b0854368efe4ee8f303cfb Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Wed, 26 Jul 2023 02:44:39 +0300 Subject: [PATCH 8/9] Refactor the entire spec into a single file, add a bunch of WIP highlevel APIs --- Cargo.toml | 2 +- jdwp-macros/Cargo.toml | 2 +- jdwp-macros/src/lib.rs | 169 +- src/client.rs | 192 +- src/codec.rs | 89 +- src/commands/array_reference.rs | 49 - src/commands/array_type.rs | 36 - src/commands/class_loader_reference.rs | 27 - src/commands/class_object_reference.rs | 11 - src/commands/class_type.rs | 140 -- src/commands/event.rs | 377 ---- src/commands/event_request.rs | 76 - src/commands/interface_type.rs | 67 - src/commands/method.rs | 171 -- src/commands/mod.rs | 32 - src/commands/object_reference.rs | 195 -- src/commands/reference_type.rs | 390 ---- src/commands/stack_frame.rs | 83 - src/commands/string_reference.rs | 11 - src/commands/thread_group_reference.rs | 44 - src/commands/thread_reference.rs | 261 --- src/commands/virtual_machine.rs | 509 ----- src/enums.rs | 301 --- src/event_modifier.rs | 192 -- src/functional.rs | 4 +- src/highlevel/array_type.rs | 18 + src/highlevel/class_type.rs | 17 + src/highlevel/event.rs | 79 + src/highlevel/field.rs | 170 ++ src/highlevel/generic.rs | 166 ++ src/highlevel/interface_type.rs | 7 + src/highlevel/method.rs | 63 + src/highlevel/mod.rs | 62 + src/highlevel/object_reference.rs | 156 ++ src/highlevel/reference_type.rs | 253 +++ src/highlevel/thread.rs | 44 + src/highlevel/vm.rs | 225 ++ src/jvm.rs | 92 +- src/lib.rs | 52 +- src/spec/commands.rs | 2828 ++++++++++++++++++++++++ src/spec/constants.rs | 513 +++++ src/spec/mod.rs | 11 + src/spec/protocol.rs | 68 + src/{ => spec}/types.rs | 466 ++-- tests/common.rs | 57 +- tests/derive_coverage.rs | 38 +- tests/event.rs | 42 +- tests/reference_type.rs | 294 +-- tests/thread_group_reference.rs | 37 +- tests/thread_reference.rs | 46 +- tests/virtual_machine.rs | 164 +- 51 files changed, 5613 insertions(+), 3785 deletions(-) delete mode 100644 src/commands/array_reference.rs delete mode 100644 src/commands/array_type.rs delete mode 100644 src/commands/class_loader_reference.rs delete mode 100644 src/commands/class_object_reference.rs delete mode 100644 src/commands/class_type.rs delete mode 100644 src/commands/event.rs delete mode 100644 src/commands/event_request.rs delete mode 100644 src/commands/interface_type.rs delete mode 100644 src/commands/method.rs delete mode 100644 src/commands/mod.rs delete mode 100644 src/commands/object_reference.rs delete mode 100644 src/commands/reference_type.rs delete mode 100644 src/commands/stack_frame.rs delete mode 100644 src/commands/string_reference.rs delete mode 100644 src/commands/thread_group_reference.rs delete mode 100644 src/commands/thread_reference.rs delete mode 100644 src/commands/virtual_machine.rs delete mode 100644 src/enums.rs delete mode 100644 src/event_modifier.rs create mode 100644 src/highlevel/array_type.rs create mode 100644 src/highlevel/class_type.rs create mode 100644 src/highlevel/event.rs create mode 100644 src/highlevel/field.rs create mode 100644 src/highlevel/generic.rs create mode 100644 src/highlevel/interface_type.rs create mode 100644 src/highlevel/method.rs create mode 100644 src/highlevel/mod.rs create mode 100644 src/highlevel/object_reference.rs create mode 100644 src/highlevel/reference_type.rs create mode 100644 src/highlevel/thread.rs create mode 100644 src/highlevel/vm.rs create mode 100644 src/spec/commands.rs create mode 100644 src/spec/constants.rs create mode 100644 src/spec/mod.rs create mode 100644 src/spec/protocol.rs rename src/{ => spec}/types.rs (76%) diff --git a/Cargo.toml b/Cargo.toml index aa104ef..c05de13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,11 +12,11 @@ rust-version = '1.66.1' [dependencies] jdwp-macros = { path = 'jdwp-macros' } paste = '1.0' +thiserror = '1.0' log = '0.4' byteorder = '1.4' bitflags = '2.3' -thiserror = '1.0' cesu8 = '1.1' [dev-dependencies] diff --git a/jdwp-macros/Cargo.toml b/jdwp-macros/Cargo.toml index f87bef6..b5e223a 100644 --- a/jdwp-macros/Cargo.toml +++ b/jdwp-macros/Cargo.toml @@ -7,6 +7,6 @@ edition = '2018' proc-macro = true [dependencies] -syn = { version = '1.0', features = ['full'] } +syn = { version = '2.0', features = ['full', 'extra-traits'] } quote = '1.0' proc-macro2 = '1.0' diff --git a/jdwp-macros/src/lib.rs b/jdwp-macros/src/lib.rs index 7976d75..3891dd0 100644 --- a/jdwp-macros/src/lib.rs +++ b/jdwp-macros/src/lib.rs @@ -1,3 +1,5 @@ +use std::collections::HashSet; + use proc_macro::TokenStream; use proc_macro2::Ident; @@ -7,7 +9,7 @@ use syn::{ punctuated::Punctuated, spanned::Spanned, token::Comma, - Data, Error, Fields, GenericParam, Index, LitInt, Token, Type, + Attribute, Data, Error, Fields, GenericParam, Index, LitInt, Meta, Token, Type, TypePath, }; fn get_generic_names(generic_params: &Punctuated) -> proc_macro2::TokenStream { @@ -21,33 +23,77 @@ fn get_generic_names(generic_params: &Punctuated) -> proc_m quote!(#(#generics,)*) } +fn is_attr_ident(attr: &Attribute, name: &str) -> bool { + match &attr.meta { + Meta::List(list) => list.path.is_ident(name), + Meta::NameValue(name_value) => name_value.path.is_ident(name), + _ => false, + } +} + +fn is_generic_param( + generic_params: &Punctuated, + ty: &Type, +) -> Option { + if let Type::Path(path) = ty { + if let Some(prefix) = path.path.segments.first() { + return generic_params + .iter() + .any(|param| match param { + GenericParam::Type(type_param) => type_param.ident == prefix.ident, + _ => false, + }) + .then(|| path.clone()); + } + } + None +} + #[proc_macro_derive(JdwpReadable, attributes(skip))] pub fn jdwp_readable(item: TokenStream) -> TokenStream { let derive_input = syn::parse_macro_input!(item as syn::DeriveInput); match &derive_input.data { Data::Struct(struct_data) => { - let ident = derive_input.ident; let generic_params = derive_input.generics.params; - let generic_names = get_generic_names(&generic_params); - let generics_where = derive_input.generics.where_clause; + let mut field_types = HashSet::new(); let read = match &struct_data.fields { Fields::Unit => quote!(Ok(Self)), Fields::Named(named) => { let fields = named.named.iter().map(|f| { let name = f.ident.as_ref().unwrap(); // we are in Named branch so this is not None + if let Some(path) = is_generic_param(&generic_params, &f.ty) { + field_types.insert(path); + } quote!(#name: ::jdwp::codec::JdwpReadable::read(read)?) }); quote!(Ok(Self { #(#fields),* })) } Fields::Unnamed(unnamed) => { - let fields = (0..unnamed.unnamed.len()) - .map(|_| quote!(::jdwp::codec::JdwpReadable::read(read)?)); + let fields = unnamed.unnamed.iter().map(|f| { + if let Some(path) = is_generic_param(&generic_params, &f.ty) { + field_types.insert(path); + } + quote!(::jdwp::codec::JdwpReadable::read(read)?) + }); quote!(Ok(Self(#(#fields),*))) } }; + + let mut generic_predicates = quote!(); + if let Some(existing) = derive_input.generics.where_clause { + for predicate in existing.predicates { + generic_predicates.extend(quote!(#predicate,)); + } + } + for field_type in field_types { + generic_predicates.extend(quote!(#field_type: ::jdwp::codec::JdwpReadable,)); + } + + let ident = derive_input.ident; + let generic_names = get_generic_names(&generic_params); let tokens = quote! { - impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_names> #generics_where { + impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_names> where #generic_predicates { fn read(read: &mut ::jdwp::codec::JdwpReader) -> ::std::io::Result { #read } @@ -59,44 +105,64 @@ pub fn jdwp_readable(item: TokenStream) -> TokenStream { let Some(repr) = derive_input .attrs .iter() - .find(|attr| attr.path.is_ident("repr")) else { + .find(|attr| is_attr_ident(attr, "repr")) else { return Error::new(enum_data.enum_token.span, "No explicit repr") .to_compile_error() .into(); }; - let repr = repr.parse_args::().expect("TODO"); + let repr = repr.parse_args::().expect("Bad repr"); // todo better error let mut match_arms = Vec::with_capacity(enum_data.variants.len()); + let mut field_types = HashSet::new(); + + let generic_params = derive_input.generics.params; for v in &enum_data.variants { let Some((_, ref d)) = v.discriminant else { - return Error::new( - v.span(), - "No explicit discriminant", - ) - .to_compile_error() - .into() + return Error::new(v.span(), "No explicit discriminant") + .to_compile_error() + .into() }; let name = &v.ident; + let constructor = match &v.fields { Fields::Named(named) => { - let fields = named.named.iter().map(|f| f.ident.as_ref().unwrap()); + let fields = named.named.iter().map(|f| { + if let Some(path) = is_generic_param(&generic_params, &f.ty) { + field_types.insert(path); + } + f.ident.as_ref().unwrap() + }); quote!( { #(#fields: ::jdwp::codec::JdwpReadable::read(read)?,)* } ) } Fields::Unnamed(unnamed) => { - let fields = unnamed - .unnamed - .iter() - .map(|_| quote!(::jdwp::codec::JdwpReadable::read(read)?)); + let fields = unnamed.unnamed.iter().map(|f| { + if let Some(path) = is_generic_param(&generic_params, &f.ty) { + field_types.insert(path); + } + quote!(::jdwp::codec::JdwpReadable::read(read)?) + }); quote!( ( #(#fields),* ) ) } Fields::Unit => quote!(), }; match_arms.push(quote!(x if x == (#d) => Self::#name #constructor)); } - let ident = &derive_input.ident; + + let mut generic_predicates = quote!(); + if let Some(existing) = derive_input.generics.where_clause { + for predicate in existing.predicates { + generic_predicates.extend(quote!(#predicate,)); + } + } + for field_type in field_types { + generic_predicates.extend(quote!(#field_type: ::jdwp::codec::JdwpReadable,)); + } + + let ident = derive_input.ident; + let generic_names = get_generic_names(&generic_params); let tokens = quote! { - impl ::jdwp::codec::JdwpReadable for #ident { + impl<#generic_params> ::jdwp::codec::JdwpReadable for #ident<#generic_names> where #generic_predicates { fn read(read: &mut ::jdwp::codec::JdwpReader) -> ::std::io::Result { let res = match #repr::read(read)? { #(#match_arms,)* @@ -158,7 +224,7 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream { let Some(repr) = derive_input .attrs .iter() - .find(|attr| attr.path.is_ident("repr")) else { + .find(|attr| is_attr_ident(attr, "repr")) else { return Error::new(enum_data.enum_token.span, "No explicit repr") .to_compile_error() .into(); @@ -205,8 +271,11 @@ pub fn jdwp_writable(item: TokenStream) -> TokenStream { }); } let ident = derive_input.ident; + let generic_params = derive_input.generics.params; + let generic_names = get_generic_names(&generic_params); + let generics_where = derive_input.generics.where_clause; let tokens = quote! { - impl ::jdwp::codec::JdwpWritable for #ident { + impl<#generic_params> ::jdwp::codec::JdwpWritable for #ident<#generic_names> #generics_where { fn write(&self, write: &mut ::jdwp::codec::JdwpWriter) -> ::std::io::Result<()> { match self { #(#match_arms)* @@ -234,7 +303,7 @@ struct CommandAttr { impl Parse for CommandAttr { fn parse(input: ParseStream) -> syn::Result { let reply_type = input.parse()?; - let _ = input.parse::()?; + _ = input.parse::()?; Ok(CommandAttr { reply_type, command: input.parse()?, @@ -259,7 +328,7 @@ impl ShortCommandAttr { impl Parse for ShortCommandAttr { fn parse(input: ParseStream) -> syn::Result { let command_set = input.parse()?; - let _ = input.parse::()?; + _ = input.parse::()?; Ok(ShortCommandAttr { command_set, command_id: input.parse()?, @@ -291,9 +360,7 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream { let generic_names = get_generic_names(generic_params); let generics_where = &item.generics.where_clause; - let new = if item.fields.is_empty() { - quote!() - } else { + let new = if !item.fields.is_empty() { let mut docs = Vec::with_capacity(item.fields.len()); let mut typed_idents = Vec::with_capacity(item.fields.len()); let mut idents = Vec::with_capacity(item.fields.len()); @@ -302,32 +369,28 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream { Some(ref ident) => { let ty = &f.ty; - // this is very cringe but also very simple - let stype = quote!(#ty).to_string(); - let string_magic = stype == "String"; - let phantom = stype.starts_with("PhantomData "); + // this is pretty cringe but eh + let phantom = quote!(#ty).to_string().starts_with("PhantomData "); if !phantom { - typed_idents.push(if string_magic { - quote!(#ident: impl Into) - } else { - quote!(#ident: #ty) - }); + typed_idents.push(quote!(#ident: #ty)); } - docs.push(f.attrs.iter().find(|a| a.path.is_ident("doc")).map(|a| { - let tokens = &a.tokens; - quote! { - #[doc = stringify!(#ident)] - #[doc = " - "] - #[doc #tokens] - #[doc = "\n"] - } - })); + docs.push( + f.attrs + .iter() + .find(|attr| is_attr_ident(attr, "doc")) + .map(|attr| { + quote! { + #[doc = stringify!(#ident)] + #[doc = " - "] + #attr + #[doc = "\n"] + } + }), + ); - idents.push(if string_magic { - quote!(#ident: #ident.into()) - } else if phantom { + idents.push(if phantom { quote!(#ident: ::std::marker::PhantomData) } else { quote!(#ident) @@ -350,14 +413,16 @@ pub fn jdwp_command(attr: TokenStream, item: TokenStream) -> TokenStream { } } } + } else { + quote!() }; let tokens = quote! { #item #new - impl<#generic_params> ::jdwp::commands::Command for #ident<#generic_names> #generics_where { - const ID: ::jdwp::CommandId = ::jdwp::CommandId::new(#command_set, #command_id); + impl<#generic_params> ::jdwp::spec::Command for #ident<#generic_names> #generics_where { + const ID: ::jdwp::spec::CommandId = ::jdwp::spec::CommandId::new(#command_set, #command_id); type Output = #reply_type; } }; diff --git a/src/client.rs b/src/client.rs index 08d39e3..31df151 100644 --- a/src/client.rs +++ b/src/client.rs @@ -14,30 +14,20 @@ use thiserror::Error; use crate::{ codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, - commands::{ + spec::{ event::Composite, - virtual_machine::{Dispose, IDSizeInfo}, - Command, + virtual_machine::{Dispose, IDSizeInfo, IDSizes}, + Command, ErrorCode, PacketHeader, PacketMeta, }, xorshift::XorShift32, - ErrorCode, PacketHeader, PacketMeta, }; -type WaitingMap = Arc, ClientError>>>>>; - -#[derive(Debug)] -pub struct JdwpClient { - writer: JdwpWriter, - host_events_rx: Receiver, - waiting: WaitingMap, - next_id: XorShift32, - reader_handle: Option>, -} - #[derive(Debug, Error)] pub enum ClientError { #[error("Failed handshake")] FailedHandshake, + #[error("Failed initial IDSizes call")] + FailedInitialIdSizesCall(Option), #[error("{0}")] HostError(ErrorCode), #[error("Too much data received from the host ({actual}/{expected} bytes)")] @@ -50,10 +40,29 @@ pub enum ClientError { const HANDSHAKE: &[u8] = b"JDWP-Handshake"; -impl JdwpClient { - pub fn attach(addr: A) -> Result { - let mut stream = TcpStream::connect(addr)?; +#[derive(Debug)] +pub struct JdwpClient { + stream: TcpStream, + id_sizes: IDSizeInfo, + next_id: XorShift32, + shared: Arc>, + reader_handle: Option>, +} + +#[derive(Debug)] +struct SharedData { + waiting: HashMap, ClientError>>>, + event_senders: Vec>, +} +impl Drop for JdwpClient { + fn drop(&mut self) { + _ = self.stream.shutdown(Shutdown::Read); + } +} + +impl JdwpClient { + pub fn attach(mut stream: TcpStream) -> Result { stream.write_all(HANDSHAKE)?; let handshake = &mut [0; HANDSHAKE.len()]; stream.read_exact(handshake)?; @@ -61,40 +70,69 @@ impl JdwpClient { return Err(ClientError::FailedHandshake); } - let waiting = Arc::new(Mutex::new(HashMap::new())); - let (host_events_tx, host_events_rx) = mpsc::channel(); - - // todo: hardcode fetching it here I guess - let id_sizes = IDSizeInfo { - field_id_size: 8, - method_id_size: 8, - object_id_size: 8, - reference_type_id_size: 8, - frame_id_size: 8, + // a stub id_sizes just to have something in the bootstrap writer and reader + // it's fine to have whatever as we are not reading any object-ids anyway + let id_sizes = IDSizeInfo::default(); + + // manually send an IDSizes command + let mut bootstrap_writer = JdwpWriter::new(&mut stream, &id_sizes); + let id_sizes_header = + PacketHeader::new(PacketHeader::JDWP_SIZE, 1, PacketMeta::Command(IDSizes::ID)); + id_sizes_header.write(&mut bootstrap_writer)?; + + // and then manually read the actual sizes from the response + let mut bootstrap_reader = JdwpReader::new(&mut stream, &id_sizes); + let reply_header = PacketHeader::read(&mut bootstrap_reader)?; + if reply_header.id() != 1 || reply_header.length() != PacketHeader::JDWP_SIZE + 20 { + return Err(ClientError::FailedInitialIdSizesCall(None)); + } + let id_sizes = match reply_header.meta() { + PacketMeta::Reply(ErrorCode::None) => IDSizeInfo::read(&mut bootstrap_reader)?, + PacketMeta::Command(_) => return Err(ClientError::FailedInitialIdSizesCall(None)), + PacketMeta::Reply(error_code) => { + return Err(ClientError::FailedInitialIdSizesCall(Some(error_code))) + } }; + log::debug!("Received IDSizes reply: {id_sizes:?}"); + + let shared = Arc::new(Mutex::new(SharedData { + waiting: HashMap::new(), + event_senders: Vec::new(), + })); + let reader_handle = thread::spawn({ - let mut reader = JdwpReader::new(stream.try_clone()?, id_sizes.clone()); - let waiting = waiting.clone(); - move || loop { - if let Err(e) = read_packet(&mut reader, &waiting, &host_events_tx) { - log::error!("Failed to read incoming data: {}", e); - break e; + let id_sizes = id_sizes.clone(); + let stream = stream.try_clone()?; + let shared = shared.clone(); + move || { + let mut reader = JdwpReader::new(stream, &id_sizes); + loop { + if let Err(e) = read_packet(&mut reader, &shared) { + log::error!("Failed to read incoming data: {e}"); + break e; + } } } }); - Ok(JdwpClient { - writer: JdwpWriter::new(stream, id_sizes), - host_events_rx, - waiting, + Ok(Self { + stream, + id_sizes, next_id: XorShift32::new(0xDEAD), + shared, reader_handle: Some(reader_handle), }) } - pub fn host_events(&self) -> &Receiver { - &self.host_events_rx + pub fn connect(addr: impl ToSocketAddrs) -> Result { + Self::attach(TcpStream::connect(addr)?) + } + + pub fn receive_events(&self) -> Receiver { + let (tx, rx) = mpsc::channel(); + self.shared.lock().unwrap().event_senders.push(tx); + rx } pub fn send(&mut self, command: C) -> Result @@ -116,34 +154,34 @@ impl JdwpClient { // see comment below if C::ID != Dispose::ID { - self.waiting.lock().unwrap().insert(id, waiting_tx); + self.shared.lock().unwrap().waiting.insert(id, waiting_tx); } - let mut data = Vec::new(); - command.write(&mut JdwpWriter::new( - &mut data, - self.writer.id_sizes.clone(), - ))?; + // the capacity is a random heuristic, should probably look into adjusting this + // when a bunch of tests are written + let mut data = Vec::with_capacity(256); - let header = PacketHeader { - length: (PacketHeader::JDWP_SIZE + data.len()) as u32, - id, - meta: PacketMeta::Command(C::ID), - }; + // write the command separately to learn the payload length + command.write(&mut JdwpWriter::new(&mut data, &self.id_sizes))?; - header.write(&mut self.writer)?; - self.writer.write_all(&data)?; + let len = PacketHeader::JDWP_SIZE + data.len() as u32; + let header = PacketHeader::new(len, id, PacketMeta::Command(C::ID)); - log::trace!("[{:x}] sent command {}: {:?}", header.id, C::ID, command); + // and then make another writer to use the JdwpWritable derive for the header + // ¯\_(ツ)_/¯ + header.write(&mut JdwpWriter::new(&mut self.stream, &self.id_sizes))?; + self.stream.write_all(&data)?; + + log::trace!("[{:x}] sent command {}: {command:?}", header.id(), C::ID); // special handling for the dispose command because // we don't always get the response header for it if C::ID == Dispose::ID { // stop the reading thread by closing the socket - self.writer.shutdown(Shutdown::Both)?; + self.stream.shutdown(Shutdown::Both)?; // force next calls to send to return ClientError::Disposed, - // otherwise we get either UnexpectedEof or BrokenPipe and maybe + // otherwise we get either UnexpectedEof or BrokenPipe or maybe // something else from closing the socket self.reader_handle = None; @@ -162,12 +200,10 @@ impl JdwpClient { let len = data.len(); let mut cursor = Cursor::new(data); - let result = C::Output::read(&mut JdwpReader::new( - &mut cursor, - self.writer.id_sizes.clone(), - ))?; - log::trace!("[{:x}] data: {:#?}", header.id, result); + let result = C::Output::read(&mut JdwpReader::new(&mut cursor, &self.id_sizes))?; + + log::trace!("[{:x}] data: {:#?}", header.id(), result); if cursor.position() < len as u64 { Err(ClientError::TooMuchDataReceived { @@ -182,26 +218,32 @@ impl JdwpClient { fn read_packet( reader: &mut JdwpReader, - waiting: &WaitingMap, - host_events_tx: &Sender, + shared: &Arc>, ) -> Result<(), ClientError> { let header = PacketHeader::read(reader)?; - let mut data = vec![0; header.length as usize - PacketHeader::JDWP_SIZE]; + let mut data = vec![0; (header.length() - PacketHeader::JDWP_SIZE) as usize]; reader.read_exact(&mut data)?; - let to_send = match header.meta { - // handle the host-sent commands; - // the only one is the Event command + let to_send = match header.meta() { + // handle the host-sent commands + // the only one is the Composite Event command PacketMeta::Command(Composite::ID) => { - let composite = Composite::read(&mut JdwpReader::new( - &mut Cursor::new(data), - reader.id_sizes.clone(), - ))?; + let mut cursor = Cursor::new(data); + let mut reader = JdwpReader::new(&mut cursor, reader.id_sizes); + let composite = Composite::read(&mut reader)?; log::trace!("[host] event: {:#?}", composite); - host_events_tx.send(composite).unwrap(); + let mut shared = shared.lock().unwrap(); + + // ugh it's pretty ugly to avoid one extra clone (and extra clones when + // receivers were dropped) and I haven't even made the retain part + // work it will optimize?. + shared + .event_senders + .retain(|sender| sender.send(composite.clone()).is_ok()); + return Ok(()); } PacketMeta::Command(command_id) => { @@ -212,16 +254,16 @@ fn read_packet( return Ok(()); } PacketMeta::Reply(ErrorCode::None) => { - log::trace!("[{:x}] reply, len {}", header.id, data.len()); + log::trace!("[{:x}] reply, len {}", header.id(), data.len()); Ok(data) } PacketMeta::Reply(error_code) => { - log::trace!("[{:x}] reply, host error: {:?}", header.id, error_code); + log::trace!("[{:x}] reply, host error: {:?}", header.id(), error_code); Err(ClientError::HostError(error_code)) } }; - match waiting.lock().unwrap().remove(&header.id) { + match shared.lock().unwrap().waiting.remove(&header.id()) { Some(waiter) => waiter.send(to_send).unwrap(), // one-shot channel send None => log::warn!( "Received an unexpected packet from the JVM, ignoring: {:?}", diff --git a/src/codec.rs b/src/codec.rs index 52e5c21..826dea2 100644 --- a/src/codec.rs +++ b/src/codec.rs @@ -5,26 +5,24 @@ use std::{ ops::{Deref, DerefMut}, }; -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{ReadBytesExt, WriteBytesExt, BE}; use paste::paste; -pub use jdwp_macros::{JdwpReadable, JdwpWritable}; - -use crate::{commands::virtual_machine::IDSizeInfo, functional::Coll}; +use crate::{functional::Coll, spec::virtual_machine::IDSizeInfo}; #[derive(Debug)] -pub struct JdwpWriter { +pub struct JdwpWriter<'a, W> { write: W, - pub(crate) id_sizes: IDSizeInfo, + pub(crate) id_sizes: &'a IDSizeInfo, } -impl JdwpWriter { - pub(crate) fn new(write: W, id_sizes: IDSizeInfo) -> Self { +impl<'a, W> JdwpWriter<'a, W> { + pub(crate) fn new(write: W, id_sizes: &'a IDSizeInfo) -> Self { Self { write, id_sizes } } } -impl Deref for JdwpWriter { +impl<'a, W> Deref for JdwpWriter<'a, W> { type Target = W; fn deref(&self) -> &Self::Target { @@ -32,25 +30,25 @@ impl Deref for JdwpWriter { } } -impl DerefMut for JdwpWriter { +impl<'a, W> DerefMut for JdwpWriter<'a, W> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.write } } #[derive(Debug)] -pub struct JdwpReader { +pub struct JdwpReader<'a, R> { read: R, - pub(crate) id_sizes: IDSizeInfo, + pub(crate) id_sizes: &'a IDSizeInfo, } -impl JdwpReader { - pub(crate) fn new(read: R, id_sizes: IDSizeInfo) -> Self { +impl<'a, R> JdwpReader<'a, R> { + pub(crate) fn new(read: R, id_sizes: &'a IDSizeInfo) -> Self { Self { read, id_sizes } } } -impl Deref for JdwpReader { +impl<'a, R> Deref for JdwpReader<'a, R> { type Target = R; fn deref(&self) -> &Self::Target { @@ -58,7 +56,7 @@ impl Deref for JdwpReader { } } -impl DerefMut for JdwpReader { +impl<'a, R> DerefMut for JdwpReader<'a, R> { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.read } @@ -68,10 +66,14 @@ pub trait JdwpReadable: Sized { fn read(read: &mut JdwpReader) -> io::Result; } +pub use jdwp_macros::JdwpReadable; + pub trait JdwpWritable { fn write(&self, write: &mut JdwpWriter) -> io::Result<()>; } +pub use jdwp_macros::JdwpWritable; + impl JdwpReadable for () { #[inline] fn read(_: &mut JdwpReader) -> io::Result { @@ -116,21 +118,9 @@ impl JdwpWritable for bool { // read/write + i8/u8 methods do not have the endianness generic, eh macro_rules! big_endian_hack { - ($f:ident, $prefix:ident, i8, $arg:tt) => { - paste! { - $f.[<$prefix i8>] $arg - } - }; - ($f:ident, $prefix:ident, u8, $arg:tt) => { - paste! { - $f.[<$prefix u8>] $arg - } - }; - ($f:ident, $prefix:ident, $t:ident, $arg:tt) => { - paste! { - $f.[<$prefix $t>]:: $arg - } - }; + ($f:ident, $prefix:ident, i8, $arg:tt) => { paste!($f.[<$prefix i8>] $arg) }; + ($f:ident, $prefix:ident, u8, $arg:tt) => { paste!($f.[<$prefix u8>] $arg) }; + ($f:ident, $prefix:ident, $t:ident, $arg:tt) => { paste!($f.[<$prefix $t>]:: $arg) }; } macro_rules! int_io { @@ -156,21 +146,30 @@ macro_rules! int_io { int_io![i8, u8, i16, u16, i32, u32, i64, u64, f32, f64]; impl JdwpReadable for String { - #[inline] fn read(read: &mut JdwpReader) -> io::Result { let mut bytes = vec![0; u32::read(read)? as usize]; read.read_exact(&mut bytes)?; + // surprisingly, jdwp is just utf-8, not java-cesu8 String::from_utf8(bytes).map_err(|_| Error::from(ErrorKind::InvalidData)) } } -impl JdwpWritable for String { +impl JdwpWritable for &str { fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { (self.len() as u32).write(write)?; + // write.write_all(cesu8::to_java_cesu8(self).as_ref()) write.write_all(self.as_bytes()) } } +// some command responses have "or an empty string if there is none of X" +// semantic +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + String::read(read).map(|s| Some(s).filter(|s| !s.is_empty())) + } +} + impl JdwpReadable for C where C: Coll + TryFrom>, @@ -242,3 +241,27 @@ where self.1.write(write) } } + +impl JdwpReadable for (A, B, C) +where + A: JdwpReadable, + B: JdwpReadable, + C: JdwpReadable, +{ + fn read(read: &mut JdwpReader) -> io::Result { + Ok((A::read(read)?, B::read(read)?, C::read(read)?)) + } +} + +impl JdwpWritable for (A, B, C) +where + A: JdwpWritable, + B: JdwpWritable, + C: JdwpWritable, +{ + fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { + self.0.write(write)?; + self.1.write(write)?; + self.2.write(write) + } +} diff --git a/src/commands/array_reference.rs b/src/commands/array_reference.rs deleted file mode 100644 index 5a25e67..0000000 --- a/src/commands/array_reference.rs +++ /dev/null @@ -1,49 +0,0 @@ -use crate::{ - codec::JdwpWritable, - types::{ArrayID, ArrayRegion, UntaggedValue}, -}; - -use super::jdwp_command; - -/// Returns the number of components in a given array. -#[jdwp_command(i32, 13, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Length { - /// The array object ID - array_id: ArrayID, -} - -/// Returns a range of array components. -/// -/// The specified range must be within the bounds of the array. -#[jdwp_command(ArrayRegion, 13, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct GetValues { - /// The array object ID - array_id: ArrayID, - /// The first index to retrieve - first_index: i32, - /// The number of components to retrieve - length: i32, -} - -/// Sets a range of array components. -/// -/// The specified range must be within the bounds of the array. -/// -/// For primitive values, each value's type must match the array component type -/// exactly. -/// -/// For object values, there must be a widening reference conversion from the -/// value's type to the array component type and the array component type must -/// be loaded. -#[jdwp_command((), 13, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues<'a> { - /// The array object ID - array_id: ArrayID, - /// The first index to set - first_index: i32, - /// Values to set - values: &'a [UntaggedValue], -} diff --git a/src/commands/array_type.rs b/src/commands/array_type.rs deleted file mode 100644 index ecd1c46..0000000 --- a/src/commands/array_type.rs +++ /dev/null @@ -1,36 +0,0 @@ -use jdwp_macros::JdwpReadable; -use std::ops::Deref; - -use super::jdwp_command; - -use crate::{ - codec::JdwpWritable, - enums::Tag, - types::{ArrayID, ArrayTypeID}, -}; - -/// Creates a new array object of this type with a given length. -#[jdwp_command(4, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct NewInstance { - /// The array type of the new instance - array_type_id: ArrayTypeID, - /// The length of the array - length: i32, -} - -#[derive(Debug, JdwpReadable)] -pub struct NewInstanceReply { - // should always be Tag::Array - _tag: Tag, - /// The newly created array object - pub new_array: ArrayID, -} - -impl Deref for NewInstanceReply { - type Target = ArrayID; - - fn deref(&self) -> &Self::Target { - &self.new_array - } -} diff --git a/src/commands/class_loader_reference.rs b/src/commands/class_loader_reference.rs deleted file mode 100644 index d1a6825..0000000 --- a/src/commands/class_loader_reference.rs +++ /dev/null @@ -1,27 +0,0 @@ -use jdwp_macros::{jdwp_command, JdwpWritable}; - -use crate::types::{ClassLoaderID, TaggedReferenceTypeID}; - -/// Returns a list of all classes which this class loader has been requested -/// to load. -/// -/// This class loader is considered to be an initiating class loader for -/// each class in the returned list. The list contains each reference -/// type defined by this loader and any types for which loading was -/// delegated by this class loader to another class loader. -/// -/// The visible class list has useful properties with respect to the type -/// namespace. -/// -/// A particular type name will occur at most once in the list. -/// -/// Each field or variable declared with that type name in a class defined -/// by this class loader must be resolved to that single type. -/// -/// No ordering of the returned list is guaranteed. -#[jdwp_command(Vec, 14, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct VisibleClasses { - /// The class loader object ID - class_loader_id: ClassLoaderID, -} diff --git a/src/commands/class_object_reference.rs b/src/commands/class_object_reference.rs deleted file mode 100644 index 59b3ed2..0000000 --- a/src/commands/class_object_reference.rs +++ /dev/null @@ -1,11 +0,0 @@ -use jdwp_macros::{jdwp_command, JdwpWritable}; - -use crate::types::{ClassObjectID, TaggedReferenceTypeID}; - -/// Returns the reference type reflected by this class object. -#[jdwp_command(TaggedReferenceTypeID, 17, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ReflectedType { - /// The class object - class_object_id: ClassObjectID, -} diff --git a/src/commands/class_type.rs b/src/commands/class_type.rs deleted file mode 100644 index c87b71c..0000000 --- a/src/commands/class_type.rs +++ /dev/null @@ -1,140 +0,0 @@ -use std::io::{self, Read}; - -use jdwp_macros::jdwp_command; - -use crate::{codec::JdwpReader, enums::InvokeOptions, types::*}; - -use super::{JdwpReadable, JdwpWritable}; - -/// Returns the immediate superclass of a class. -/// -/// The return is null if the class is java.lang.Object. -#[jdwp_command(Option, 3, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Superclass { - /// The class type ID. - class_id: ClassID, -} - -/// Sets the value of one or more static fields. -/// -/// Each field must be member of the class type or one of its superclasses, -/// superinterfaces, or implemented interfaces. -/// -/// Access control is not enforced; for example, the values of private fields -/// can be set. -/// -/// Final fields cannot be set. -/// -/// For primitive values, the value's type must match the field's type exactly. -/// -/// For object values, there must exist a widening reference conversion from the -/// value's type to thefield's type and the field's type must be loaded. -#[jdwp_command((), 3, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues<'a> { - /// The class type ID. - class_id: ClassID, - /// Fields to set and their values. - values: &'a [(FieldID, UntaggedValue)], -} - -/// Invokes a static method. The method must be member of the class type or one -/// of its superclasses, superinterfaces, or implemented interfaces. Access -/// control is not enforced; for example, private methods can be invoked. -/// -/// The method invocation will occur in the specified thread. Method invocation -/// can occur only if the specified thread has been suspended by an event. -/// Method invocation is not supported when the target VM has been suspended by -/// the front-end. -/// -/// The specified method is invoked with the arguments in the specified argument -/// list. The method invocation is synchronous; the reply packet is not sent -/// until the invoked method returns in the target VM. The return value -/// (possibly the void value) is included in the reply packet. If the invoked -/// method throws an exception, the exception object ID is set in the reply -/// packet; otherwise, the exception object ID is null. -/// -/// For primitive arguments, the argument value's type must match the argument's -/// type exactly. For object arguments, there must exist a widening reference -/// conversion from the argument value's type to the argument's type and the -/// argument's type must be loaded. -/// -/// By default, all threads in the target VM are resumed while the method is -/// being invoked if they were previously suspended by an event or by command. -/// This is done to prevent the deadlocks that will occur if any of the threads -/// own monitors that will be needed by the invoked method. It is possible that -/// breakpoints or other events might occur during the invocation. Note, -/// however, that this implicit resume acts exactly like the ThreadReference -/// [resume](super::thread_reference::Resume) command, so if the -/// thread's suspend count is greater than 1, it will remain in a suspended -/// state during the invocation. By default, when the invocation completes, all -/// threads in the target VM are suspended, regardless their state before the -/// invocation. -/// -/// The resumption of other threads during the invoke can be prevented by -/// specifying the -/// [INVOKE_SINGLE_THREADED](crate::enums::InvokeOptions::SINGLE_THREADED) bit -/// flag in the options field; however, there is no protection against or -/// recovery from the deadlocks described above, so this option should be used -/// with great caution. Only the specified thread will be resumed (as described -/// for all threads above). Upon completion of a single threaded invoke, the -/// invoking thread will be suspended once again. Note that any threads started -/// during the single threaded invocation will not be suspended when the -/// invocation completes. -/// -/// If the target VM is disconnected during the invoke (for example, through the -/// VirtualMachine [dispose](super::virtual_machine::Dispose) command) the -/// method invocation continues. - -#[jdwp_command(3, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct InvokeMethod<'a> { - /// The class type ID. - class_id: ClassID, - /// The thread in which to invoke. - thread_id: ThreadID, - /// The method to invoke. - method_id: MethodID, - /// Arguments to the method. - arguments: &'a [Value], - // Invocation options - options: InvokeOptions, -} - -#[jdwp_command(3, 4)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct NewInstance<'a> { - /// The class type ID. - class_id: ClassID, - /// The thread in which to invoke the constructor. - thread_id: ThreadID, - /// The constructor to invoke. - method_id: MethodID, - /// Arguments for the constructor method. - arguments: &'a [Value], - // Constructor invocation options - options: InvokeOptions, -} - -#[derive(Debug)] -pub enum NewInstanceReply { - /// The newly created object. - NewObject(TaggedObjectID), - /// The thrown exception. - Exception(TaggedObjectID), -} - -// better types everyone -impl JdwpReadable for NewInstanceReply { - fn read(read: &mut JdwpReader) -> io::Result { - let new_object = Option::::read(read)?; - let exception = Option::::read(read)?; - - match (new_object, exception) { - (Some(new_object), None) => Ok(NewInstanceReply::NewObject(new_object)), - (None, Some(exception)) => Ok(NewInstanceReply::Exception(exception)), - _ => Err(io::Error::from(io::ErrorKind::InvalidData)), - } - } -} diff --git a/src/commands/event.rs b/src/commands/event.rs deleted file mode 100644 index d135fe1..0000000 --- a/src/commands/event.rs +++ /dev/null @@ -1,377 +0,0 @@ -use crate::{ - codec::JdwpReadable, - enums::{ClassStatus, EventKind, SuspendPolicy}, - types::{ - FieldID, Location, ReferenceTypeID, RequestID, TaggedObjectID, TaggedReferenceTypeID, - ThreadID, Value, - }, -}; - -use super::jdwp_command; - -#[derive(Debug, Clone, JdwpReadable)] -#[repr(u8)] -pub enum Event { - /// Notification of step completion in the target VM. - /// - /// The step event is generated before the code at its location is executed. - SingleStep( - /// Request that generated the event - RequestID, - /// Stepped thread - ThreadID, - /// Location stepped to - Location, - ) = EventKind::SingleStep as u8, - /// Notification of a breakpoint in the target VM. - /// - /// The breakpoint event is generated before the code at its location is - /// executed. - Breakpoint( - /// Request that generated the event - RequestID, - /// Thread which hit breakpoint - ThreadID, - /// Location hit - Location, - ) = EventKind::Breakpoint as u8, - /// Notification of a method invocation in the target VM. - /// - /// This event is generated before any code in the invoked method has - /// executed. - /// - /// Method entry events are generated for both native and non-native - /// methods. - /// - /// In some VMs method entry events can occur for a particular thread before - /// its thread start event occurs if methods are called as part of the - /// thread's initialization. - MethodEntry( - /// Request that generated the event - RequestID, - /// Thread which entered method - ThreadID, - /// The initial executable location in the method - Location, - ) = EventKind::MethodEntry as u8, - /// Notification of a method return in the target VM. - /// - /// This event is generated after all code in the method has executed, but - /// the location of this event is the last executed location in the - /// method. - /// - /// Method exit events are generated for both native and non-native methods. - /// - /// Method exit events are not generated if the method terminates with a - /// thrown exception. - MethodExit( - /// Request that generated the event - RequestID, - /// Thread which exited method - ThreadID, - /// Location of exit - Location, - ) = EventKind::MethodExit as u8, - /// Notification of a method return in the target VM. - /// - /// This event is generated after all code in the method has executed, but - /// the location of this event is the last executed location in the - /// method. - /// - /// Method exit events are generated for both native and non-native methods. - /// - /// Method exit events are not generated if the method terminates with a - /// thrown exception. - /// - /// Since JDWP version 1.6. - MethodExitWithReturnValue( - /// Request that generated the event - RequestID, - /// Thread which exited method - ThreadID, - /// Location of exit - Location, - /// Value that will be returned by the method - Value, - ) = EventKind::MethodExitWithReturnValue as u8, - /// Notification that a thread in the target VM is attempting to enter a - /// monitor that is already acquired by another thread. - /// - /// Requires `can_request_monitor_events` capability - see - /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). - /// - /// Since JDWP version 1.6. - MonitorContendedEnter( - /// Request that generated the event - RequestID, - /// Thread which is trying to enter the monitor - ThreadID, - /// Monitor object reference - TaggedObjectID, - /// Location of contended monitor enter - Location, - ) = EventKind::MonitorContendedEnter as u8, - /// Notification of a thread in the target VM is entering a monitor after - /// waiting for it to be released by another thread. - /// - /// Requires `can_request_monitor_events` capability - see - /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). - /// - /// Since JDWP version 1.6. - MonitorContendedEntered( - /// Request that generated the event - RequestID, - /// Thread which entered monitor - ThreadID, - /// Monitor object reference - TaggedObjectID, - /// Location of contended monitor enter - Location, - ) = EventKind::MonitorContendedEntered as u8, - /// Notification of a thread about to wait on a monitor object. - /// - /// Requires `can_request_monitor_events` capability - see - /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). - /// - /// Since JDWP version 1.6. - MonitorWait( - /// Request that generated the event - RequestID, - /// Thread which is about to wait - ThreadID, - /// Monitor object reference - TaggedObjectID, - /// Location at which the wait will occur - Location, - /// Thread wait time in milliseconds - u64, - ) = EventKind::MonitorWait as u8, - /// Notification that a thread in the target VM has finished waiting on a - /// monitor object. - /// - /// Requires `can_request_monitor_events` capability - see - /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). - /// - /// Since JDWP version 1.6. - MonitorWaited( - /// Request that generated the event - RequestID, - /// Thread which waited - ThreadID, - /// Monitor object reference - TaggedObjectID, - /// Location at which the wait occurred - Location, - /// True if timed out - bool, - ) = EventKind::MonitorWaited as u8, - /// Notification of an exception in the target VM. - /// - /// If the exception is thrown from a non-native method, the exception event - /// is generated at the location where the exception is thrown. - /// - /// If the exception is thrown from a native method, the exception event is - /// generated at the first non-native location reached after the exception - /// is thrown. - Exception( - /// Request that generated the event - RequestID, - /// Thread with exception - ThreadID, - /// Location of exception throw (or first non-native location after - /// throw if thrown from a native method) - Location, - /// Thrown exception - TaggedObjectID, - /// Location of catch if caught. - /// - /// An exception is considered to be caught if, at the point of the - /// throw, the current location is dynamically enclosed in a try - /// statement that handles the exception. (See the JVM - /// specification for details). If there is such a try - /// statement, the catch location is the first location in the - /// appropriate catch clause. - /// - /// If there are native methods in the call stack at the time of the - /// exception, there are important restrictions to note about the - /// returned catch location. - /// - /// In such cases, it is not possible to predict whether an exception - /// will be handled by some native method on the call stack. - /// - /// Thus, it is possible that exceptions considered uncaught here will, - /// in fact, be handled by a native method and not cause - /// termination of the target VM. - /// - /// Furthermore, it cannot be assumed that the catch location returned - /// here will ever be reached by the throwing thread. If there - /// is a native frame between the current location and the catch - /// location, the exception might be handled and cleared in that - /// native method instead. - /// - /// Note that compilers can generate try-catch blocks in some cases - /// where they are not explicit in the source code; for example, - /// the code generated for synchronized and finally blocks can - /// contain implicit try-catch blocks. - /// - /// If such an implicitly generated try-catch is present on the call - /// stack at the time of the throw, the exception will be - /// considered caught even though it appears to be uncaught from - /// examination of the source code. - Option, - ) = EventKind::Exception as u8, - /// Notification of a new running thread in the target VM. - /// - /// The new thread can be the result of a call to `java.lang.Thread.start` - /// or the result of attaching a new thread to the VM though JNI. - /// - /// The notification is generated by the new thread some time before its - /// execution starts. - /// - /// Because of this timing, it is possible to receive other events for the - /// thread before this event is received. - /// - /// (Notably, Method Entry Events and Method Exit Events might occur during - /// thread initialization. - /// - /// It is also possible for the - /// [AllThreads](super::virtual_machine::AllThreads) command to return a - /// thread before its thread start event is received. - /// - /// Note that this event gives no information about the creation of the - /// thread object which may have happened much earlier, depending on the - /// VM being debugged. - ThreadStart( - /// Request that generated the event - RequestID, - /// Started thread - ThreadID, - ) = EventKind::ThreadStart as u8, - /// Notification of a completed thread in the target VM. - /// - /// The notification is generated by the dying thread before it terminates. - /// - /// Because of this timing, it is possible for - /// [AllThreads](super::virtual_machine::AllThreads) to return this thread - /// after this event is received. - /// - /// Note that this event gives no information about the lifetime of the - /// thread object. - /// - /// It may or may not be collected soon depending on what references exist - /// in the target VM. - ThreadDeath( - /// Request that generated the event - RequestID, - /// Ending thread - ThreadID, - ) = EventKind::ThreadDeath as u8, - /// Notification of a class prepare in the target VM. - /// - /// See the JVM specification for a definition of class preparation. - /// - /// Class prepare events are not generated for primitive classes - /// (for example, `java.lang.Integer.TYPE`). - ClassPrepare( - /// Request that generated the event - RequestID, - /// Preparing thread. - /// - /// In rare cases, this event may occur in a debugger system thread - /// within the target VM. - /// - /// Debugger threads take precautions to prevent these events, but they - /// cannot be avoided under some conditions, especially for some - /// subclasses of `java.lang.Error`. - /// - /// If the event was generated by a debugger system thread, the value - /// returned by this method is null, and if the requested suspend policy - /// for the event was [EventThread](SuspendPolicy::EventThread) - /// all threads will be suspended instead, and the composite event's - /// suspend policy will reflect this change. - /// - /// Note that the discussion above does not apply to system threads - /// created by the target VM during its normal (non-debug) - /// operation. - ThreadID, - /// Type being prepared - TaggedReferenceTypeID, - /// Type signature - String, - /// Status of type - ClassStatus, - ) = EventKind::ClassPrepare as u8, - /// Notification of a class unload in the target VM. - /// - /// There are severe constraints on the debugger back-end during garbage - /// collection, so unload information is greatly limited. - ClassUnload( - /// Request that generated the event - RequestID, - /// Type signature - String, - ) = EventKind::ClassUnload as u8, - /// Notification of a field access in the target VM. - /// - /// Field modifications are not considered field accesses. - /// - /// Requires `can_watch_field_access` capability - see - /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). - FieldAccess( - /// Request that generated the event - RequestID, - /// Accessing thread - ThreadID, - /// Location of access - Location, - /// Field being accessed - (ReferenceTypeID, FieldID), - /// Object being accessed (None for statics) - Option, - ) = EventKind::FieldAccess as u8, - /// Notification of a field modification in the target VM. Requires - /// `can_watch_field_modification` capability - see - /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). - FieldModification( - /// Request that generated the event - RequestID, - /// Modifying thread - ThreadID, - /// Location of modify - Location, - /// Field being modified - (TaggedReferenceTypeID, FieldID), - /// Object being modified (None for statics) - Option, - /// Value to be assigned - Value, - ) = EventKind::FieldModification as u8, - /// Notification of initialization of a target VM. - /// - /// This event is received before the main thread is started and before any - /// application code has been executed. - /// - /// Before this event occurs a significant amount of system code has - /// executed and a number of system classes have been loaded. - /// - /// This event is always generated by the target VM, even if not explicitly - /// requested. - VmStart( - /// Request that generated the event (or None if this event is - /// automatically generated) - Option, - /// Initial thread - ThreadID, - ) = EventKind::VmStart as u8, - VmDeath( - /// Request that generated the event - RequestID, - ) = EventKind::VmDeath as u8, -} - -#[jdwp_command((), 64, 100)] -#[derive(Debug, Clone, JdwpReadable)] -pub struct Composite { - pub suspend_policy: SuspendPolicy, - pub events: Vec, -} diff --git a/src/commands/event_request.rs b/src/commands/event_request.rs deleted file mode 100644 index 37499bc..0000000 --- a/src/commands/event_request.rs +++ /dev/null @@ -1,76 +0,0 @@ -use super::jdwp_command; - -use crate::{ - codec::JdwpWritable, - enums::{EventKind, SuspendPolicy}, - event_modifier::Modifier, - types::RequestID, -}; - -/// Set an event request. -/// -/// When the event described by this request occurs, an event is sent from the -/// target VM. -/// -/// If an event occurs that has not been requested then it is not sent from the -/// target VM. -/// -/// The two exceptions to this are the VM Start Event and the VM Death Event -/// which are automatically generated events - see -/// [Composite](super::event::Composite) command for further details. -#[jdwp_command(RequestID, 15, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Set<'a> { - /// Event kind to request. Some events may require a capability in order to - /// be requested. - event_kind: EventKind, - /// What threads are suspended when this event occurs? - /// - /// Note that the order of events and command replies accurately reflects - /// the order in which threads are suspended and resumed. - /// - /// For example, if a VM-wide resume is processed before an event occurs - /// which suspends the VM, the reply to the resume command will be written - /// to the transport before the suspending event. - suspend_policy: SuspendPolicy, - /// Constraints used to control the number of generated events. - /// - /// Modifiers specify additional tests that an event must satisfy before it - /// is placed in the event queue. - /// - /// Events are filtered by applying each modifier to an event in the order - /// they are specified in this collection. Only events that satisfy all - /// modifiers are reported. - /// - /// An empty list means there are no modifiers in the request. - /// - /// Filtering can improve debugger performance dramatically by reducing the - /// amount of event traffic sent from the target VM to the debugger. - modifiers: &'a [Modifier], -} - -/// Clear an event request. -/// -/// See [EventKind] for a complete list of events that can be cleared. -/// -/// Only the event request matching the specified event kind and `request_id` -/// is cleared. -/// -/// If there isn't a matching event request the command is a no-op and does not -/// result in an error. -/// -/// Automatically generated events do not have a corresponding event request -/// and may not be cleared using this command. -#[jdwp_command((), 15, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Clear { - /// Event kind to clear - event_kind: EventKind, - /// ID of request to clear - request_id: RequestID, -} - -/// Removes all set breakpoints, a no-op if there are no breakpoints set. -#[jdwp_command((), 15, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ClearAllBreakpoints; diff --git a/src/commands/interface_type.rs b/src/commands/interface_type.rs deleted file mode 100644 index f45142f..0000000 --- a/src/commands/interface_type.rs +++ /dev/null @@ -1,67 +0,0 @@ -use jdwp_macros::{jdwp_command, JdwpWritable}; - -use crate::{ - enums::InvokeOptions, - types::{InterfaceID, InvokeMethodReply, MethodID, ThreadID, Value}, -}; - -/// Invokes a static method. The method must not be a static initializer. -/// The method must be a member of the interface type. -/// -/// Since JDWP version 1.8 -/// -/// The method invocation will occur in the specified thread. Method -/// invocation can occur only if the specified thread has been suspended by -/// an event. Method invocation is not supported when the target VM has been -/// suspended by the front-end. -/// -/// The specified method is invoked with the arguments in the specified -/// argument list. The method invocation is synchronous; the reply packet is -/// not sent until the invoked method returns in the target VM. The return -/// value (possibly the void value) is included in the reply packet. If the -/// invoked method throws an exception, the exception object ID is set in -/// the reply packet; otherwise, the exception object ID is null. -/// -/// For primitive arguments, the argument value's type must match the -/// argument's type exactly. For object arguments, there must exist a -/// widening reference conversion from the argument value's type to the -/// argument's type and the argument's type must be loaded. -/// -/// By default, all threads in the target VM are resumed while the method is -/// being invoked if they were previously suspended by an event or by a -/// command. This is done to prevent the deadlocks that will occur if any of -/// the threads own monitors that will be needed by the invoked method. It -/// is possible that breakpoints or other events might occur during the -/// invocation. Note, however, that this implicit resume acts exactly like -/// the ThreadReference resume command, so if the thread's suspend count is -/// greater than 1, it will remain in a suspended state during the -/// invocation. By default, when the invocation completes, all threads in -/// the target VM are suspended, regardless their state before the -/// invocation. -/// -/// The resumption of other threads during the invoke can be prevented by -/// specifying the SINGLE_THREADED bit flag in the options field; -/// however, there is no protection against or recovery from the deadlocks -/// described above, so this option should be used with great caution. Only -/// the specified thread will be resumed (as described for all threads -/// above). Upon completion of a single threaded invoke, the invoking thread -/// will be suspended once again. Note that any threads started during the -/// single threaded invocation will not be suspended when the invocation -/// completes. - -// If the target VM is disconnected during the invoke (for example, through the VirtualMachine -// [Dispose](super::virtual_machine::Dispose) command) the method invocation continues. -#[jdwp_command(5, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct InvokeMethod<'a> { - /// The interface type ID - interface_id: InterfaceID, - /// The thread in which to invoke - thread_id: ThreadID, - /// The method to invoke - method_id: MethodID, - /// The argument values - arguments: &'a [Value], - /// Invocation options - options: InvokeOptions, -} diff --git a/src/commands/method.rs b/src/commands/method.rs deleted file mode 100644 index b8fa8d1..0000000 --- a/src/commands/method.rs +++ /dev/null @@ -1,171 +0,0 @@ -use jdwp_macros::JdwpReadable; - -use crate::{ - codec::JdwpWritable, - types::{MethodID, ReferenceTypeID}, -}; - -use super::jdwp_command; - -/// Returns line number information for the method, if present. -/// -/// The line table maps source line numbers to the initial code index of the -/// line. -/// -/// The line table is ordered by code index (from lowest to highest). -/// -/// The line number information is constant unless a new class definition is -/// installed using [RedefineClasses](super::virtual_machine::RedefineClasses). -#[jdwp_command(6, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct LineTable { - /// The class. - reference_type_id: ReferenceTypeID, - /// The method. - method_id: MethodID, -} - -#[derive(Debug, JdwpReadable)] -pub struct LineTableReply { - /// Lowest valid code index for the method, >=0, or -1 if the method is - /// native - pub start: i64, - /// Highest valid code index for the method, >=0, or -1 if the method is - /// native - pub end: i64, - /// The entries of the line table for this method. - pub lines: Vec, -} - -#[derive(Debug, JdwpReadable)] -pub struct Line { - /// Initial code index of the line, start <= lineCodeIndex < end - pub line_code_index: u64, - /// Line number. - pub line_number: u32, -} - -/// Returns variable information for the method. -/// -/// The variable table includes arguments and locals declared within the method. -/// For instance methods, the "this" reference is included in the table. Also, -/// synthetic variables may be present. -#[jdwp_command(6, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct VariableTable { - /// The class. - reference_type_id: ReferenceTypeID, - /// The method. - method_id: MethodID, -} - -#[derive(Debug, JdwpReadable)] -pub struct VariableTableReply { - /// The number of words in the frame used by arguments. Eight-byte arguments - /// use two words; all others use one. - pub arg_cnt: u32, - /// The variables. - pub variables: Vec, -} - -#[derive(Debug, JdwpReadable)] -pub struct Variable { - /// First code index at which the variable is visible. - /// - /// Used in conjunction with length. The variable can be get or set only - /// when the current codeIndex <= current frame code index < codeIndex + - /// length - pub code_index: u64, - /// The variable's name. - pub name: String, - /// The variable type's JNI signature. - pub signature: String, - /// Unsigned value used in conjunction with codeIndex. - /// - /// The variable can be get or set only when the current codeIndex <= - /// current frame code index < code index + length - pub length: u32, - /// The local variable's index in its frame - pub slot: u32, -} - -/// Retrieve the method's bytecodes as defined in The Java™ Virtual Machine -/// Specification. -/// -/// Requires `canGetBytecodes` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command(Vec, 6, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Bytecodes { - /// The class. - reference_type_id: ReferenceTypeID, - /// The method. - method_id: MethodID, -} - -/// Determine if this method is obsolete. -/// -/// A method is obsolete if it has been replaced by a non-equivalent method -/// using the [RedefineClasses](super::virtual_machine::RedefineClasses) -/// command. The original and redefined methods are considered equivalent if -/// their bytecodes are the same except for indices into the constant pool and -/// the referenced constants are equal. -#[jdwp_command(bool, 6, 4)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct IsObsolete { - /// The class. - reference_type_id: ReferenceTypeID, - /// The method. - method_id: MethodID, -} - -/// Returns variable information for the method, including generic signatures -/// for the variables. -/// -/// The variable table includes arguments and locals declared within the method. -/// For instance methods, the "this" reference is included in the table. Also, -/// synthetic variables may be present. Generic signatures are described in the -/// signature attribute section in The Java™ Virtual Machine Specification. -/// -/// Since JDWP version 1.5. -#[jdwp_command(6, 5)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct VariableTableWithGeneric { - /// The class. - reference_type_id: ReferenceTypeID, - /// The method. - method_id: MethodID, -} - -#[derive(Debug, JdwpReadable)] -pub struct VariableTableWithGenericReply { - /// The number of words in the frame used by arguments. Eight-byte arguments - /// use two words; all others use one. - pub arg_cnt: u32, - /// The variables. - pub variables: Vec, -} - -#[derive(Debug, JdwpReadable)] -pub struct VariableWithGeneric { - /// First code index at which the variable is visible. - /// - /// Used in conjunction with length. The variable can be get or set only - /// when the current codeIndex <= current frame code index < codeIndex + - /// length - pub code_index: u64, - /// The variable's name. - pub name: String, - /// The variable type's JNI signature. - pub signature: String, - /// The variable type's generic signature or an empty string if there is - /// none. - pub generic_signature: String, - /// Unsigned value used in conjunction with codeIndex. - /// - /// The variable can be get or set only when the current codeIndex <= - /// current frame code index < code index + length - pub length: u32, - /// The local variable's index in its frame - pub slot: u32, -} diff --git a/src/commands/mod.rs b/src/commands/mod.rs deleted file mode 100644 index afb0838..0000000 --- a/src/commands/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -use jdwp_macros::jdwp_command; - -use crate::{ - codec::{JdwpReadable, JdwpWritable}, - CommandId, -}; - -pub mod array_reference; -pub mod array_type; -pub mod class_loader_reference; -pub mod class_object_reference; -pub mod class_type; -pub mod event; -pub mod event_request; -pub mod interface_type; -pub mod method; -pub mod object_reference; -pub mod reference_type; -pub mod stack_frame; -pub mod string_reference; -pub mod thread_group_reference; -pub mod thread_reference; -pub mod virtual_machine; - -/// This module is defined to mirror the JDWP command set, which is empty -pub mod field {} - -pub trait Command { - const ID: CommandId; - - type Output; -} diff --git a/src/commands/object_reference.rs b/src/commands/object_reference.rs deleted file mode 100644 index 12ad8b9..0000000 --- a/src/commands/object_reference.rs +++ /dev/null @@ -1,195 +0,0 @@ -use super::jdwp_command; -use crate::{ - codec::{JdwpReadable, JdwpWritable}, - enums::InvokeOptions, - functional::Coll, - types::{ - ClassID, FieldID, InvokeMethodReply, ObjectID, TaggedObjectID, TaggedReferenceTypeID, - ThreadID, UntaggedValue, Value, - }, -}; - -#[jdwp_command(TaggedReferenceTypeID, 9, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ReferenceType { - /// The object ID - object: ObjectID, -} - -/// Returns the value of one or more instance fields. -/// -/// Each field must be member of the object's type or one of its superclasses, -/// superinterfaces, or implemented interfaces. Access control is not enforced; -/// for example, the values of private fields can be obtained. -#[jdwp_command(C::Map, 9, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct GetValues> { - /// The object ID - object: ObjectID, - /// Fields to get - fields: C, -} - -/// Sets the value of one or more instance fields. -/// -/// Each field must be member of the object's type or one of its superclasses, -/// superinterfaces, or implemented interfaces. Access control is not enforced; -/// for example, the values of private fields can be set. For primitive values, -/// the value's type must match the field's type exactly. For object values, -/// there must be a widening reference conversion from the value's type to the -/// field's type and the field's type must be loaded. -#[jdwp_command((), 9, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues<'a> { - /// The object ID - object: ObjectID, - /// Fields and the values to set them to - fields: &'a [(FieldID, UntaggedValue)], -} - -/// Returns monitor information for an object. -/// -/// All threads in the VM must be suspended. -/// -/// Requires `can_get_monitor_info` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command(9, 5)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct MonitorInfo { - /// The object ID - object: ObjectID, -} - -#[derive(Debug, JdwpReadable)] -pub struct MonitorInfoReply { - /// The monitor owner, or null if it is not currently owned - pub owner: Option, - /// The number of times the monitor has been entered. - pub entry_count: i32, - /// The threads that are waiting for the monitor 0 if there is no current - /// owner - pub waiters: Vec, -} - -/// Invokes a instance method. -/// -/// The method must be member of the object's type or one of its superclasses, -/// superinterfaces, or implemented interfaces. Access control is not enforced; -/// for example, private methods can be invoked. -/// -/// The method invocation will occur in the specified thread. Method invocation -/// can occur only if the specified thread has been suspended by an event. -/// Method invocation is not supported when the target VM has been suspended by -/// the front-end. -/// -/// The specified method is invoked with the arguments in the specified argument -/// list. The method invocation is synchronous; the reply packet is not sent -/// until the invoked method returns in the target VM. The return value -/// (possibly the void value) is included in the reply packet. -/// -/// For primitive arguments, the argument value's type must match the argument's -/// type exactly. For object arguments, there must be a widening reference -/// conversion from the argument value's type to the argument's type and the -/// argument's type must be loaded. -/// -/// By default, all threads in the target VM are resumed while the method is -/// being invoked if they were previously suspended by an event or by a command. -/// This is done to prevent the deadlocks that will occur if any of the threads -/// own monitors that will be needed by the invoked method. It is possible that -/// breakpoints or other events might occur during the invocation. Note, -/// however, that this implicit resume acts exactly like the ThreadReference -/// resume command, so if the thread's suspend count is greater than 1, it will -/// remain in a suspended state during the invocation. By default, when the -/// invocation completes, all threads in the target VM are suspended, regardless -/// their state before the invocation. -/// -/// The resumption of other threads during the invoke can be prevented by -/// specifying the INVOKE_SINGLE_THREADED bit flag in the options field; -/// however, there is no protection against or recovery from the deadlocks -/// described above, so this option should be used with great caution. Only the -/// specified thread will be resumed (as described for all threads above). Upon -/// completion of a single threaded invoke, the invoking thread will be -/// suspended once again. Note that any threads started during the single -/// threaded invocation will not be suspended when the invocation completes. -/// -/// If the target VM is disconnected during the invoke (for example, through the -/// VirtualMachine dispose command) the method invocation continues. -#[jdwp_command(9, 6)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct InvokeMethod<'a> { - /// The object ID - object: ObjectID, - /// The thread in which to invoke - thread: ThreadID, - /// The class type - class: ClassID, - /// The method to invoke - method: FieldID, - /// The arguments - arguments: &'a [Value], - /// Invocation options - options: InvokeOptions, -} - -/// Prevents garbage collection for the given object. -/// -/// By default all objects in back-end replies may be collected at any time the -/// target VM is running. A call to this command guarantees that the object will -/// not be collected. The [EnableCollection] command can be used to allow -/// collection once again. -/// -/// Note that while the target VM is suspended, no garbage collection will occur -/// because all threads are suspended. The typical examination of variables, -/// fields, and arrays during the suspension is safe without explicitly -/// disabling garbage collection. -/// -/// This method should be used sparingly, as it alters the pattern of garbage -/// collection in the target VM and, consequently, may result in application -/// behavior under the debugger that differs from its non-debugged behavior. -#[jdwp_command((), 9, 7)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct DisableCollection { - /// The object ID - object: ObjectID, -} - -/// Permits garbage collection for this object. -/// -/// By default all objects returned by JDWP may become unreachable in the target -/// VM, and hence may be garbage collected. A call to this command is necessary -/// only if garbage collection was previously disabled with the -/// [DisableCollection] command. -#[jdwp_command((), 9, 8)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct EnableCollection { - /// The object ID - object: ObjectID, -} - -/// Determines whether an object has been garbage collected in the target VM. -#[jdwp_command(bool, 9, 9)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct IsCollected { - /// The object ID - object: ObjectID, -} - -/// Returns objects that directly reference this object. Only objects that are -/// reachable for the purposes of garbage collection are returned. Note that an -/// object can also be referenced in other ways, such as from a local variable -/// in a stack frame, or from a JNI global reference. Such non-object referrers -/// are not returned by this command. -/// -/// Since JDWP version 1.6. -/// -/// Requires `can_get_instance_info` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command(Vec, 9, 10)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ReferringObjects { - /// The object ID - object: ObjectID, - /// Maximum number of referring objects to return. Must be non-negative. If - /// zero, all referring objects are returned. - max_referrers: u32, -} diff --git a/src/commands/reference_type.rs b/src/commands/reference_type.rs deleted file mode 100644 index 88fc38f..0000000 --- a/src/commands/reference_type.rs +++ /dev/null @@ -1,390 +0,0 @@ -use std::fmt::{self, Debug}; - -use super::jdwp_command; -use crate::{ - codec::{JdwpReadable, JdwpWritable}, - enums::ClassStatus, - functional::Coll, - jvm::{FieldModifiers, MethodModifiers, TypeModifiers}, - types::{ - ClassLoaderID, ClassObjectID, FieldID, InterfaceID, MethodID, ReferenceTypeID, - TaggedObjectID, TaggedReferenceTypeID, Value, - }, -}; - -/// Returns the JNI signature of a reference type. -/// -/// JNI signature formats are described in the Java Native Interface -/// Specification. -/// -/// For primitive classes the returned signature is the signature of the -/// corresponding primitive type; for example, "I" is returned as the signature -/// of the class represented by `java.lang.Integer.TYPE`. -#[jdwp_command(String, 2, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Signature { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the instance of `java.lang.ClassLoader` which loaded a given -/// reference type. -/// -/// If the reference type was loaded by the system class loader, the returned -/// object ID is null. -#[jdwp_command(Option, 2, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ClassLoader { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the modifiers (also known as access flags) for a reference type. -/// -/// The returned bit mask contains information on the declaration of the -/// reference type. -/// -/// If the reference type is an array or a primitive class (for example, -/// `java.lang.Integer.TYPE`), the value of the returned bit mask is undefined. -#[jdwp_command(TypeModifiers, 2, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Modifiers { - ref_type: ReferenceTypeID, -} - -/// Returns information for each field in a reference type. -/// -/// Inherited fields are not included. -/// -/// The field list will include any synthetic fields created by the compiler. -/// -/// Fields are returned in the order they occur in the class file. -#[jdwp_command(Vec, 2, 4)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Fields { - ref_type: ReferenceTypeID, -} - -#[derive(Debug, JdwpReadable)] -pub struct Field { - /// Field ID - pub field_id: FieldID, - /// Name of field - pub name: String, - /// JNI Signature of field. - pub signature: String, - /// The modifier bit flags (also known as access flags) which provide - /// additional information on the field declaration. - /// - /// Individual flag values are defined in Chapter 4 of The Java™ Virtual - /// Machine Specification. - /// - /// In addition, the 0xf0000000 bit identifies the field as synthetic, if - /// the synthetic attribute capability is available. - pub mod_bits: FieldModifiers, -} - -/// Returns information for each method in a reference type. -/// -/// Inherited methods are not included. -/// -/// The list of methods will include constructors (identified with the name -/// "<init>"), the initialization method (identified with the name -/// "<clinit>") if present, and any synthetic methods created by the -/// compiler. -/// -/// Methods are returned in the order they occur in the class file. -#[jdwp_command(Vec, 2, 5)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Methods { - ref_type: ReferenceTypeID, -} - -#[derive(Debug, JdwpReadable)] -pub struct Method { - /// Method ID - pub method_id: MethodID, - /// Name of method - pub name: String, - /// JNI Signature of method. - pub signature: String, - /// The modifier bit flags (also known as access flags) which provide - /// additional information on the method declaration. - /// - /// Individual flag values are defined in Chapter 4 of The Java™ Virtual - /// Machine Specification. - /// - /// In addition, The 0xf0000000 bit identifies the method as synthetic, if - /// the synthetic attribute capability is available. - pub mod_bits: MethodModifiers, -} - -/// Returns the value of one or more static fields of the reference type. -/// -/// Each field must be member of the reference type or one of its superclasses, -/// superinterfaces, or implemented interfaces. -/// -/// Access control is not enforced; for example, the values of private fields -/// can be obtained. -#[jdwp_command(C::Map, 2, 6)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct GetValues> { - /// The reference type ID - pub ref_type: ReferenceTypeID, - /// Field IDs of fields to get - pub fields: C, -} - -/// Returns the source file name in which a reference type was declared. -#[jdwp_command(String, 2, 7)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SourceFile { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the classes and interfaces directly nested within this type. Types -/// further nested within those types are not included. -#[jdwp_command(Vec, 2, 8)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct NestedTypes { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the current status of the reference type. -/// -/// The status indicates the extent to which the reference type has been -/// initialized, as described in section 2.1.6 of The Java™ Virtual Machine -/// Specification. -/// -/// If the class is linked the PREPARED and VERIFIED bits in the returned -/// status bits will be set. -/// -/// If the class is initialized the INITIALIZED bit in the returned status bits -/// will be set. -/// -/// If an error occurred during initialization then the ERROR bit in the -/// returned status bits will be set. -/// -/// The returned status bits are undefined for array types and for primitive -/// classes (such as java.lang.Integer.TYPE). -#[jdwp_command(ClassStatus, 2, 9)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Status { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the interfaces declared as implemented by this class. -/// -/// Interfaces indirectly implemented (extended by the implemented interface or -/// implemented by a superclass) are not included. -#[jdwp_command(Vec, 2, 10)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Interfaces { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the class object corresponding to this type. -#[jdwp_command(ClassObjectID, 2, 11)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ClassObject { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the value of the SourceDebugExtension attribute. -/// -/// Since JDWP version 1.4. Requires canGetSourceDebugExtension capability - -/// see [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command(String, 2, 12)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SourceDebugExtension { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -/// Returns the JNI signature of a reference type along with the generic -/// signature if there is one. -/// -/// Generic signatures are described in the signature attribute section in -/// The Java™ Virtual Machine Specification. -/// -/// Since JDWP version 1.5. -#[jdwp_command(2, 13)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SignatureWithGeneric { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -#[derive(Debug, JdwpReadable)] -pub struct SignatureWithGenericReply { - /// The JNI signature for the reference type. - pub signature: String, - /// The generic signature for the reference type or an empty string if - /// there is none. - pub generic_signature: String, -} - -/// Returns information, including the generic signature if any, for each field -/// in a reference type. -/// -/// Inherited fields are not included. -/// -/// The field list will include any synthetic fields created by the compiler. -/// -/// Fields are returned in the order they occur in the class file. -/// -/// Generic signatures are described in the signature attribute section in -/// The Java™ Virtual Machine Specification. -/// -/// Since JDWP version 1.5. - -#[jdwp_command(Vec, 2, 14)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct FieldsWithGeneric { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -#[derive(Debug, JdwpReadable)] -pub struct FieldWithGeneric { - /// The field ID - pub field_id: FieldID, - /// The name of the field - pub name: String, - /// The JNI signature of the field - pub signature: String, - /// The generic signature of the field, or an empty string if there is none - pub generic_signature: String, - /// The modifier bit flags (also known as access flags) which provide - /// additional information on the field declaration. - /// - /// Individual flag values are defined in Chapter 4 of The Java™ Virtual - /// Machine Specification. - /// - /// In addition, the 0xf0000000 bit identifies the field as synthetic, if - /// the synthetic attribute capability is available. - pub mod_bits: FieldModifiers, -} - -/// Returns information, including the generic signature if any, for each method -/// in a reference type. Inherited methodss are not included. -/// The list of methods will include constructors (identified with the name -/// "<init>"), the initialization method (identified with the name -/// "<clinit>") if present, and any synthetic methods created by the -/// compiler. Methods are returned in the order they occur in the class file. -/// -/// Generic signatures are described in the signature attribute section in -/// The Java™ Virtual Machine Specification. -/// -/// Since JDWP version 1.5. -#[jdwp_command(Vec, 2, 15)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct MethodsWithGeneric { - /// The reference type ID - ref_type: ReferenceTypeID, -} - -#[derive(Debug, JdwpReadable)] -pub struct MethodWithGeneric { - /// The method ID - pub method_id: MethodID, - /// The name of the method - pub name: String, - /// The JNI signature of the method - pub signature: String, - /// The generic signature of the method, or an empty string if there is none - pub generic_signature: String, - /// The modifier bit flags (also known as access flags) which provide - /// additional information on the method declaration. - /// - /// Individual flag values are defined in Chapter 4 of The Java™ Virtual - /// Machine Specification. - /// - /// In addition, the 0xf0000000 bit identifies the method as synthetic, if - /// the synthetic attribute capability is available. - pub mod_bits: MethodModifiers, -} - -/// Returns instances of this reference type. -/// -/// Only instances that are reachable for the purposes of garbage collection are -/// returned. -#[jdwp_command(Vec, 2, 16)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Instances { - /// The reference type ID - ref_type: ReferenceTypeID, - /// Maximum number of instances to return. - /// - /// If zero, all instances are returned. - max_instances: u32, -} - -/// Returns the class object corresponding to this type. -#[jdwp_command(2, 17)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ClassFileVersion { - /// The class - ref_type: ReferenceTypeID, -} - -#[derive(Debug, JdwpReadable)] -pub struct ClassFileVersionReply { - /// Major version number - pub major_version: u32, - /// Minor version number - pub minor_version: u32, -} - -/// Return the raw bytes of the constant pool in the format of the -/// constant_pool item of the Class File Format in The Java™ Virtual Machine -/// Specification. -/// -/// Since JDWP version 1.6. Requires canGetConstantPool capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command(2, 18)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ConstantPool { - /// The class - ref_type: ReferenceTypeID, -} - -#[derive(JdwpReadable)] -pub struct ConstantPoolReply { - /// Total number of constant pool entries plus one. - /// - /// This corresponds to the constant_pool_count item of the Class File - /// Format in The Java™ Virtual Machine Specification. - pub count: u32, - /// Raw bytes of the constant pool - pub bytes: Vec, -} - -// special debug so that trace logs dont take a quadrillion lines -impl Debug for ConstantPoolReply { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let hex_bytes = self - .bytes - .iter() - .map(|b| format!("{:02x}", b)) - .collect::(); - - struct Unquoted(String); - - impl Debug for Unquoted { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(&self.0) - } - } - - f.debug_struct("ConstantPoolReply") - .field("count", &self.count) - .field("bytes", &Unquoted(hex_bytes)) - .finish() - } -} diff --git a/src/commands/stack_frame.rs b/src/commands/stack_frame.rs deleted file mode 100644 index 92180f6..0000000 --- a/src/commands/stack_frame.rs +++ /dev/null @@ -1,83 +0,0 @@ -use crate::{ - codec::JdwpWritable, - enums::Tag, - functional::Coll, - types::{FrameID, TaggedObjectID, ThreadID, Value}, -}; - -use super::jdwp_command; - -/// Returns the value of one or more local variables in a given frame. -/// -/// Each variable must be visible at the frame's code index. -/// -/// Even if local variable information is not available, values can be retrieved -/// if the front-end is able to determine the correct local variable index. -/// (Typically, this index can be determined for method arguments from the -/// method signature without access to the local variable table information.) -#[jdwp_command(C::Map, 16, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct GetValues> { - /// The frame's thread. - pub thread_id: ThreadID, - /// The frame ID. - pub frame_id: FrameID, - /// Local variable indices and types to get. - pub slots: C, -} - -/// Sets the value of one or more local variables. -/// -/// Each variable must be visible at the current frame code index. For primitive -/// values, the value's type must match the variable's type exactly. For object -/// values, there must be a widening reference conversion from the value's type -/// to thevariable's type and the variable's type must be loaded. -/// -/// Even if local variable information is not available, values can be set, if -/// the front-end is able to determine the correct local variable index. -/// (Typically, thisindex can be determined for method arguments from the method -/// signature without access to the local variable table information.) -#[jdwp_command((), 16, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SetValues<'a> { - /// The frame's thread. - pub thread_id: ThreadID, - /// The frame ID. - pub frame_id: FrameID, - /// Local variable indices and values to set. - pub slots: &'a [(u32, Value)], -} - -/// Returns the value of the 'this' reference for this frame. -/// -/// If the frame's method is static or native, the reply will contain the null -/// object reference. -#[jdwp_command(Option, 16, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ThisObject { - /// The frame's thread. - pub thread_id: ThreadID, - /// The frame ID. - pub frame_id: FrameID, -} - -/// Pop the top-most stack frames of the thread stack, up to, and including -/// 'frame'. The thread must be suspended to perform this command. The top-most -/// stack frames are discarded and the stack frame previous to 'frame' becomes -/// the current frame. The operand stack is restored -- the argument values are -/// added back and if the invoke was not invokestatic, objectref is added back -/// as well. The Java virtual machine program counter is restored to the opcode -/// of the invoke instruction. -/// -/// Since JDWP version 1.4. -/// -/// Requires `canPopFrames` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command((), 16, 4)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct PopFrames { - /// The frame's thread. - pub thread_id: ThreadID, - /// The frame ID. - pub frame_id: FrameID, -} diff --git a/src/commands/string_reference.rs b/src/commands/string_reference.rs deleted file mode 100644 index 4c4c216..0000000 --- a/src/commands/string_reference.rs +++ /dev/null @@ -1,11 +0,0 @@ -use jdwp_macros::{jdwp_command, JdwpWritable}; - -use crate::types::ObjectID; - -/// Returns the characters contained in the string. -#[jdwp_command(String, 10, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Value { - /// The String object ID - string_object: ObjectID, -} diff --git a/src/commands/thread_group_reference.rs b/src/commands/thread_group_reference.rs deleted file mode 100644 index e7fa11d..0000000 --- a/src/commands/thread_group_reference.rs +++ /dev/null @@ -1,44 +0,0 @@ -use super::jdwp_command; -use crate::{ - codec::{JdwpReadable, JdwpWritable}, - types::{ThreadGroupID, ThreadID}, -}; - -/// Returns the thread group name. -#[jdwp_command(String, 12, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Name { - /// The thread group object ID - group: ThreadGroupID, -} - -/// Returns the thread group, if any, which contains a given thread group. -#[jdwp_command(Option, 12, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Parent { - /// The thread group object ID - group: ThreadGroupID, -} - -/// Returns the live threads and active thread groups directly contained in -/// this thread group. -/// -/// Threads and thread groups in child thread groups are not included. -/// -/// A thread is alive if it has been started and has not yet been stopped. -/// -/// See `java.lang.ThreadGroup` for information about active ThreadGroups. -#[jdwp_command(12, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Children { - /// The thread group object ID - group: ThreadGroupID, -} - -#[derive(Debug, JdwpReadable)] -pub struct ChildrenReply { - /// Live direct child threads - pub child_threads: Vec, - /// Active child thread groups - pub child_groups: Vec, -} diff --git a/src/commands/thread_reference.rs b/src/commands/thread_reference.rs deleted file mode 100644 index fd52b14..0000000 --- a/src/commands/thread_reference.rs +++ /dev/null @@ -1,261 +0,0 @@ -use jdwp_macros::jdwp_command; - -use crate::{ - codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, - enums::{SuspendStatus, ThreadStatus}, - types::{FrameID, Location, TaggedObjectID, ThreadGroupID, ThreadID, Value}, -}; - -/// Returns the thread name. -#[jdwp_command(String, 11, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Name { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Suspends the thread. -/// -/// Unlike `java.lang.Thread.suspend()`, suspends of both the virtual machine -/// and individual threads are counted. Before a thread will run again, it must -/// be resumed the same number of times it has been suspended. -/// -/// Suspending single threads with command has the same dangers -/// `java.lang.Thread.suspend()`. If the suspended thread holds a monitor needed -/// by another running thread, deadlock is possible in the target VM (at least -/// until the suspended thread is resumed again). -/// -/// The suspended thread is guaranteed to remain suspended until resumed through -/// one of the JDI resume methods mentioned above; the application in the target -/// VM cannot resume the suspended thread through `java.lang.Thread.resume()`. -/// -/// Note that this doesn't change the status of the thread (see the -/// [ThreadStatus] command.) For example, if it was Running, it will still -/// appear running to other threads. -#[jdwp_command((), 11, 2)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Suspend { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Resumes the execution of a given thread. -/// -/// If this thread was not previously suspended by the front-end, calling this -/// command has no effect. Otherwise, the count of pending suspends on this -/// thread is decremented. If it is decremented to 0, the thread will continue -/// to execute. -#[jdwp_command((), 11, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Resume { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Returns the current status of a thread. -/// -/// The thread status reply indicates the thread status the last time it was -/// running. the suspend status provides information on the thread's suspension, -/// if any. -#[jdwp_command((ThreadStatus, SuspendStatus), 11, 4)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Status { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Returns the thread group that contains a given thread. -#[jdwp_command(ThreadGroupID, 11, 5)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ThreadGroup { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Returns the current call stack of a suspended thread. -/// -/// The sequence of frames starts with the currently executing frame, followed -/// by its caller, and so on. The thread must be suspended, and the returned -/// frameID is valid only while the thread is suspended. -#[jdwp_command(Vec<(FrameID, Location)>, 11, 6)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Frames { - /// The thread object ID. - pub thread: ThreadID, - /// The index of the first frame to retrieve. - pub start_frame: u32, - /// The amount of frames to retrieve. - pub limit: FrameLimit, -} - -/// A nice readable enum to be used in place of raw `i32` with a special meaning -/// for -1. -#[derive(Debug, Clone)] -pub enum FrameLimit { - Limit(u32), - AllRemaining, -} - -impl JdwpWritable for FrameLimit { - fn write(&self, write: &mut JdwpWriter) -> std::io::Result<()> { - match self { - FrameLimit::Limit(n) => n.write(write), - FrameLimit::AllRemaining => (-1i32).write(write), - } - } -} - -/// Returns the count of frames on this thread's stack. -/// -/// The thread must be suspended, and the returned count is valid only while the -/// thread is suspended. -/// -/// Returns [ThreadNotSuspended](crate::enums::ErrorCode::ThreadNotSuspended) if -/// not suspended. -#[jdwp_command(u32, 11, 7)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct FrameCount { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Returns the objects whose monitors have been entered by this thread. -/// -/// The thread must be suspended, and the returned information is relevant only -/// while the thread is suspended. -/// -/// Requires `can_get_owned_monitor_info` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command(Vec, 11, 8)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct OwnedMonitors { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Returns the object, if any, for which this thread is waiting. -/// -/// The thread may be waiting to enter a monitor, or it may be waiting, via the -/// `java.lang.Object.wait` method, for another thread to invoke the notify -/// method. The thread must be suspended, and the returned information is -/// relevant only while the thread is suspended. -/// -/// Requires `can_get_current_contended_monitor` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command(Option, 11, 9)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct CurrentContendedMonitor { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Stops the thread with an asynchronous exception, as if done by -/// `java.lang.Thread.stop` -#[jdwp_command((), 11, 10)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Stop { - /// The thread object ID. - pub thread: ThreadID, - /// Asynchronous exception. - /// - /// This object must be an instance of `java.lang.Throwable` or a subclass - pub throwable: TaggedObjectID, -} - -/// Interrupt the thread, as if done by `java.lang.Thread.interrupt` -#[jdwp_command((), 11, 11)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Interrupt { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Get the suspend count for this thread. -/// -/// The suspend count is the number of times the thread has been suspended -/// through the thread-level or VM-level suspend commands without a -/// corresponding resume -#[jdwp_command(u32, 11, 12)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SuspendCount { - /// The thread object ID. - pub thread: ThreadID, -} - -/// Returns monitor objects owned by the thread, along with stack depth at which -/// the monitor was acquired. -/// -/// Stack depth can be unknown (e.g., for monitors acquired by JNI -/// MonitorEnter). The thread must be suspended, and the returned information is -/// relevant only while the thread is suspended. -/// -/// Requires `can_get_monitor_frame_info` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -/// -/// Since JDWP version 1.6. -#[jdwp_command(Vec<(TaggedObjectID, StackDepth)>, 11, 13)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct OwnedMonitorsStackDepthInfo { - /// The thread object ID. - pub thread: ThreadID, -} - -#[derive(Debug, Clone)] -pub enum StackDepth { - Depth(u32), - Unknown, -} - -impl JdwpReadable for StackDepth { - fn read(read: &mut JdwpReader) -> std::io::Result { - let depth = match i32::read(read)? { - -1 => StackDepth::Unknown, - n => StackDepth::Depth(n as u32), - }; - Ok(depth) - } -} - -/// Force a method to return before it reaches a return statement. -/// -/// The method which will return early is referred to as the called method. The -/// called method is the current method (as defined by the Frames section in The -/// Java™ Virtual Machine Specification) for the specified thread at the time -/// this command is received. -/// -/// The specified thread must be suspended. The return occurs when execution of -/// Java programming language code is resumed on this thread. Between sending -/// this command and resumption of thread execution, the state of the stack is -/// undefined. -/// -/// No further instructions are executed in the called method. Specifically, -/// finally blocks are not executed. Note: this can cause inconsistent states in -/// the application. -/// -/// A lock acquired by calling the called method (if it is a synchronized -/// method) and locks acquired by entering synchronized blocks within the called -/// method are released. Note: this does not apply to JNI locks or -/// java.util.concurrent.locks locks. -/// -/// Events, such as [MethodExit](super::event::Event::MethodExit), are generated -/// as they would be in a normal return. -/// -/// The called method must be a non-native Java programming language method. -/// Forcing return on a thread with only one frame on the stack causes the -/// thread to exit when resumed. -/// -/// For void methods, the value must be a void value. For methods that return -/// primitive values, the value's type must match the return type exactly. For -/// object values, there must be a widening reference conversion from the -/// value's type to the return type type and the return type must be loaded. -/// -/// Since JDWP version 1.6. Requires `can_force_early_return` capability - see -/// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). -#[jdwp_command((), 11, 14)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ForceEarlyReturn { - /// The thread object ID. - pub thread: ThreadID, - /// The value to return. - pub value: Value, -} diff --git a/src/commands/virtual_machine.rs b/src/commands/virtual_machine.rs deleted file mode 100644 index ef33f3e..0000000 --- a/src/commands/virtual_machine.rs +++ /dev/null @@ -1,509 +0,0 @@ -use std::{fmt::Debug, marker::PhantomData}; - -use crate::{ - codec::{JdwpReadable, JdwpWritable}, - enums::ClassStatus, - functional::{Coll, Single}, - types::{ObjectID, ReferenceTypeID, StringID, TaggedReferenceTypeID, ThreadGroupID, ThreadID}, -}; - -use super::jdwp_command; - -/// Returns the JDWP version implemented by the target VM. -/// -/// The version string format is implementation dependent. -#[jdwp_command(1, 1)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Version; - -#[derive(Debug, JdwpReadable)] -pub struct VersionReply { - /// Text information on the VM version - pub description: String, - /// Major JDWP Version number - pub version_major: u32, - /// Minor JDWP Version number - pub version_minor: u32, - /// Target VM JRE version, as in the java.version property - pub vm_version: String, - /// Target VM name, as in the java.vm.name property - pub vm_name: String, -} - -/// Returns reference types for all the classes loaded by the target VM which -/// match the given signature. -/// -/// Multiple reference types will be returned if two or more class loaders have -/// loaded a class of the same name. -/// -/// The search is confined to loaded classes only; no attempt is made to load a -/// class of the given signature. -#[jdwp_command(C, 1, 2)] -#[derive(Clone, JdwpWritable)] -pub struct ClassesBySignatureGeneric> { - /// JNI signature of the class to find (for example, "Ljava/lang/String;") - signature: String, - _phantom: PhantomData, -} - -/// This is needed because inference cannot guess what you need since there are -/// no parameters -/// And the Single helper type is in a private jdwp module -pub type ClassBySignature = ClassesBySignatureGeneric>; - -impl Debug for ClassBySignature { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ClassBySignature") - .field("signature", &self.signature) - .finish() - } -} - -/// The inference is able to figure out N by the destructuring pattern -pub type ClassesBySignatureStatic = - ClassesBySignatureGeneric<[(TaggedReferenceTypeID, ClassStatus); N]>; - -impl Debug for ClassesBySignatureStatic { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct(&format!("ClassesBySignatureStatic<{}>", N)) // hope this const-optimizes?. - .field("signature", &self.signature) - .finish() - } -} - -/// The 'standard' variant with a vector -pub type ClassesBySignature = ClassesBySignatureGeneric>; - -impl Debug for ClassesBySignature { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ClassesBySignature") - .field("signature", &self.signature) - .finish() - } -} - -/// Returns reference types for all classes currently loaded by the target VM. -#[jdwp_command(Vec, 1, 3)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct AllClasses; - -#[derive(Debug, JdwpReadable)] -pub struct Class { - /// Matching loaded reference type - pub type_id: TaggedReferenceTypeID, - /// The JNI signature of the loaded reference type - pub signature: String, - /// The current class status - pub status: ClassStatus, -} - -/// Returns all threads currently running in the target VM. -/// -/// The returned list contains threads created through java.lang.Thread, all -/// native threads attached to the target VM through JNI, and system threads -/// created by the target VM. -/// -/// Threads that have not yet been started and threads that have completed -/// their execution are not included in the returned list. -#[jdwp_command(Vec, 1, 4)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct AllThreads; - -/// Returns all thread groups that do not have a parent. This command may be -/// used as the first step in building a tree (or trees) of the existing thread -/// groups. -#[jdwp_command(Vec, 1, 5)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct TopLevelThreadGroups; - -/// Invalidates this virtual machine mirror. -/// -/// The communication channel to the target VM is closed, and the target VM -/// prepares to accept another subsequent connection from this debugger or -/// another debugger, including the following tasks: -/// - All event requests are cancelled. -/// - All threads suspended by the thread-level resume command or the VM-level -/// resume command are resumed as many times as necessary for them to run. -/// - Garbage collection is re-enabled in all cases where it was disabled -/// -/// Any current method invocations executing in the target VM are continued -/// after the disconnection. Upon completion of any such method invocation, -/// the invoking thread continues from the location where it was originally -/// stopped. -/// -/// Resources originating in this VirtualMachine (ObjectReferences, -/// ReferenceTypes, etc.) will become invalid. -#[jdwp_command((), 1, 6)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Dispose; - -/// Returns the sizes of variably-sized data types in the target VM. -/// -/// The returned values indicate the number of bytes used by the identifiers in -/// command and reply packets. -#[jdwp_command(IDSizeInfo, 1, 7)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct IDSizes; - -#[derive(Debug, Clone, JdwpReadable)] -pub struct IDSizeInfo { - /// field_id size in bytes - pub field_id_size: u32, - /// method_id size in bytes - pub method_id_size: u32, - /// object_id size in bytes - pub object_id_size: u32, - /// reference_type_id size in bytes - pub reference_type_id_size: u32, - /// frame_id size in bytes - pub frame_id_size: u32, -} - -/// Suspends the execution of the application running in the target VM. -/// All Java threads currently running will be suspended. -/// -/// Unlike java.lang.Thread.suspend, suspends of both the virtual machine and -/// individual threads are counted. Before a thread will run again, it must -/// be resumed through the VM-level resume command or the thread-level resume -/// command the same number of times it has been suspended. -#[jdwp_command((), 1, 8)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Suspend; - -/// Resumes execution of the application after the suspend command or an event -/// has stopped it. -/// -/// Suspensions of the Virtual Machine and individual threads are counted. -/// -/// If a particular thread is suspended n times, it must resumed n times before -/// it will continue. -#[jdwp_command((), 1, 9)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Resume; - -/// Terminates the target VM with the given exit code. -/// -/// On some platforms, the exit code might be truncated, for example, to the -/// low order 8 bits. -/// -/// All ids previously returned from the target VM become invalid. -/// -/// Threads running in the VM are abruptly terminated. -/// -/// A thread death exception is not thrown and finally blocks are not run. -#[jdwp_command((), 1, 10)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Exit { - exit_code: i32, -} - -/// Creates a new string object in the target VM and returns its id. -#[jdwp_command(StringID, 1, 11)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct CreateString { - /// UTF-8 characters to use in the created string - string: String, -} - -/// Retrieve this VM's capabilities. -/// -/// The capabilities are returned as booleans, each indicating the presence or -/// absence of a capability. -/// -/// The commands associated with each capability will return the -/// NOT_IMPLEMENTED error if the capability is not available. -#[jdwp_command(1, 12)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct Capabilities; - -#[derive(Debug, JdwpReadable)] -pub struct CapabilitiesReply { - /// Can the VM watch field modification, and therefore can it send the - /// Modification Watchpoint Event? - pub can_watch_field_modification: bool, - /// Can the VM watch field access, and therefore can it send the - /// Access Watchpoint Event? - pub can_watch_field_access: bool, - /// Can the VM get the bytecodes of a given method? - pub can_get_bytecodes: bool, - /// Can the VM determine whether a field or method is synthetic? - /// (that is, can the VM determine if the method or the field was invented - /// by the compiler?) - pub can_get_synthetic_attribute: bool, - /// Can the VM get the owned monitors information for a thread? - pub can_get_owned_monitor_info: bool, - /// Can the VM get the current contended monitor of a thread? - pub can_get_current_contended_monitor: bool, - /// Can the VM get the monitor information for a given object? - pub can_get_monitor_info: bool, -} - -/// Retrieve the classpath and bootclasspath of the target VM. -/// -/// If the classpath is not defined, returns an empty list. -/// -/// If the bootclasspath is not defined returns an empty list. -#[jdwp_command(1, 13)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ClassPaths; - -#[derive(Debug, JdwpReadable)] -pub struct ClassPathsReply { - /// Base directory used to resolve relative paths in either of the following - /// lists. - pub base_dir: String, - /// Components of the classpath - pub classpaths: Vec, - /// Components of the bootclasspath - pub bootclasspaths: Vec, -} - -/// Releases a list of object IDs. -/// -/// For each object in the list, the following applies. -/// -/// The count of references held by the back-end (the reference count) will be -/// decremented by ref_cnt. -/// -/// If thereafter the reference count is less than or equal to zero, the ID is -/// freed. -/// -/// Any back-end resources associated with the freed ID may be freed, and if -/// garbage collection was disabled for the object, it will be re-enabled. -/// -/// The sender of this command promises that no further commands will be sent -/// referencing a freed ID. -/// -/// Use of this command is not required. -/// -/// If it is not sent, resources associated with each ID will be freed by the -/// back-end at some time after the corresponding object is garbage collected. -/// -/// It is most useful to use this command to reduce the load on the back-end if -/// a very large number of objects has been retrieved from the back-end (a large -/// array, for example) but may not be garbage collected any time soon. -/// -/// IDs may be re-used by the back-end after they have been freed with this -/// command. -/// -/// This description assumes reference counting, a back-end may use any -/// implementation which operates equivalently. -#[jdwp_command((), 1, 14)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct DisposeObjects<'a> { - requests: &'a [(ObjectID, u32)], -} - -/// Tells the target VM to stop sending events. Events are not discarded; they -/// are held until a subsequent ReleaseEvents command is sent. -/// -/// This command is useful to control the number of events sent to the debugger -/// VM in situations where very large numbers of events are generated. -/// -/// While events are held by the debugger back-end, application execution may -/// be frozen by the debugger back-end to prevent buffer overflows on the back -/// end. -/// -/// Responses to commands are never held and are not affected by this command. -/// If events are already being held, this command is ignored. -#[jdwp_command((), 1, 15)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct HoldEvents; - -/// Tells the target VM to continue sending events. -/// -/// This command is used to restore normal activity after a HoldEvents command. -/// -/// If there is no current HoldEvents command in effect, this command is -/// ignored. -#[jdwp_command((), 1, 16)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct ReleaseEvents; - -/// Retrieve all of this VM's capabilities. -/// -/// The capabilities are returned as booleans, each indicating the presence or -/// absence of a capability. -/// -/// The commands associated with each capability will return the -/// NOT_IMPLEMENTED error if the capability is not available. -/// -/// Since JDWP version 1.4. -#[jdwp_command(1, 17)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct CapabilitiesNew; - -#[derive(JdwpReadable)] -pub struct CapabilitiesNewReply { - /// The prefix of [CapabilitiesNew] is identical to that of old - /// [Capabilities] - pub capabilities: CapabilitiesReply, - /// Can the VM redefine classes? - pub can_redefine_classes: bool, - /// Can the VM add methods when redefining classes? - pub can_add_method: bool, - /// Can the VM redefine classes in arbitrary ways? - pub can_unrestrictedly_redefine_classes: bool, - /// Can the VM pop stack frames? - pub can_pop_frames: bool, - /// Can the VM filter events by specific object? - pub can_use_instance_filters: bool, - /// Can the VM get the source debug extension? - pub can_get_source_debug_extension: bool, - /// Can the VM request VM death events? - pub can_request_vmdeath_event: bool, - /// Can the VM set a default stratum? - pub can_set_default_stratum: bool, - /// Can the VM return instances, counts of instances of classes and - /// referring objects? - pub can_get_instance_info: bool, - /// Can the VM request monitor events? - pub can_request_monitor_events: bool, - /// Can the VM get monitors with frame depth info? - pub can_get_monitor_frame_info: bool, - /// Can the VM filter class prepare events by source name? - pub can_use_source_name_filters: bool, - /// Can the VM return the constant pool information? - pub can_get_constant_pool: bool, - /// Can the VM force early return from a method? - pub can_force_early_return: bool, - /// Reserved for future capability - _reserved_22: bool, - /// Reserved for future capability - _reserved_23: bool, - /// Reserved for future capability - _reserved_24: bool, - /// Reserved for future capability - _reserved_25: bool, - /// Reserved for future capability - _reserved_26: bool, - /// Reserved for future capability - _reserved_27: bool, - /// Reserved for future capability - _reserved_28: bool, - /// Reserved for future capability - _reserved_29: bool, - /// Reserved for future capability - _reserved_30: bool, - /// Reserved for future capability - _reserved_31: bool, - /// Reserved for future capability - _reserved_32: bool, -} - -// skip reserved fields from Debug -impl Debug for CapabilitiesNewReply { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("CapabilitiesNewReply") - .field("capabilities", &self.capabilities) - .field("can_redefine_classes", &self.can_redefine_classes) - .field("can_add_method", &self.can_add_method) - .field( - "can_unrestrictedly_redefine_classes", - &self.can_unrestrictedly_redefine_classes, - ) - .field("can_pop_frames", &self.can_pop_frames) - .field("can_use_instance_filters", &self.can_use_instance_filters) - .field( - "can_get_source_debug_extension", - &self.can_get_source_debug_extension, - ) - .field("can_request_vmdeath_event", &self.can_request_vmdeath_event) - .field("can_set_default_stratum", &self.can_set_default_stratum) - .field("can_get_instance_info", &self.can_get_instance_info) - .field( - "can_request_monitor_events", - &self.can_request_monitor_events, - ) - .field( - "can_get_monitor_frame_info", - &self.can_get_monitor_frame_info, - ) - .field( - "can_use_source_name_filters", - &self.can_use_source_name_filters, - ) - .field("can_get_constant_pool", &self.can_get_constant_pool) - .field("can_force_early_return", &self.can_force_early_return) - .finish() - } -} - -/// Installs new class definitions. -/// -/// If there are active stack frames in methods of the redefined classes in the -/// target VM then those active frames continue to run the bytecodes of the -/// original method. These methods are considered obsolete - see -/// [IsObsolete](super::method::IsObsolete). -/// -/// The methods in the redefined classes will be used for new invokes in the -/// target VM. The original method ID refers to the redefined method. -/// -/// All breakpoints in the redefined classes are cleared. -/// -/// If resetting of stack frames is desired, the PopFrames command can be -/// used to pop frames with obsolete methods. -/// -/// Requires `can_redefine_classes` capability - see [CapabilitiesNew]. -/// -/// In addition to the `can_redefine_classes` capability, the target VM must -/// have the `can_add_method` capability to add methods when redefining classes, -/// or the `can_unrestrictedly_redefine_classes` to redefine classes in -/// arbitrary ways. -#[jdwp_command((), 1, 18)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct RedefineClasses<'a> { - classes: &'a [(ReferenceTypeID, Vec)], -} - -/// Set the default stratum. Requires `can_set_default_stratum` capability - -/// see [CapabilitiesNew]. -#[jdwp_command((), 1, 19)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct SetDefaultStratum { - /// default stratum, or empty string to use reference type default. - stratum_id: String, -} - -/// Returns reference types for all classes currently loaded by the target VM. -/// -/// Both the JNI signature and the generic signature are returned for each -/// class. -/// -/// Generic signatures are described in the signature attribute section in -/// The Java™ Virtual Machine Specification. -/// -/// Since JDWP version 1.5. -#[jdwp_command(Vec, 1, 20)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct AllClassesWithGeneric; - -#[derive(Debug, JdwpReadable)] -pub struct GenericClass { - /// Loaded reference type - pub type_id: TaggedReferenceTypeID, - /// The JNI signature of the loaded reference type - pub signature: String, - /// The generic signature of the loaded reference type or an empty string if - /// there is none. - pub generic_signature: String, - /// The current class status - pub status: ClassStatus, -} - -/// Returns the number of instances of each reference type in the input list. -/// -/// Only instances that are reachable for the purposes of garbage collection -/// are counted. -/// -/// If a reference type is invalid, eg. it has been unloaded, zero is returned -/// for its instance count. -/// -/// Since JDWP version 1.6. Requires canGetInstanceInfo capability - see -/// [CapabilitiesNew]. -#[jdwp_command(C::Map, 1, 21)] -#[derive(Debug, Clone, JdwpWritable)] -pub struct InstanceCounts> { - /// A list of reference type IDs. - ref_types: C, -} diff --git a/src/enums.rs b/src/enums.rs deleted file mode 100644 index 38bd44d..0000000 --- a/src/enums.rs +++ /dev/null @@ -1,301 +0,0 @@ -use std::{ - fmt::{Display, Formatter}, - io::{self, Error, ErrorKind, Read, Write}, -}; - -use bitflags::bitflags; - -use crate::codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}; - -macro_rules! jdwp_enum { - ($e:ident: $repr:ident, $($name:ident = $id:literal | $string:literal),* $(,)?) => { - #[repr($repr)] - #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] - #[non_exhaustive] - pub enum $e { - $( - #[doc = $string] - $name = $id, - )* - } - - impl TryFrom<$repr> for $e { - type Error = $repr; - - fn try_from(value: $repr) -> Result { - match value { - $($id => Ok($e::$name),)* - other => Err(other), - } - } - } - - impl JdwpReadable for $e { - fn read(read: &mut JdwpReader) -> std::io::Result { - Self::try_from($repr::read(read)?) - .map_err(|_| Error::from(ErrorKind::InvalidData)) - } - } - - impl JdwpWritable for $e { - fn write(&self, write: &mut JdwpWriter) -> std::io::Result<()> { - (*self as $repr).write(write) - } - } - }; - ($e:ident: $repr:ident, $($name:ident = $id:literal),* $(,)?) => { - jdwp_enum!($e: $repr, $($name = $id | "",)*); - }; - ($e:ident: $repr:ident | Display, $($name:ident = $id:literal | $string:literal),* $(,)?) => { - jdwp_enum!($e: $repr, $($name = $id | $string,)*); - - impl Display for $e { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { - $($e::$name => $string,)* - }) - } - } - }; -} - -jdwp_enum! { - ErrorCode: u16 | Display, - - None = 0 | "No error has occurred", - InvalidThread = 10 | "Passed thread is null, is not a valid thread or has exited", - InvalidThreadGroup = 11 | "Thread group invalid", - InvalidPriority = 12 | "Invalid priority", - ThreadNotSuspended = 13 | "If the specified thread has not been suspended by an event", - ThreadSuspended = 14 | "Thread already suspended", - ThreadNotAlive = 15 | "Thread has not been started or is now dead", - InvalidObject = 20 | "If this reference type has been unloaded and garbage collected", - InvalidClass = 21 | "Invalid class", - ClassNotPrepared = 22 | "Class has been loaded but not yet prepared", - InvalidMethodid = 23 | "Invalid method", - InvalidLocation = 24 | "Invalid location", - InvalidFieldid = 25 | "Invalid field", - InvalidFrameid = 30 | "Invalid jframeID", - NoMoreFrames = 31 | "There are no more Java or JNI frames on the call stack", - OpaqueFrame = 32 | "Information about the frame is not available", - NotCurrentFrame = 33 | "Operation can only be performed on current frame", - TypeMismatch = 34 | "The variable is not an appropriate type for the function used", - InvalidSlot = 35 | "Invalid slot", - Duplicate = 40 | "Item already set", - NotFound = 41 | "Desired element not found", - InvalidMonitor = 50 | "Invalid monitor", - NotMonitorOwner = 51 | "This thread doesn't own the monitor", - Interrupt = 52 | "The call has been interrupted before completion", - InvalidClassFormat = 60 | "The virtual machine attempted to read a class file and determined that the file is malformed or otherwise cannot be interpreted as a class file", - CircularClassDefinition = 61 | "A circularity has been detected while initializing a class", - FailsVerification = 62 | "The verifier detected that a class file, though well formed, contained some sort of internal inconsistency or security problem", - AddMethodNotImplemented = 63 | "Adding methods has not been implemented", - SchemaChangeNotImplemented = 64 | "Schema change has not been implemented", - InvalidTypestate = 65 | "The state of the thread has been modified, and is now inconsistent", - HierarchyChangeNotImplemented = 66 | "A direct superclass is different for the new class version, or the set of directly implemented interfaces is different and canUnrestrictedlyRedefineClasses is false", - DeleteMethodNotImplemented = 67 | "The new class version does not declare a method declared in the old class version and canUnrestrictedlyRedefineClasses is false", - UnsupportedVersion = 68 | "A class file has a version number not supported by this VM", - NamesDontMatch = 69 | "The class name defined in the new class file is different from the name in the old class object", - ClassModifiersChangeNotImplemented = 70 | "The new class version has different modifiers and and canUnrestrictedlyRedefineClasses is false", - MethodModifiersChangeNotImplemented = 71 | "A method in the new class version has different modifiers than its counterpart in the old class version and and canUnrestrictedlyRedefineClasses is false", - NotImplemented = 99 | "The functionality is not implemented in this virtual machine", - NullPointer = 100 | "Invalid pointer", - AbsentInformation = 101 | "Desired information is not available", - InvalidEventType = 102 | "The specified event type id is not recognized", - IllegalArgument = 103 | "Illegal argument", - OutOfMemory = 110 | "The function needed to allocate memory and no more memory was available for allocation", - AccessDenied = 111 | "Debugging has not been enabled in this virtual machine. JVMTI cannot be used", - VmDead = 112 | "The virtual machine is not running", - Internal = 113 | "An unexpected internal error has occurred", - UnattachedThread = 115 | "The thread being used to call this function is not attached to the virtual machine. Calls must be made from attached threads", - InvalidTag = 500 | "object type id or class tag", - AlreadyInvoking = 502 | "Previous invoke not complete", - InvalidIndex = 503 | "Index is invalid", - InvalidLength = 504 | "The length is invalid", - InvalidString = 506 | "The string is invalid", - InvalidClassLoader = 507 | "The class loader is invalid", - InvalidArray = 508 | "The array is invalid", - TransportLoad = 509 | "Unable to load the transport", - TransportInit = 510 | "Unable to initialize the transport", - NativeMethod = 511 | "NATIVE_METHOD", - InvalidCount = 512 | "The count is invalid", -} - -jdwp_enum! { - EventKind: u8, - - SingleStep = 1, - Breakpoint = 2, - FramePop = 3, - Exception = 4, - UserDefined = 5, - ThreadStart = 6, - ThreadDeath = 7, - ClassPrepare = 8, - ClassUnload = 9, - ClassLoad = 10, - FieldAccess = 20, - FieldModification = 21, - ExceptionCatch = 30, - MethodEntry = 40, - MethodExit = 41, - MethodExitWithReturnValue = 42, - MonitorContendedEnter = 43, - MonitorContendedEntered = 44, - MonitorWait = 45, - MonitorWaited = 46, - VmStart = 90, - VmDeath = 99, - VmDisconnected = 100, -} - -jdwp_enum! { - ThreadStatus: u32, - - Zombie = 0, - Running = 1, - Sleeping = 2, - Monitor = 3, - Wait = 4, -} - -jdwp_enum! { - SuspendStatus: u32, - - NotSuspended = 0, - Suspended = 1, -} - -bitflags! { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct ClassStatus: u32 { - const VERIFIED = 1; - const PREPARED = 2; - const INITIALIZED = 4; - const ERROR = 8; - - const OK = Self::VERIFIED.bits() | Self::PREPARED.bits() | Self::INITIALIZED.bits(); - } -} - -impl JdwpReadable for ClassStatus { - fn read(read: &mut JdwpReader) -> io::Result { - Self::from_bits(u32::read(read)?).ok_or_else(|| Error::from(ErrorKind::InvalidData)) - } -} - -impl JdwpWritable for ClassStatus { - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - self.bits().write(write) - } -} - -jdwp_enum! { - TypeTag: u8, - - Class = 1 | "ReferenceType is a class", - Interface = 2 | "ReferenceType is an interface", - Array = 3 | "ReferenceType is an array", -} - -impl JdwpReadable for Option { - fn read(read: &mut JdwpReader) -> io::Result { - Ok(match u8::read(read)? { - 0 => None, - raw => Some(TypeTag::try_from(raw).map_err(|_| Error::from(ErrorKind::InvalidData))?), - }) - } -} - -jdwp_enum! { - Tag: u8, - - Array = 91 | "'[' - an array object ([ObjectID](crate::types::ObjectID) size).", - Byte = 66 | "'B' - a byte value (1 byte).", - Char = 67 | "'C' - a character value (2 bytes).", - Object = 76 | "'L' - an object ([ObjectID](crate::types::ObjectID) size).", - Float = 70 | "'F' - a float value (4 bytes).", - Double = 68 | "'D' - a double value (8 bytes).", - Int = 73 | "'I' - an int value (4 bytes).", - Long = 74 | "'J' - a long value (8 bytes).", - Short = 83 | "'S' - a short value (2 bytes).", - Void = 86 | "'V' - a void value (no bytes).", - Boolean = 90 | "'Z' - a boolean value (1 byte).", - String = 115 | "'s' - a String object ([ObjectID](crate::types::ObjectID) size).", - Thread = 116 | "'t' - a Thread object ([ObjectID](crate::types::ObjectID) size).", - ThreadGroup = 103 | "'g' - a ThreadGroup object ([ObjectID](crate::types::ObjectID) size).", - ClassLoader = 108 | "'l' - a ClassLoader object ([ObjectID](crate::types::ObjectID) size).", - ClassObject = 99 | "'c' - a class object object ([ObjectID](crate::types::ObjectID) size).", -} - -impl JdwpReadable for Option { - fn read(read: &mut JdwpReader) -> io::Result { - Ok(match u8::read(read)? { - 0 => None, - raw => Some(Tag::try_from(raw).map_err(|_| Error::from(ErrorKind::InvalidData))?), - }) - } -} - -jdwp_enum! { - StepDepth: u32, - - Into = 0 | "Step into any method calls that occur before the end of the step", - Over = 1 | "Step over any method calls that occur before the end of the step", - Out = 2 | "Step out of the current method", -} - -jdwp_enum! { - StepSize: u32, - - Min = 0 | "Step by the minimum possible amount (often a byte code instruction)", - Line = 1 | "Step to the next source line unless there is no line number information in which case a MIN step is done instead", -} - -jdwp_enum! { - SuspendPolicy: u8, - - None = 0 | "Suspend no threads when this event is encountered", - EventThread = 1 | "Suspend the event thread when this event is encountered", - All = 2 | "Suspend all threads when this event is encountered", -} - -bitflags! { - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct InvokeOptions: u32 { - const NONE = 0x00; - /// otherwise, all threads started - const SINGLE_THREADED = 0x01; - /// otherwise, normal virtual invoke (instance methods only) - const NONVIRTUAL = 0x02; - } -} - -impl JdwpReadable for InvokeOptions { - fn read(read: &mut JdwpReader) -> io::Result { - Self::from_bits(u32::read(read)?).ok_or_else(|| Error::from(ErrorKind::InvalidData)) - } -} - -impl JdwpWritable for InvokeOptions { - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - self.bits().write(write) - } -} - -jdwp_enum! { - ModifierKind: u8, - - Count = 1 | "Limit the requested event to be reported at most once after a given number of occurrences. The event is not reported the first count - 1 times this filter is reached. To request a one-off event, call this method with a count of 1.", - Conditional = 2 | "Conditional on expression", - ThreadOnly = 3 | "Restricts reported events to those in the given thread. This modifier can be used with any event kind except for class unload.", - ClassOnly = 4 | "For class prepare events, restricts the events generated by this request to be the preparation of the given reference type and any subtypes. For monitor wait and waited events, restricts the events generated by this request to those whose monitor object is of the given reference type or any of its subtypes. For other events, restricts the events generated by this request to those whose location is in the given reference type or any of its subtypes. An event will be generated for any location in a reference type that can be safely cast to the given reference type. This modifier can be used with any event kind except class unload, thread start, and thread end.", - ClassMatch = 5 | "Restricts reported events to those for classes whose name matches the given restricted regular expression. For class prepare events, the prepared class name is matched. For class unload events, the unloaded class name is matched. For monitor wait and waited events, the name of the class of the monitor object is matched. For other events, the class name of the event's location is matched. This modifier can be used with any event kind except thread start and thread end.", - ClassExclude = 6 | "Restricts reported events to those for classes whose name does not match the given restricted regular expression. For class prepare events, the prepared class name is matched. For class unload events, the unloaded class name is matched. For monitor wait and waited events, the name of the class of the monitor object is matched. For other events, the class name of the event's location is matched. This modifier can be used with any event kind except thread start and thread end.", - LocationOnly = 7 | "Restricts reported events to those that occur at the given location. This modifier can be used with breakpoint, field access, field modification, step, and exception event kinds.", - ExceptionOnly = 8 | "Restricts reported exceptions by their class and whether they are caught or uncaught. This modifier can be used with exception event kinds only.", - FieldOnly = 9 | "Restricts reported events to those that occur for a given field. This modifier can be used with field access and field modification event kinds only.", - Step = 10 | "Restricts reported step events to those which satisfy depth and size constraints. This modifier can be used with step event kinds only.", - InstanceOnly = 11 | "Restricts reported events to those whose active 'this' object is the given object. Match value is the null object for static methods. This modifier can be used with any event kind except class prepare, class unload, thread start, and thread end. Introduced in JDWP version 1.4.", - SourceNameMatch = 12 | "", -} diff --git a/src/event_modifier.rs b/src/event_modifier.rs deleted file mode 100644 index 636dd54..0000000 --- a/src/event_modifier.rs +++ /dev/null @@ -1,192 +0,0 @@ -use crate::{ - codec::JdwpWritable, - enums::{ModifierKind, StepDepth, StepSize}, - types::{FieldID, Location, ObjectID, ReferenceTypeID, ThreadID}, -}; - -#[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpWritable)] -#[repr(u8)] -pub enum Modifier { - /// Limit the requested event to be reported at most once after a given - /// number of occurrences. - /// - /// The event is not reported the first count - 1 times this filter is - /// reached. - /// - /// To request a one-off event, call this method with a count of 1. - /// - /// Once the count reaches 0, any subsequent filters in this request are - /// applied. - /// - /// If none of those filters cause the event to be suppressed, the event is - /// reported. - /// - /// Otherwise, the event is not reported. - /// - /// In either case subsequent events are never reported for this request. - /// - /// This modifier can be used with any event kind. - Count( - /// Count before event. One for one-off - i32, - ) = ModifierKind::Count as u8, - /// Conditional on expression - Conditional { - /// For the future - expr_id: i32, - } = ModifierKind::Conditional as u8, - /// Restricts reported events to those in the given thread. - /// This modifier can be used with any event kind except for class unload. - ThreadOnly( - /// Required thread - ThreadID, - ) = ModifierKind::ThreadOnly as u8, - /// For class prepare events, restricts the events generated by this request - /// to be the preparation of the given reference type and any subtypes. - /// - /// For monitor wait and waited events, restricts the events generated by - /// this request to those whose monitor object is of the given reference - /// type or any of its subtypes. - /// - /// For other events, restricts the events generated by this request to - /// those whose location is in the given reference type or any of its - /// subtypes. - /// - /// An event will be generated for any location in a reference type that can - /// be safely cast to the given reference type. - /// - /// This modifier can be used with any event kind except class unload, - /// thread start, and thread end. - ClassOnly( - /// Required class - ReferenceTypeID, - ) = ModifierKind::ClassOnly as u8, - /// Restricts reported events to those for classes whose name matches the - /// given restricted regular expression. - /// - /// For class prepare events, the prepared class name is matched. - /// - /// For class unload events, the unloaded class name is matched. - /// - /// For monitor wait and waited events, the name of the class of the monitor - /// object is matched. - /// - /// For other events, the class name of the event's location is matched. - /// - /// This modifier can be used with any event kind except thread start and - /// thread end. - ClassMatch( - /// Required class pattern. - /// - /// Matches are limited to exact matches of the given class pattern and - /// matches of patterns that begin or end with `*`; for example, `*.Foo` - /// or `java.*`. - String, - ) = ModifierKind::ClassMatch as u8, - /// Restricts reported events to those for classes whose name does not match - /// the given restricted regular expression. - /// - /// For class prepare events, the prepared class name is matched. - /// - /// For class unload events, the unloaded class name is matched. - /// - /// For monitor wait and waited events, the name of the class of the monitor - /// object is matched. - /// - /// For other events, the class name of the event's location is matched. - /// - /// This modifier can be used with any event kind except thread start and - /// thread end. - ClassExclude( - /// Disallowed class pattern. - /// - /// Matches are limited to exact matches of the given class pattern and - /// matches of patterns that begin or end with `*`; for example, `*.Foo` - /// or `java.*`. - String, - ) = ModifierKind::ClassExclude as u8, - /// Restricts reported events to those that occur at the given location. - /// - /// This modifier can be used with breakpoint, field access, field - /// modification, step, and exception event kinds. - LocationOnly( - /// Required location - Location, - ) = ModifierKind::LocationOnly as u8, - /// Restricts reported exceptions by their class and whether they are caught - /// or uncaught. - /// - /// This modifier can be used with exception event kinds only. - ExceptionOnly { - /// Exception to report. `None` means report exceptions of all types. - /// - /// A non-null type restricts the reported exception events to - /// exceptions of the given type or any of its subtypes. - exception: Option, - /// Report caught exceptions - uncaught: bool, - /// Report uncaught exceptions. - /// - /// Note that it is not always possible to determine whether an - /// exception is caught or uncaught at the time it is thrown. - /// - /// See the exception event catch location under composite events for - /// more information. - caught: bool, - } = ModifierKind::ExceptionOnly as u8, - /// Restricts reported events to those that occur for a given field. - /// - /// This modifier can be used with field access and field modification event - /// kinds only. - FieldOnly( - /// Type in which field is declared - ReferenceTypeID, - /// Required field - FieldID, - ) = ModifierKind::FieldOnly as u8, - /// Restricts reported step events to those which satisfy depth and size - /// constraints. - /// - /// This modifier can be used with step event kinds only. - Step( - /// Thread in which to step - ThreadID, - /// Size of each step - StepSize, - /// Relative call stack limit - StepDepth, - ) = ModifierKind::Step as u8, - /// Restricts reported events to those whose active 'this' object is the - /// given object. - /// - /// Match value is the null object for static methods. - /// - /// This modifier can be used with any event kind except class prepare, - /// class unload, thread start, and thread end. - /// - /// Introduced in JDWP version 1.4. - InstanceOnly( - /// Required 'this' object - ObjectID, - ) = ModifierKind::InstanceOnly as u8, - /// Restricts reported class prepare events to those for reference types - /// which have a source name which matches the given restricted regular - /// expression. - /// - /// The source names are determined by the reference type's - /// SourceDebugExtension. - /// - /// This modifier can only be used with class prepare events. - /// - /// Since JDWP version 1.6. - /// - /// Requires the `can_use_source_name_filters` capability - see - /// [CapabilitiesNew](crate::commands::virtual_machine::CapabilitiesNew). - SourceNameMatch( - /// Required source name pattern. - /// Matches are limited to exact matches of the given pattern and - /// matches of patterns that begin or end with `*`; for example, - /// `*.Foo` or `java.*` - String, - ) = ModifierKind::SourceNameMatch as u8, -} diff --git a/src/functional.rs b/src/functional.rs index e1d8c84..66f2495 100644 --- a/src/functional.rs +++ b/src/functional.rs @@ -1,4 +1,4 @@ -use std::{iter, num::NonZeroUsize}; +use std::{fmt::Debug, iter, num::NonZeroUsize}; use std::ops::Deref; @@ -10,7 +10,7 @@ mod sealed { /// /// It makes it convenient to call certain commands where the input list size /// matches the output list size, for example -/// [GetValues](crate::commands::object_reference::GetValues). +/// [GetValues](crate::spec::object_reference::GetValues). pub trait Coll: sealed::Sealed { type Item; type Map; diff --git a/src/highlevel/array_type.rs b/src/highlevel/array_type.rs new file mode 100644 index 0000000..f2e05ef --- /dev/null +++ b/src/highlevel/array_type.rs @@ -0,0 +1,18 @@ +use crate::{ + client::ClientError, + spec::{array_type::NewInstance, ArrayTypeID}, +}; + +use super::{ExtendedJvmObject, JvmArray, JvmObject}; + +pub type ArrayType = ExtendedJvmObject; + +impl ArrayType { + pub fn new_instance(&self, length: u32) -> Result { + let array = self + .client() + .get() + .send(NewInstance::new(self.id(), length))?; + Ok(JvmArray::new(self.client().clone(), array.new_array)) + } +} diff --git a/src/highlevel/class_type.rs b/src/highlevel/class_type.rs new file mode 100644 index 0000000..754adda --- /dev/null +++ b/src/highlevel/class_type.rs @@ -0,0 +1,17 @@ +use crate::{ + client::ClientError, + spec::{class_type::Superclass, ClassID}, +}; + +use super::{ExtendedJvmObject, JvmObject}; + +pub type ClassType = ExtendedJvmObject; + +impl ClassType { + pub fn superclass(&self) -> Result, ClientError> { + let mut client = self.client().get(); + Ok(client + .send(Superclass::new(self.id()))? + .map(|id| ClassType::new(self.client().clone(), id))) + } +} diff --git a/src/highlevel/event.rs b/src/highlevel/event.rs new file mode 100644 index 0000000..dfc4de3 --- /dev/null +++ b/src/highlevel/event.rs @@ -0,0 +1,79 @@ +use crate::{ + highlevel::JvmField, + spec::event::{self, Spec}, +}; + +use super::{JvmThread, SharedClient, TaggedObject, TaggedReferenceType}; + +#[derive(Debug, Clone)] +pub struct Object; + +impl event::sealed::Domain for Object { + type Thread = JvmThread; + type TaggedObject = TaggedObject; + type TaggedReferenceType = TaggedReferenceType; + type Field = JvmField; +} + +pub type JvmEvent = event::Event; + +impl JvmEvent { + pub fn new(event: event::Event, client: SharedClient) -> Self { + use event::Event::*; + + match event { + SingleStep(rid, tid, loc) => SingleStep(rid, JvmThread::new(client, tid), loc), + Breakpoint(rid, tid, loc) => Breakpoint(rid, JvmThread::new(client, tid), loc), + MethodEntry(rid, tid, loc) => MethodEntry(rid, JvmThread::new(client, tid), loc), + MethodExit(rid, tid, loc) => MethodExit(rid, JvmThread::new(client, tid), loc), + MethodExitWithReturnValue(rid, tid, loc, val) => { + MethodExitWithReturnValue(rid, JvmThread::new(client, tid), loc, val) + } + MonitorContendedEnter(rid, tid, obj, loc) => { + let thread = JvmThread::new(client.clone(), tid); + let object = TaggedObject::new(client, obj); + MonitorContendedEnter(rid, thread, object, loc) + } + MonitorContendedEntered(rid, tid, obj, loc) => { + let thread = JvmThread::new(client.clone(), tid); + let object = TaggedObject::new(client, obj); + MonitorContendedEntered(rid, thread, object, loc) + } + MonitorWait(rid, tid, obj, loc, time) => { + let thread = JvmThread::new(client.clone(), tid); + let object = TaggedObject::new(client, obj); + MonitorWait(rid, thread, object, loc, time) + } + MonitorWaited(rid, tid, obj, loc, timed_out) => { + let thread = JvmThread::new(client.clone(), tid); + let object = TaggedObject::new(client, obj); + MonitorWaited(rid, thread, object, loc, timed_out) + } + Exception(rid, tid, e_loc, obj, c_loc) => { + let thread = JvmThread::new(client.clone(), tid); + let object = TaggedObject::new(client, obj); + Exception(rid, thread, e_loc, object, c_loc) + } + ThreadStart(rid, tid) => ThreadStart(rid, JvmThread::new(client, tid)), + ThreadDeath(rid, tid) => ThreadDeath(rid, JvmThread::new(client, tid)), + ClassPrepare(rid, tid, ref_id, sig, st) => { + let thread = JvmThread::new(client.clone(), tid); + let ref_type = TaggedReferenceType::new(client, ref_id); + ClassPrepare(rid, thread, ref_type, sig, st) + } + ClassUnload(rid, sig) => ClassUnload(rid, sig), + FieldAccess(rid, tid, loc, field_data) => { + let thread = JvmThread::new(client.clone(), tid); + let field = JvmField::new(client, field_data); + FieldAccess(rid, thread, loc, field) + } + FieldModification(rid, tid, loc, field_data, val) => { + let thread = JvmThread::new(client.clone(), tid); + let field = JvmField::new(client, field_data); + FieldModification(rid, thread, loc, field, val) + } + VmStart(rid, tid) => VmStart(rid, JvmThread::new(client, tid)), + VmDeath(rid) => VmDeath(rid), + } + } +} diff --git a/src/highlevel/field.rs b/src/highlevel/field.rs new file mode 100644 index 0000000..412d7cc --- /dev/null +++ b/src/highlevel/field.rs @@ -0,0 +1,170 @@ +use std::fmt::Debug; + +use crate::{ + client::ClientError, + codec::JdwpReadable, + functional::{Coll, Single}, + spec::{ + class_type, object_reference, reference_type, FieldID, ReferenceTypeID, TaggedObjectID, + TaggedReferenceTypeID, UntaggedValue, Value, + }, +}; + +use super::{ + ChildJvmObject, ClassType, JvmObject, ObjectReference, PlainJvmObject, ReferenceType, + SharedClient, TaggedReferenceType, +}; + +pub type JvmReferenceField = ChildJvmObject; +pub type JvmReferenceFields = ChildJvmObject; + +impl JvmReferenceField { + pub fn get(&self) -> Result { + let res = self.client().get().send(reference_type::GetValues::new( + self.parent().id(), + Single(self.id()), + ))?; + Ok(*res) + } +} + +impl ChildJvmObject, C> +where + C: Coll + Clone + Debug, + C::Map: JdwpReadable + Debug, +{ + pub fn get(&self) -> Result, ClientError> { + let res = self.client().get().send(reference_type::GetValues::new( + self.parent().id(), + self.id(), + ))?; + Ok(res) + } +} + +pub type JvmClassField = ChildJvmObject; +pub type JvmClassFields = ChildJvmObject; + +impl JvmClassField { + pub fn get(&self) -> Result { + let res = self.client().get().send(reference_type::GetValues::new( + *self.parent().id(), + Single(self.id()), + ))?; + Ok(*res) + } + + pub fn set(&self, value: impl Into) -> Result<(), ClientError> { + self.client().get().send(class_type::SetValues::new( + self.parent().id(), + &[(self.id(), value.into())], + )) + } +} + +impl JvmClassFields { + pub fn get(&self) -> Result<[Value; N], ClientError> { + self.client().get().send(reference_type::GetValues::new( + *self.parent().id(), + self.id(), + )) + } + + pub fn set(&self, values: [impl Into; N]) -> Result<(), ClientError> { + self.client().get().send(class_type::SetValues::new( + self.parent().id(), + &self + .id() + .into_iter() + .zip(values.into_iter().map(Into::into)) + .collect::>()[..], + )) + } +} + +pub type JvmInstanceField = ChildJvmObject; +pub type JvmInstanceFields = ChildJvmObject; + +impl JvmInstanceField { + pub fn get(&self) -> Result { + let [value] = self.client().get().send(object_reference::GetValues::new( + self.parent().id(), + [self.id()], + ))?; + Ok(value) + } + + pub fn set(&self, value: impl Into) -> Result<(), ClientError> { + self.client().get().send(object_reference::SetValues::new( + self.parent().id(), + &[(self.id(), value.into())], + )) + } +} + +impl JvmInstanceFields { + pub fn get(&self) -> Result<[Value; N], ClientError> { + self.client().get().send(object_reference::GetValues::new( + self.parent().id(), + self.id(), + )) + } + + pub fn set(&self, values: [impl Into; N]) -> Result<(), ClientError> { + self.client().get().send(object_reference::SetValues::new( + self.parent().id(), + &self + .id() + .into_iter() + .zip(values.into_iter().map(Into::into)) + .collect::>()[..], + )) + } +} + +#[derive(Debug)] +pub enum JvmField { + /// If the static field is part of an array or interface type, there is no + /// way to set it. + ReadonlyStatic(JvmReferenceField), + /// A static field of a class. + Static(JvmClassField), + /// An instance field of an object. + Instance(JvmInstanceField), +} + +impl JvmField { + pub fn new( + client: impl Into, + (ref_id, fid, obj): (TaggedReferenceTypeID, FieldID, Option), + ) -> Self { + if let Some(obj) = obj { + Self::Instance(ObjectReference::new(client, *obj).child(fid)) + } else if let TaggedReferenceTypeID::Class(class_id) = ref_id { + Self::Static(ClassType::new(client, class_id).child(fid)) + } else { + Self::ReadonlyStatic(TaggedReferenceType::new(client, ref_id).child(fid)) + } + } + + /// A shortcut to get the value of any kind of field. + pub fn get(&self) -> Result { + match self { + Self::ReadonlyStatic(field) => field.get(), + Self::Static(field) => field.get(), + Self::Instance(field) => field.get(), + } + } + + /// A shortcut to set the value of any kind of field. + /// + /// If the field is a static reference field, that is it's not statically + /// verified to be a static class field, this will fail. + pub fn set(&self, value: impl Into) -> Result<(), ClientError> { + match self { + Self::ReadonlyStatic(_) => todo!("highlevel errors"), + Self::Static(field) => field.set(value), + Self::Instance(field) => field.set(value), + } + } +} diff --git a/src/highlevel/generic.rs b/src/highlevel/generic.rs new file mode 100644 index 0000000..9e79612 --- /dev/null +++ b/src/highlevel/generic.rs @@ -0,0 +1,166 @@ +use std::{ + fmt::{self, Debug}, + ops::Deref, +}; + +use super::SharedClient; + +pub trait JvmObject: Clone { + type Id; + + fn client(&self) -> &SharedClient; + + fn id(&self) -> Self::Id; + + fn child(&self, id: N) -> ChildJvmObject { + ChildJvmObject::new(self.clone(), id) + } +} + +#[derive(Clone)] +pub struct PlainJvmObject { + client: SharedClient, + id: I, +} + +impl JvmObject for PlainJvmObject { + type Id = I; + + fn id(&self) -> I { + self.id.clone() + } + + fn client(&self) -> &SharedClient { + &self.client + } +} + +impl PlainJvmObject { + pub fn new(client: impl Into, id: I) -> Self { + Self { + client: client.into(), + id, + } + } +} + +impl Debug for PlainJvmObject { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("JvmObject").field(&self.id).finish() + } +} + +#[derive(Clone)] +pub struct ChildJvmObject { + parent: P, + id: I, +} + +impl JvmObject for ChildJvmObject { + type Id = I; + + fn id(&self) -> I { + self.id.clone() + } + + fn client(&self) -> &SharedClient { + self.parent.client() + } +} + +impl ChildJvmObject { + pub fn new(parent: P, id: I) -> Self { + Self { parent, id } + } + + pub fn parent(&self) -> &P { + &self.parent + } +} + +impl Debug for ChildJvmObject +where + P::Id: Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("NestedJvmObject") + .field(&self.parent.id()) + .field(&self.id) + .finish() + } +} + +#[derive(Clone)] +pub struct ExtendedJvmObject +where + I: Deref, + I::Target: Sized, +{ + peer: PlainJvmObject, + id: I, +} + +impl JvmObject for ExtendedJvmObject +where + I: Clone + Deref, + I::Target: Clone, +{ + type Id = I; + + fn id(&self) -> I { + self.id.clone() + } + + fn client(&self) -> &SharedClient { + &self.peer.client + } +} + +impl ExtendedJvmObject +where + I: Clone + Deref, + I::Target: Clone, +{ + pub fn new(client: impl Into, id: I) -> Self { + Self { + peer: PlainJvmObject::new(client, (*id).clone()), + id, + } + } + + pub fn peer(&self) -> &PlainJvmObject { + &self.peer + } +} + +impl Debug for ExtendedJvmObject +where + I: Deref, + I::Target: Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_tuple("WrapperJvmObject").field(&self.id).finish() + } +} + +impl From> for ExtendedJvmObject +where + I: Clone + Deref, + I::Target: Clone, +{ + fn from(peer: PlainJvmObject) -> Self { + Self::new(peer.client, peer.id) + } +} + +impl Deref for ExtendedJvmObject +where + I: Deref, + I::Target: Sized, +{ + type Target = PlainJvmObject; + + fn deref(&self) -> &Self::Target { + &self.peer + } +} diff --git a/src/highlevel/interface_type.rs b/src/highlevel/interface_type.rs new file mode 100644 index 0000000..abf9a96 --- /dev/null +++ b/src/highlevel/interface_type.rs @@ -0,0 +1,7 @@ +use crate::spec::InterfaceID; + +use super::ExtendedJvmObject; + +pub type InterfaceType = ExtendedJvmObject; + +impl InterfaceType {} diff --git a/src/highlevel/method.rs b/src/highlevel/method.rs new file mode 100644 index 0000000..a752e3e --- /dev/null +++ b/src/highlevel/method.rs @@ -0,0 +1,63 @@ +use crate::{ + client::ClientError, + spec::{ + class_type, interface_type, object_reference, ClassID, InvokeMethodReply, InvokeOptions, + MethodID, Value, + }, +}; + +use super::{ + ChildJvmObject, ClassType, InterfaceType, JvmObject, JvmThread, ObjectReference, SharedClient, + TaggedObject, +}; + +pub type JvmClassMethod = ChildJvmObject; +pub type JvmInterfaceMethod = ChildJvmObject; +pub type JvmInstanceMethod = ChildJvmObject; + +macro_rules! invoke_impls { + ($($module:ident => $method_type:ty)*) => { + $( + impl $method_type { + pub fn invoke( + &self, + thread: JvmThread, + args: &[Value], + options: InvokeOptions, + ) -> Result { + let reply = self.client().get().send($module::InvokeMethod::new( + self.parent().id(), + thread.id(), + self.id(), + args, + options, + ))?; + Ok(InvokeReply::new(self.client().clone(), reply)) + } + } + )* + }; +} + +invoke_impls! { + class_type => JvmClassMethod + interface_type => JvmInterfaceMethod + object_reference => JvmInstanceMethod +} + +#[derive(Debug)] +pub enum InvokeReply { + Value(Value), + Exception(TaggedObject), +} + +impl InvokeReply { + pub fn new(client: impl Into, reply: InvokeMethodReply) -> Self { + match reply { + InvokeMethodReply::Value(v) => Self::Value(v), + InvokeMethodReply::Exception(obj) => { + Self::Exception(TaggedObject::new(client.into(), obj)) + } + } + } +} diff --git a/src/highlevel/mod.rs b/src/highlevel/mod.rs new file mode 100644 index 0000000..8908181 --- /dev/null +++ b/src/highlevel/mod.rs @@ -0,0 +1,62 @@ +use std::{ + ops::Deref, + sync::{Arc, Mutex, MutexGuard}, +}; + +use crate::client::JdwpClient; + +#[derive(Debug, Clone)] +pub struct SharedClient(Arc>); + +impl From for SharedClient { + fn from(client: JdwpClient) -> Self { + Self(Arc::new(Mutex::new(client))) + } +} + +impl SharedClient { + fn get(&self) -> MutexGuard { + self.0.lock().expect("Posioned client lock") + } +} + +impl Deref for SharedClient { + type Target = Arc>; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +mod generic; +pub use generic::*; + +mod vm; +pub use vm::*; + +mod reference_type; +pub use reference_type::*; + +mod thread; +pub use thread::*; + +mod class_type; +pub use class_type::*; + +mod interface_type; +pub use interface_type::*; + +mod array_type; +pub use array_type::*; + +mod object_reference; +pub use object_reference::*; + +mod field; +pub use field::*; + +mod method; +pub use method::*; + +mod event; +pub use event::*; diff --git a/src/highlevel/object_reference.rs b/src/highlevel/object_reference.rs new file mode 100644 index 0000000..c428da1 --- /dev/null +++ b/src/highlevel/object_reference.rs @@ -0,0 +1,156 @@ +use crate::{ + client::ClientError, + spec::{ + array_reference::{GetValues, Length, SetValues}, + class_loader_reference::VisibleClasses, + class_object_reference::ReflectedType, + string_reference::Value, + thread_group_reference::{self, Children, Parent}, + ArrayID, ArrayRegion, ClassLoaderID, ClassObjectID, JdwpValue, ObjectID, StringID, Tag, + TaggedObjectID, ThreadGroupID, + }, +}; +use std::{fmt::Debug, ops::Deref}; + +use super::{ + ExtendedJvmObject, JvmObject, JvmThread, PlainJvmObject, SharedClient, TaggedReferenceType, +}; + +pub type ObjectReference = PlainJvmObject; + +impl ObjectReference {} + +pub type JvmArray = ExtendedJvmObject; + +impl JvmArray { + pub fn length(&self) -> Result { + self.client().get().send(Length::new(self.id())) + } + + pub fn get_values(&self, first_index: u32, length: u32) -> Result { + self.client() + .get() + .send(GetValues::new(self.id(), first_index, length)) + } + + pub fn set_values( + &self, + first_index: u32, + values: &[impl JdwpValue + Debug], + ) -> Result<(), ClientError> { + self.client() + .get() + .send(SetValues::new(self.id(), first_index, values)) + } +} + +pub type JvmString = ExtendedJvmObject; + +impl JvmString { + pub fn value(&self) -> Result { + self.client().get().send(Value::new(self.id())) + } +} + +pub type ThreadGroup = ExtendedJvmObject; + +impl ThreadGroup { + pub fn name(&self) -> Result { + self.client() + .get() + .send(thread_group_reference::Name::new(self.id())) + } + + pub fn parent(&self) -> Result, ClientError> { + let parent_id = self.client().get().send(Parent::new(self.id()))?; + Ok(parent_id.map(|id| ThreadGroup::new(self.client().clone(), id))) + } + + pub fn children(&self) -> Result<(Vec, Vec), ClientError> { + let reply = self.client().get().send(Children::new(self.id()))?; + let groups = reply + .child_groups + .iter() + .map(|id| ThreadGroup::new(self.client().clone(), *id)) + .collect(); + let threads = reply + .child_threads + .iter() + .map(|id| JvmThread::new(self.client().clone(), *id)) + .collect(); + Ok((groups, threads)) + } +} + +pub type ClassLoader = ExtendedJvmObject; + +impl ClassLoader { + pub fn visible_classes(&self) -> Result, ClientError> { + let classes = self.client().get().send(VisibleClasses::new(self.id()))?; + Ok(classes + .into_iter() + .map(|id| TaggedReferenceType::new(self.client().clone(), id)) + .collect()) + } +} + +pub type ClassObject = ExtendedJvmObject; + +impl ClassObject { + pub fn reflected_type(&self) -> Result { + let ref_type = self.client().get().send(ReflectedType::new(self.id()))?; + Ok(TaggedReferenceType::new(self.client().clone(), ref_type)) + } +} + +#[derive(Debug)] +#[repr(u8)] +pub enum TaggedObject { + Array(JvmArray) = Tag::Array as u8, + Object(ObjectReference) = Tag::Object as u8, + String(JvmString) = Tag::String as u8, + Thread(JvmThread) = Tag::Thread as u8, + ThreadGroup(ThreadGroup) = Tag::ThreadGroup as u8, + ClassLoader(ClassLoader) = Tag::ClassLoader as u8, + ClassObject(ClassObject) = Tag::ClassObject as u8, +} + +impl TaggedObject { + pub fn new(client: impl Into, id: TaggedObjectID) -> Self { + use TaggedObject::*; + use TaggedObjectID as ID; + + match id { + ID::Array(id) => Array(JvmArray::new(client, id)), + ID::Object(id) => Object(ObjectReference::new(client, id)), + ID::String(id) => String(JvmString::new(client, id)), + ID::Thread(id) => Thread(JvmThread::new(client, id)), + ID::ThreadGroup(id) => ThreadGroup(super::ThreadGroup::new(client, id)), + ID::ClassLoader(id) => ClassLoader(super::ClassLoader::new(client, id)), + ID::ClassObject(id) => ClassObject(super::ClassObject::new(client, id)), + } + } + + pub fn tag(&self) -> Tag { + // SAFETY: Self and Tag fulfill the requirements + unsafe { crate::spec::tag(self) } + } +} + +impl Deref for TaggedObject { + type Target = ObjectReference; + + fn deref(&self) -> &Self::Target { + use TaggedObject::*; + + match self { + Array(obj) => obj, + Object(obj) => obj, + String(obj) => obj, + Thread(obj) => obj, + ThreadGroup(obj) => obj, + ClassLoader(obj) => obj, + ClassObject(obj) => obj, + } + } +} diff --git a/src/highlevel/reference_type.rs b/src/highlevel/reference_type.rs new file mode 100644 index 0000000..73ba90c --- /dev/null +++ b/src/highlevel/reference_type.rs @@ -0,0 +1,253 @@ +use std::ops::Deref; + +use crate::{ + client::ClientError, + jvm::{FieldModifiers, TypeModifiers}, + spec::{ + reference_type::{ + self, ClassFileVersion, ClassFileVersionReply, ConstantPool, ConstantPoolReply, Fields, + FieldsWithGeneric, InstanceLimit, Instances, Interfaces, Modifiers, NestedTypes, + Signature, SignatureWithGeneric, SignatureWithGenericReply, SourceDebugExtension, + SourceFile, Status, + }, + virtual_machine::InstanceCounts, + ClassStatus, ReferenceTypeID, TaggedReferenceTypeID, TypeTag, + }, +}; + +use super::{ + ArrayType, ClassLoader, ClassObject, ClassType, InterfaceType, JvmObject, JvmReferenceField, + PlainJvmObject, SharedClient, TaggedObject, +}; + +pub type ReferenceType = PlainJvmObject; + +impl ReferenceType { + pub fn signature(&self) -> Result { + self.client().get().send(Signature::new(self.id())) + } + + pub fn class_loader(&self) -> Result, ClientError> { + let id = self + .client() + .get() + .send(reference_type::ClassLoader::new(self.id()))?; + let class_loader = id.map(|id| ClassLoader::new(self.client().clone(), id)); + Ok(class_loader) + } + + pub fn modifiers(&self) -> Result { + self.client().get().send(Modifiers::new(self.id())) + } + + pub fn fields(&self) -> Result, ClientError> { + let fields = self + .client() + .get() + .send(Fields::new(self.id()))? + .into_iter() + .map(|f| StaticField { + object: self.child(f.field_id), + name: f.name, + signature: f.signature, + generic_signature: None, + modifiers: f.mod_bits, + }) + .collect(); + Ok(fields) + } + + // todo: move this to ClassType/InterfaceType, dispatch here maybe? + // pub fn methods(&self) -> Result, ClientError> { + // let methods = self + // .client() + // .get() + // .send(Methods::new(self.id()))? + // .into_iter() + // .map(|m| ReferenceMethod { + // object: self.child(m.method_id), + // name: m.name, + // signature: m.signature, + // generic_signature: None, + // modifiers: m.mod_bits, + // }) + // .collect(); + // Ok(methods) + // } + + pub fn fields_generic(&self) -> Result, ClientError> { + let fields = self + .client() + .get() + .send(FieldsWithGeneric::new(self.id()))? + .into_iter() + .map(|f| StaticField { + object: self.child(f.field_id), + name: f.name, + signature: f.signature, + generic_signature: f.generic_signature, + modifiers: f.mod_bits, + }) + .collect(); + Ok(fields) + } + + pub fn source_file(&self) -> Result { + self.client().get().send(SourceFile::new(self.id())) + } + + pub fn nested_types(&self) -> Result, ClientError> { + let types = self.client().get().send(NestedTypes::new(self.id()))?; + let types = types + .into_iter() + .map(|id| TaggedReferenceType::new(self.client().clone(), id)) + .collect(); + Ok(types) + } + + pub fn status(&self) -> Result { + self.client().get().send(Status::new(self.id())) + } + + pub fn interfaces(&self) -> Result, ClientError> { + let interfaces = self.client().get().send(Interfaces::new(self.id()))?; + let interfaces = interfaces + .into_iter() + .map(|id| InterfaceType::new(self.client().clone(), id)) + .collect(); + Ok(interfaces) + } + + pub fn class(&self) -> Result { + let id = self + .client() + .get() + .send(reference_type::ClassObject::new(self.id()))?; + Ok(ClassObject::new(self.client().clone(), id)) + } + + pub fn source_debug_extension(&self) -> Result { + self.client() + .get() + .send(SourceDebugExtension::new(self.id())) + } + + pub fn signature_with_generic(&self) -> Result { + self.client() + .get() + .send(SignatureWithGeneric::new(self.id())) + } + + pub fn instances(&self, limit: InstanceLimit) -> Result, ClientError> { + let instances = self.client().get().send(Instances::new(self.id(), limit))?; + let instances = instances + .into_iter() + .map(|id| TaggedObject::new(self.client().clone(), id)) + .collect(); + Ok(instances) + } + + pub fn class_file_version(&self) -> Result { + self.client().get().send(ClassFileVersion::new(self.id())) + } + + pub fn constant_pool(&self) -> Result { + self.client().get().send(ConstantPool::new(self.id())) + } +} + +impl ReferenceType { + pub fn instance_count(&self) -> Result { + let [count] = self.client().get().send(InstanceCounts::new([self.id()]))?; + Ok(count) + } + + pub fn field(&self, name: &str) -> Result { + self.fields()? + .into_iter() + .find(|f| f.name == name) + .ok_or_else(|| todo!("High-level errors")) // or just option? + // ergomomics.. + } + + pub fn field_generic(&self, name: &str) -> Result { + self.fields_generic()? + .into_iter() + .find(|f| f.name == name) + .ok_or_else(|| todo!("High-level errors")) + } +} + +#[derive(Debug)] +pub struct StaticField { + pub name: String, + pub signature: String, + pub generic_signature: Option, + pub modifiers: FieldModifiers, + object: JvmReferenceField, +} + +impl Deref for StaticField { + type Target = JvmReferenceField; + + fn deref(&self) -> &Self::Target { + &self.object + } +} + +// #[derive(Debug)] +// pub struct ReferenceMethod { +// pub name: String, +// pub signature: String, +// pub generic_signature: Option, +// pub modifiers: MethodModifiers, +// object: JvmReferenceMethod, +// } + +// impl Deref for ReferenceMethod { +// type Target = JvmReferenceMethod; + +// fn deref(&self) -> &Self::Target { +// &self.object +// } +// } + +#[derive(Debug)] +#[repr(u8)] +pub enum TaggedReferenceType { + Class(ClassType) = TypeTag::Class as u8, + Interface(InterfaceType) = TypeTag::Interface as u8, + Array(ArrayType) = TypeTag::Array as u8, +} + +impl TaggedReferenceType { + pub fn new(client: impl Into, id: TaggedReferenceTypeID) -> Self { + use TaggedReferenceType::*; + use TaggedReferenceTypeID as ID; + + match id { + ID::Class(id) => Class(ClassType::new(client, id)), + ID::Interface(id) => Interface(InterfaceType::new(client, id)), + ID::Array(id) => Array(ArrayType::new(client, id)), + } + } + + pub fn tag(&self) -> TypeTag { + // SAFETY: Self and TypeTag fulfill the requirements + unsafe { crate::spec::tag(self) } + } +} + +impl Deref for TaggedReferenceType { + type Target = ReferenceType; + + fn deref(&self) -> &Self::Target { + use TaggedReferenceType::*; + + match self { + Class(ref_type) => ref_type, + Interface(ref_type) => ref_type, + Array(ref_type) => ref_type, + } + } +} diff --git a/src/highlevel/thread.rs b/src/highlevel/thread.rs new file mode 100644 index 0000000..e791e71 --- /dev/null +++ b/src/highlevel/thread.rs @@ -0,0 +1,44 @@ +use super::{ExtendedJvmObject, JvmObject, ThreadGroup}; +use crate::{ + client::ClientError, + spec::{ + thread_reference::{self, FrameCount, Name, Resume, Status, Suspend, SuspendCount}, + SuspendStatus, ThreadID, ThreadStatus, + }, +}; + +pub type JvmThread = ExtendedJvmObject; + +impl JvmThread { + pub fn name(&self) -> Result { + self.client().get().send(Name::new(self.id())) + } + + pub fn suspend(&self) -> Result<(), ClientError> { + self.client().get().send(Suspend::new(self.id())) + } + + pub fn resume(&self) -> Result<(), ClientError> { + self.client().get().send(Resume::new(self.id())) + } + + pub fn status(&self) -> Result<(ThreadStatus, SuspendStatus), ClientError> { + self.client().get().send(Status::new(self.id())) + } + + pub fn group(&self) -> Result { + let id = self + .client() + .get() + .send(thread_reference::ThreadGroup::new(self.id()))?; + Ok(ThreadGroup::new(self.client().clone(), id)) + } + + pub fn frame_count(&self) -> Result { + self.client().get().send(FrameCount::new(self.id())) + } + + pub fn suspend_count(&self) -> Result { + self.client().get().send(SuspendCount::new(self.id())) + } +} diff --git a/src/highlevel/vm.rs b/src/highlevel/vm.rs new file mode 100644 index 0000000..803cefe --- /dev/null +++ b/src/highlevel/vm.rs @@ -0,0 +1,225 @@ +use std::{fmt::Debug, net::ToSocketAddrs}; + +use super::{ + JvmEvent, JvmString, JvmThread, PlainJvmObject, SharedClient, TaggedReferenceType, ThreadGroup, +}; +use crate::{ + client::{ClientError, JdwpClient}, + codec::{JdwpReadable, JdwpWritable}, + functional::Coll, + spec::{ + virtual_machine::{ + self, AllClasses, AllClassesWithGeneric, CapabilitiesNewReply, CapabilitiesReply, + ClassBySignature, ClassPathsReply, ClassesBySignature, IDSizeInfo, InstanceCounts, + Version, VersionReply, + }, + ClassStatus, Command, JdwpId, ReferenceTypeID, SuspendPolicy, + }, +}; + +#[derive(Debug)] +pub struct VM { + client: SharedClient, +} + +impl From for VM { + fn from(client: JdwpClient) -> Self { + Self::new(client) + } +} + +impl VM { + pub fn new(client: impl Into) -> Self { + Self { + client: client.into(), + } + } + + pub fn connect(addr: impl ToSocketAddrs) -> Result { + Ok(Self::new(JdwpClient::connect(addr)?)) + } + + pub fn child(&self, id: I) -> PlainJvmObject { + PlainJvmObject::new(self.client.clone(), id) + } + + pub fn client(&self) -> &SharedClient { + &self.client + } +} + +impl VM { + pub fn version(&self) -> Result { + self.client.get().send(Version) + } + + pub fn class_by_signature( + &self, + signature: &str, + ) -> Result<(TaggedReferenceType, ClassStatus), ClientError> { + let (id, status) = *self.client.get().send(ClassBySignature::new(signature))?; + Ok((TaggedReferenceType::new(self.client.clone(), id), status)) + } + + pub fn classes_by_signature( + &self, + signature: &str, + ) -> Result, ClientError> { + let classes = self.client.get().send(ClassesBySignature::new(signature))?; + let classes = classes + .into_iter() + .map(|(id, status)| (TaggedReferenceType::new(self.client.clone(), id), status)) + .collect(); + Ok(classes) + } + + pub fn all_classes(&self) -> Result, ClientError> { + let classes = self.client.get().send(AllClasses)?; + let classes = classes + .into_iter() + .map(|class| Class { + object: TaggedReferenceType::new(self.client.clone(), class.type_id), + signature: class.signature, + generic_signature: None, + status: class.status, + }) + .collect(); + Ok(classes) + } + + pub fn all_threads(&self) -> Result, ClientError> { + let reply = self.client.get().send(virtual_machine::AllThreads)?; + Ok(reply + .iter() + .map(|id| JvmThread::new(self.client.clone(), *id)) + .collect()) + } + + pub fn top_level_thread_groups(&self) -> Result, ClientError> { + let reply = self + .client + .get() + .send(virtual_machine::TopLevelThreadGroups)?; + Ok(reply + .iter() + .map(|id| ThreadGroup::new(self.client.clone(), *id)) + .collect()) + } + + pub fn dispose(self) -> Result<(), ClientError> { + self.client.get().send(virtual_machine::Dispose) + } + + pub fn id_sizes(&self) -> Result { + self.client.get().send(virtual_machine::IDSizes) + } + + pub fn suspend(&self) -> Result<(), ClientError> { + self.client.get().send(virtual_machine::Suspend) + } + + pub fn resume(&self) -> Result<(), ClientError> { + self.client.get().send(virtual_machine::Resume) + } + + pub fn exit(self, exit_code: i32) -> Result<(), ClientError> { + self.client + .get() + .send(virtual_machine::Exit::new(exit_code)) + } + + pub fn create_string(&self, value: &str) -> Result { + let id = self + .client + .get() + .send(virtual_machine::CreateString::new(value))?; + Ok(JvmString::new(self.client.clone(), id)) + } + + pub fn capabilities(&self) -> Result { + self.client.get().send(virtual_machine::Capabilities) + } + + pub fn classpaths(&self) -> Result { + self.client.get().send(virtual_machine::ClassPaths) + } + + pub fn hold_events(&self) -> Result<(), ClientError> { + self.client.get().send(virtual_machine::HoldEvents) + } + + pub fn release_events(&self) -> Result<(), ClientError> { + self.client.get().send(virtual_machine::ReleaseEvents) + } + + pub fn capabilities_new(&self) -> Result { + self.client.get().send(virtual_machine::CapabilitiesNew) + } + + pub fn set_default_stratum(&self, stratum: &str) -> Result<(), ClientError> { + self.client + .get() + .send(virtual_machine::SetDefaultStratum::new(stratum)) + } + + pub fn all_classes_with_generic(&self) -> Result, ClientError> { + let classes = self.client.get().send(AllClassesWithGeneric)?; + let classes = classes + .into_iter() + .map(|class| Class { + object: TaggedReferenceType::new(self.client.clone(), class.type_id), + signature: class.signature, + generic_signature: class.generic_signature, + status: class.status, + }) + .collect(); + Ok(classes) + } + + // oof wtf are those bounds + pub fn instance_counts>( + &self, + ref_types: C, + ) -> Result, ClientError> + where + InstanceCounts: JdwpWritable + Debug, + as Command>::Output: JdwpReadable + Debug, + { + self.client.get().send(InstanceCounts::new(ref_types)) + } +} + +impl VM { + pub fn main_thread(&self) -> Result { + for thread in self.all_threads()? { + if thread.name()? == "main" { + return Ok(thread); + } + } + todo!("high level errors") + } + + pub fn receive_events(&self) -> impl Iterator)> { + let cloned = self.client.clone(); // avoid return depending on the lifetime of &self + self.client + .get() + .receive_events() + .into_iter() + .map(move |composite| { + let events = composite + .events + .into_iter() + .map(|event| JvmEvent::new(event, cloned.clone())) + .collect(); + (composite.suspend_policy, events) + }) + } +} + +#[derive(Debug)] +pub struct Class { + pub object: TaggedReferenceType, + pub signature: String, + pub generic_signature: Option, + pub status: ClassStatus, +} diff --git a/src/jvm.rs b/src/jvm.rs index 23d6708..d93b682 100644 --- a/src/jvm.rs +++ b/src/jvm.rs @@ -1,12 +1,13 @@ -use crate::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}; +use crate::codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}; use bitflags::bitflags; use std::{ + borrow::Cow, io::{self, Read, Write}, rc::Rc, }; use thiserror::Error; -use byteorder::{BigEndian, ReadBytesExt}; +use byteorder::{ReadBytesExt, BE}; /// This represents an item in the constant pool table. #[repr(u8)] @@ -108,68 +109,77 @@ impl ConstantPoolItem { let tag = read.read_u8()?; let item = match tag { 1 => { - let length = read.read_u16::()?; + let length = read.read_u16::()?; let mut bytes = vec![0; length as usize]; read.read_exact(&mut bytes)?; - let string = cesu8::from_java_cesu8(&bytes) + let cow = cesu8::from_java_cesu8(&bytes) .map_err(|_| ConstantPoolParsingError::BadUtf8 { index })?; - ConstantPoolItem::Utf8(string.into_owned()) + + ConstantPoolItem::Utf8(match cow { + Cow::Borrowed(_) => { + // SAFETY: from_cesu8 only returns borrowed if input was valid utf-8 + // so we just reinterpret to avoid the clone in Cow::into_owned + // Maybe I should make a PR to add this to cesu8? + unsafe { String::from_utf8_unchecked(bytes) } + } + Cow::Owned(s) => s, + }) } - 3 => ConstantPoolItem::Integer(read.read_i32::()?), - 4 => ConstantPoolItem::Float(read.read_f32::()?), - 5 => ConstantPoolItem::Long(read.read_i64::()?), - 6 => ConstantPoolItem::Double(read.read_f64::()?), - 7 => ConstantPoolItem::Class(read.read_u16::()?), - 8 => ConstantPoolItem::String(read.read_u16::()?), + 3 => ConstantPoolItem::Integer(read.read_i32::()?), + 4 => ConstantPoolItem::Float(read.read_f32::()?), + 5 => ConstantPoolItem::Long(read.read_i64::()?), + 6 => ConstantPoolItem::Double(read.read_f64::()?), + 7 => ConstantPoolItem::Class(read.read_u16::()?), + 8 => ConstantPoolItem::String(read.read_u16::()?), 9 => ConstantPoolItem::Fieldref { - class_index: read.read_u16::()?, - name_and_type_index: read.read_u16::()?, + class_index: read.read_u16::()?, + name_and_type_index: read.read_u16::()?, }, 10 => ConstantPoolItem::Methodref { - class_index: read.read_u16::()?, - name_and_type_index: read.read_u16::()?, + class_index: read.read_u16::()?, + name_and_type_index: read.read_u16::()?, }, 11 => ConstantPoolItem::InterfaceMethodref { - class_index: read.read_u16::()?, - name_and_type_index: read.read_u16::()?, + class_index: read.read_u16::()?, + name_and_type_index: read.read_u16::()?, }, 12 => ConstantPoolItem::NameAndType { - name_index: read.read_u16::()?, - descriptor_index: read.read_u16::()?, + name_index: read.read_u16::()?, + descriptor_index: read.read_u16::()?, }, 15 => { use ReferenceKind::*; - let kind = read.read_u8()?; - let reference_kind = match kind { - 1 => GetField, - 2 => GetStatic, - 3 => PutField, - 4 => PutStatic, - 5 => InvokeVirtual, - 6 => InvokeStatic, - 7 => InvokeSpecial, - 8 => NewInvokeSpecial, - 9 => InvokeInterface, - _ => return Err(ConstantPoolParsingError::BadReferenceKind { kind, index }), - }; ConstantPoolItem::MethodHandle { - reference_kind, - reference_index: read.read_u16::()?, + reference_kind: match read.read_u8()? { + 1 => GetField, + 2 => GetStatic, + 3 => PutField, + 4 => PutStatic, + 5 => InvokeVirtual, + 6 => InvokeStatic, + 7 => InvokeSpecial, + 8 => NewInvokeSpecial, + 9 => InvokeInterface, + kind => { + return Err(ConstantPoolParsingError::BadReferenceKind { kind, index }) + } + }, + reference_index: read.read_u16::()?, } } - 16 => ConstantPoolItem::MethodType(read.read_u16::()?), + 16 => ConstantPoolItem::MethodType(read.read_u16::()?), 17 => ConstantPoolItem::Dynamic { - bootstrap_method_attr_index: read.read_u16::()?, - name_and_type_index: read.read_u16::()?, + bootstrap_method_attr_index: read.read_u16::()?, + name_and_type_index: read.read_u16::()?, }, 18 => ConstantPoolItem::InvokeDynamic { - bootstrap_method_attr_index: read.read_u16::()?, - name_and_type_index: read.read_u16::()?, + bootstrap_method_attr_index: read.read_u16::()?, + name_and_type_index: read.read_u16::()?, }, - 19 => ConstantPoolItem::Module(read.read_u16::()?), - 20 => ConstantPoolItem::Package(read.read_u16::()?), + 19 => ConstantPoolItem::Module(read.read_u16::()?), + 20 => ConstantPoolItem::Package(read.read_u16::()?), _ => return Err(ConstantPoolParsingError::BadTag { tag, index }), }; Ok(item) diff --git a/src/lib.rs b/src/lib.rs index 0ba64a1..3557a43 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,59 +2,13 @@ extern crate self as jdwp; -use std::fmt::{Display, Formatter}; - -use crate::{ - codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, - enums::ErrorCode, -}; - pub mod client; pub mod codec; -pub mod commands; -pub mod enums; -pub mod event_modifier; +pub mod highlevel; pub mod jvm; -pub mod types; +pub mod spec; mod functional; mod xorshift; -#[derive(Copy, Clone, Debug, PartialEq, Eq, JdwpReadable, JdwpWritable)] -pub struct CommandId { - command_set: u8, - command: u8, -} - -impl CommandId { - pub(crate) const fn new(command_set: u8, command: u8) -> CommandId { - CommandId { - command_set, - command, - } - } -} - -impl Display for CommandId { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}.{}", self.command_set, self.command) - } -} - -#[derive(Debug, Copy, Clone, JdwpReadable, JdwpWritable)] -#[repr(u8)] -enum PacketMeta { - Command(CommandId) = 0x00, - Reply(ErrorCode) = 0x80, -} - -#[derive(Debug, Copy, Clone, JdwpReadable, JdwpWritable)] -pub struct PacketHeader { - length: u32, - id: u32, - meta: PacketMeta, -} - -impl PacketHeader { - pub(crate) const JDWP_SIZE: usize = 11; -} +pub(crate) use jdwp_macros::jdwp_command; diff --git a/src/spec/commands.rs b/src/spec/commands.rs new file mode 100644 index 0000000..3cdb319 --- /dev/null +++ b/src/spec/commands.rs @@ -0,0 +1,2828 @@ +use std::{ + fmt, + fmt::Debug, + io::{self, Read}, + marker::PhantomData, + ops::Deref, +}; + +use crate::{ + codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, + functional::{Coll, Single}, + jdwp_command, + jvm::{FieldModifiers, MethodModifiers, TypeModifiers}, + spec::*, +}; + +/// VirtualMachine Command Set (1) +pub mod virtual_machine { + use super::*; + + /// Returns the JDWP version implemented by the target VM. + /// + /// The version string format is implementation dependent. + #[jdwp_command(1, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Version; + + #[derive(Debug, JdwpReadable)] + pub struct VersionReply { + /// Text information on the VM version + pub description: String, + /// Major JDWP Version number + pub version_major: u32, + /// Minor JDWP Version number + pub version_minor: u32, + /// Target VM JRE version, as in the java.version property + pub vm_version: String, + /// Target VM name, as in the java.vm.name property + pub vm_name: String, + } + + /// Returns reference types for all the classes loaded by the target VM + /// which match the given signature. + /// + /// Multiple reference types will be returned if two or more class loaders + /// have loaded a class of the same name. + /// + /// The search is confined to loaded classes only; no attempt is made to + /// load a class of the given signature. + #[jdwp_command(C, 1, 2)] + #[derive(Clone, JdwpWritable)] + pub struct ClassesBySignatureGeneric<'a, C: Coll> { + /// JNI signature of the class to find (for example, + /// "Ljava/lang/String;") + signature: &'a str, + _phantom: PhantomData, + } + + /// This is needed because inference cannot guess what you need since there + /// are no parameters + /// And the Single helper type is in a private jdwp module + pub type ClassBySignature<'a> = + ClassesBySignatureGeneric<'a, Single<(TaggedReferenceTypeID, ClassStatus)>>; + + impl<'a> Debug for ClassBySignature<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClassBySignature") + .field("signature", &self.signature) + .finish() + } + } + + /// The inference is able to figure out N by the destructuring pattern + pub type ClassesBySignatureStatic<'a, const N: usize> = + ClassesBySignatureGeneric<'a, [(TaggedReferenceTypeID, ClassStatus); N]>; + + impl<'a, const N: usize> Debug for ClassesBySignatureStatic<'a, N> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct(&format!("ClassesBySignatureStatic<{}>", N)) // hope this const-optimizes?. + .field("signature", &self.signature) + .finish() + } + } + + /// The 'standard' variant with a vector + pub type ClassesBySignature<'a> = + ClassesBySignatureGeneric<'a, Vec<(TaggedReferenceTypeID, ClassStatus)>>; + + impl<'a> Debug for ClassesBySignature<'a> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ClassesBySignature") + .field("signature", &self.signature) + .finish() + } + } + + /// Returns reference types for all classes currently loaded by the target + /// VM. + #[jdwp_command(Vec, 1, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct AllClasses; + + #[derive(Debug, JdwpReadable)] + pub struct Class { + /// Matching loaded reference type + pub type_id: TaggedReferenceTypeID, + /// The JNI signature of the loaded reference type + pub signature: String, + /// The current class status + pub status: ClassStatus, + } + + /// Returns all threads currently running in the target VM. + /// + /// The returned list contains threads created through java.lang.Thread, all + /// native threads attached to the target VM through JNI, and system threads + /// created by the target VM. + /// + /// Threads that have not yet been started and threads that have completed + /// their execution are not included in the returned list. + #[jdwp_command(Vec, 1, 4)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct AllThreads; + + /// Returns all thread groups that do not have a parent. This command may be + /// used as the first step in building a tree (or trees) of the existing + /// thread groups. + #[jdwp_command(Vec, 1, 5)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct TopLevelThreadGroups; + + /// Invalidates this virtual machine mirror. + /// + /// The communication channel to the target VM is closed, and the target VM + /// prepares to accept another subsequent connection from this debugger or + /// another debugger, including the following tasks: + /// - All event requests are cancelled. + /// - All threads suspended by the thread-level resume command or the + /// VM-level + /// resume command are resumed as many times as necessary for them to run. + /// - Garbage collection is re-enabled in all cases where it was disabled + /// + /// Any current method invocations executing in the target VM are continued + /// after the disconnection. Upon completion of any such method invocation, + /// the invoking thread continues from the location where it was originally + /// stopped. + /// + /// Resources originating in this VirtualMachine (ObjectReferences, + /// ReferenceTypes, etc.) will become invalid. + #[jdwp_command((), 1, 6)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Dispose; + + /// Returns the sizes of variably-sized data types in the target VM. + /// + /// The returned values indicate the number of bytes used by the identifiers + /// in command and reply packets. + #[jdwp_command(IDSizeInfo, 1, 7)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct IDSizes; + + #[derive(Debug, Clone, JdwpReadable)] + pub struct IDSizeInfo { + /// field_id size in bytes + pub field_id_size: u32, + /// method_id size in bytes + pub method_id_size: u32, + /// object_id size in bytes + pub object_id_size: u32, + /// reference_type_id size in bytes + pub reference_type_id_size: u32, + /// frame_id size in bytes + pub frame_id_size: u32, + } + + impl Default for IDSizeInfo { + fn default() -> Self { + Self { + field_id_size: 8, + method_id_size: 8, + object_id_size: 8, + reference_type_id_size: 8, + frame_id_size: 8, + } + } + } + + /// Suspends the execution of the application running in the target VM. + /// All Java threads currently running will be suspended. + /// + /// Unlike java.lang.Thread.suspend, suspends of both the virtual machine + /// and individual threads are counted. Before a thread will run again, + /// it must be resumed through the VM-level resume command or the + /// thread-level resume command the same number of times it has been + /// suspended. + #[jdwp_command((), 1, 8)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Suspend; + + /// Resumes execution of the application after the suspend command or an + /// event has stopped it. + /// + /// Suspensions of the Virtual Machine and individual threads are counted. + /// + /// If a particular thread is suspended n times, it must resumed n times + /// before it will continue. + #[jdwp_command((), 1, 9)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Resume; + + /// Terminates the target VM with the given exit code. + /// + /// On some platforms, the exit code might be truncated, for example, to the + /// low order 8 bits. + /// + /// All ids previously returned from the target VM become invalid. + /// + /// Threads running in the VM are abruptly terminated. + /// + /// A thread death exception is not thrown and finally blocks are not run. + #[jdwp_command((), 1, 10)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Exit { + exit_code: i32, + } + + /// Creates a new string object in the target VM and returns its id. + #[jdwp_command(StringID, 1, 11)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct CreateString<'a> { + /// UTF-8 characters to use in the created string + string: &'a str, + } + + /// Retrieve this VM's capabilities. + /// + /// The capabilities are returned as booleans, each indicating the presence + /// or absence of a capability. + /// + /// The commands associated with each capability will return the + /// NOT_IMPLEMENTED error if the capability is not available. + #[jdwp_command(1, 12)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Capabilities; + + #[derive(Debug, JdwpReadable)] + pub struct CapabilitiesReply { + /// Can the VM watch field modification, and therefore can it send the + /// Modification Watchpoint Event? + pub can_watch_field_modification: bool, + /// Can the VM watch field access, and therefore can it send the + /// Access Watchpoint Event? + pub can_watch_field_access: bool, + /// Can the VM get the bytecodes of a given method? + pub can_get_bytecodes: bool, + /// Can the VM determine whether a field or method is synthetic? + /// (that is, can the VM determine if the method or the field was + /// invented by the compiler?) + pub can_get_synthetic_attribute: bool, + /// Can the VM get the owned monitors information for a thread? + pub can_get_owned_monitor_info: bool, + /// Can the VM get the current contended monitor of a thread? + pub can_get_current_contended_monitor: bool, + /// Can the VM get the monitor information for a given object? + pub can_get_monitor_info: bool, + } + + /// Retrieve the classpath and bootclasspath of the target VM. + /// + /// If the classpath is not defined, returns an empty list. + /// + /// If the bootclasspath is not defined returns an empty list. + #[jdwp_command(1, 13)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ClassPaths; + + #[derive(Debug, JdwpReadable)] + pub struct ClassPathsReply { + /// Base directory used to resolve relative paths in either of the + /// following lists. + pub base_dir: String, + /// Components of the classpath + pub classpaths: Vec, + /// Components of the bootclasspath + pub bootclasspaths: Vec, + } + + /// Releases a list of object IDs. + /// + /// For each object in the list, the following applies. + /// + /// The count of references held by the back-end (the reference count) will + /// be decremented by ref_cnt. + /// + /// If thereafter the reference count is less than or equal to zero, the ID + /// is freed. + /// + /// Any back-end resources associated with the freed ID may be freed, and if + /// garbage collection was disabled for the object, it will be re-enabled. + /// + /// The sender of this command promises that no further commands will be + /// sent referencing a freed ID. + /// + /// Use of this command is not required. + /// + /// If it is not sent, resources associated with each ID will be freed by + /// the back-end at some time after the corresponding object is garbage + /// collected. + /// + /// It is most useful to use this command to reduce the load on the back-end + /// if a very large number of objects has been retrieved from the + /// back-end (a large array, for example) but may not be garbage + /// collected any time soon. + /// + /// IDs may be re-used by the back-end after they have been freed with this + /// command. + /// + /// This description assumes reference counting, a back-end may use any + /// implementation which operates equivalently. + #[jdwp_command((), 1, 14)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct DisposeObjects<'a> { + requests: &'a [(ObjectID, u32)], + } + + /// Tells the target VM to stop sending events. Events are not discarded; + /// they are held until a subsequent ReleaseEvents command is sent. + /// + /// This command is useful to control the number of events sent to the + /// debugger VM in situations where very large numbers of events are + /// generated. + /// + /// While events are held by the debugger back-end, application execution + /// may be frozen by the debugger back-end to prevent buffer overflows + /// on the back end. + /// + /// Responses to commands are never held and are not affected by this + /// command. If events are already being held, this command is ignored. + #[jdwp_command((), 1, 15)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct HoldEvents; + + /// Tells the target VM to continue sending events. + /// + /// This command is used to restore normal activity after a HoldEvents + /// command. + /// + /// If there is no current HoldEvents command in effect, this command is + /// ignored. + #[jdwp_command((), 1, 16)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ReleaseEvents; + + /// Retrieve all of this VM's capabilities. + /// + /// The capabilities are returned as booleans, each indicating the presence + /// or absence of a capability. + /// + /// The commands associated with each capability will return the + /// NOT_IMPLEMENTED error if the capability is not available. + /// + /// Since JDWP version 1.4. + #[jdwp_command(1, 17)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct CapabilitiesNew; + + #[derive(JdwpReadable)] + pub struct CapabilitiesNewReply { + /// The prefix of [CapabilitiesNew] is identical to that of old + /// [Capabilities] + pub capabilities: CapabilitiesReply, + /// Can the VM redefine classes? + pub can_redefine_classes: bool, + /// Can the VM add methods when redefining classes? + pub can_add_method: bool, + /// Can the VM redefine classes in arbitrary ways? + pub can_unrestrictedly_redefine_classes: bool, + /// Can the VM pop stack frames? + pub can_pop_frames: bool, + /// Can the VM filter events by specific object? + pub can_use_instance_filters: bool, + /// Can the VM get the source debug extension? + pub can_get_source_debug_extension: bool, + /// Can the VM request VM death events? + pub can_request_vmdeath_event: bool, + /// Can the VM set a default stratum? + pub can_set_default_stratum: bool, + /// Can the VM return instances, counts of instances of classes and + /// referring objects? + pub can_get_instance_info: bool, + /// Can the VM request monitor events? + pub can_request_monitor_events: bool, + /// Can the VM get monitors with frame depth info? + pub can_get_monitor_frame_info: bool, + /// Can the VM filter class prepare events by source name? + pub can_use_source_name_filters: bool, + /// Can the VM return the constant pool information? + pub can_get_constant_pool: bool, + /// Can the VM force early return from a method? + pub can_force_early_return: bool, + /// Reserved for future capability + _reserved_22: bool, + /// Reserved for future capability + _reserved_23: bool, + /// Reserved for future capability + _reserved_24: bool, + /// Reserved for future capability + _reserved_25: bool, + /// Reserved for future capability + _reserved_26: bool, + /// Reserved for future capability + _reserved_27: bool, + /// Reserved for future capability + _reserved_28: bool, + /// Reserved for future capability + _reserved_29: bool, + /// Reserved for future capability + _reserved_30: bool, + /// Reserved for future capability + _reserved_31: bool, + /// Reserved for future capability + _reserved_32: bool, + } + + // skip reserved fields from Debug + impl Debug for CapabilitiesNewReply { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("CapabilitiesNewReply") + .field("capabilities", &self.capabilities) + .field("can_redefine_classes", &self.can_redefine_classes) + .field("can_add_method", &self.can_add_method) + .field( + "can_unrestrictedly_redefine_classes", + &self.can_unrestrictedly_redefine_classes, + ) + .field("can_pop_frames", &self.can_pop_frames) + .field("can_use_instance_filters", &self.can_use_instance_filters) + .field( + "can_get_source_debug_extension", + &self.can_get_source_debug_extension, + ) + .field("can_request_vmdeath_event", &self.can_request_vmdeath_event) + .field("can_set_default_stratum", &self.can_set_default_stratum) + .field("can_get_instance_info", &self.can_get_instance_info) + .field( + "can_request_monitor_events", + &self.can_request_monitor_events, + ) + .field( + "can_get_monitor_frame_info", + &self.can_get_monitor_frame_info, + ) + .field( + "can_use_source_name_filters", + &self.can_use_source_name_filters, + ) + .field("can_get_constant_pool", &self.can_get_constant_pool) + .field("can_force_early_return", &self.can_force_early_return) + .finish() + } + } + + /// Installs new class definitions. + /// + /// If there are active stack frames in methods of the redefined classes in + /// the target VM then those active frames continue to run the bytecodes + /// of the original method. These methods are considered obsolete - see + /// [IsObsolete](super::method::IsObsolete). + /// + /// The methods in the redefined classes will be used for new invokes in the + /// target VM. The original method ID refers to the redefined method. + /// + /// All breakpoints in the redefined classes are cleared. + /// + /// If resetting of stack frames is desired, the PopFrames command can be + /// used to pop frames with obsolete methods. + /// + /// Requires `can_redefine_classes` capability - see [CapabilitiesNew]. + /// + /// In addition to the `can_redefine_classes` capability, the target VM must + /// have the `can_add_method` capability to add methods when redefining + /// classes, or the `can_unrestrictedly_redefine_classes` to redefine + /// classes in arbitrary ways. + #[jdwp_command((), 1, 18)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct RedefineClasses<'a> { + classes: &'a [(ReferenceTypeID, Vec)], + } + + /// Set the default stratum. Requires `can_set_default_stratum` capability - + /// see [CapabilitiesNew]. + #[jdwp_command((), 1, 19)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SetDefaultStratum<'a> { + /// default stratum, or empty string to use reference type default. + stratum_id: &'a str, + } + + /// Returns reference types for all classes currently loaded by the target + /// VM. + /// + /// Both the JNI signature and the generic signature are returned for each + /// class. + /// + /// Generic signatures are described in the signature attribute section in + /// The Java™ Virtual Machine Specification. + /// + /// Since JDWP version 1.5. + #[jdwp_command(Vec, 1, 20)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct AllClassesWithGeneric; + + #[derive(Debug, JdwpReadable)] + pub struct GenericClass { + /// Loaded reference type + pub type_id: TaggedReferenceTypeID, + /// The JNI signature of the loaded reference type + pub signature: String, + /// The generic signature of the loaded reference type if there is any. + pub generic_signature: Option, + /// The current class status + pub status: ClassStatus, + } + + /// Returns the number of instances of each reference type in the input + /// list. + /// + /// Only instances that are reachable for the purposes of garbage collection + /// are counted. + /// + /// If a reference type is invalid, eg. it has been unloaded, zero is + /// returned for its instance count. + /// + /// Since JDWP version 1.6. Requires canGetInstanceInfo capability - see + /// [CapabilitiesNew]. + #[jdwp_command(C::Map, 1, 21)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct InstanceCounts> { + /// A list of reference type IDs. + ref_types: C, + } +} + +/// ReferenceType Command Set (2) +pub mod reference_type { + use std::num::NonZeroU32; + + use super::*; + + /// Returns the JNI signature of a reference type. + /// + /// JNI signature formats are described in the Java Native Interface + /// Specification. + /// + /// For primitive classes the returned signature is the signature of the + /// corresponding primitive type; for example, "I" is returned as the + /// signature of the class represented by `java.lang.Integer.TYPE`. + #[jdwp_command(String, 2, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Signature { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the instance of `java.lang.ClassLoader` which loaded a given + /// reference type. + /// + /// If the reference type was loaded by the system class loader, the + /// returned object ID is null. + #[jdwp_command(Option, 2, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ClassLoader { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the modifiers (also known as access flags) for a reference type. + /// + /// The returned bit mask contains information on the declaration of the + /// reference type. + /// + /// If the reference type is an array or a primitive class (for example, + /// `java.lang.Integer.TYPE`), the value of the returned bit mask is + /// undefined. + #[jdwp_command(TypeModifiers, 2, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Modifiers { + ref_type: ReferenceTypeID, + } + + /// Returns information for each field in a reference type. + /// + /// Inherited fields are not included. + /// + /// The field list will include any synthetic fields created by the + /// compiler. + /// + /// Fields are returned in the order they occur in the class file. + #[jdwp_command(Vec, 2, 4)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Fields { + ref_type: ReferenceTypeID, + } + + #[derive(Debug, JdwpReadable)] + pub struct Field { + /// Field ID + pub field_id: FieldID, + /// Name of field + pub name: String, + /// JNI Signature of field. + pub signature: String, + /// The modifier bit flags (also known as access flags) which provide + /// additional information on the field declaration. + /// + /// Individual flag values are defined in Chapter 4 of The Java™ Virtual + /// Machine Specification. + /// + /// In addition, the 0xf0000000 bit identifies the field as synthetic, + /// if the synthetic attribute capability is available. + pub mod_bits: FieldModifiers, + } + + /// Returns information for each method in a reference type. + /// + /// Inherited methods are not included. + /// + /// The list of methods will include constructors (identified with the name + /// "<init>"), the initialization method (identified with the name + /// "<clinit>") if present, and any synthetic methods created by the + /// compiler. + /// + /// Methods are returned in the order they occur in the class file. + #[jdwp_command(Vec, 2, 5)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Methods { + ref_type: ReferenceTypeID, + } + + #[derive(Debug, JdwpReadable)] + pub struct Method { + /// Method ID + pub method_id: MethodID, + /// Name of method + pub name: String, + /// JNI Signature of method. + pub signature: String, + /// The modifier bit flags (also known as access flags) which provide + /// additional information on the method declaration. + /// + /// Individual flag values are defined in Chapter 4 of The Java™ Virtual + /// Machine Specification. + /// + /// In addition, The 0xf0000000 bit identifies the method as synthetic, + /// if the synthetic attribute capability is available. + pub mod_bits: MethodModifiers, + } + + /// Returns the value of one or more static fields of the reference type. + /// + /// Each field must be member of the reference type or one of its + /// superclasses, superinterfaces, or implemented interfaces. + /// + /// Access control is not enforced; for example, the values of private + /// fields can be obtained. + #[jdwp_command(C::Map, 2, 6)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct GetValues> { + /// The reference type ID + pub ref_type: ReferenceTypeID, + /// Field IDs of fields to get + pub fields: C, + } + + /// Returns the source file name in which a reference type was declared. + #[jdwp_command(String, 2, 7)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SourceFile { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the classes and interfaces directly nested within this type. + /// Types further nested within those types are not included. + #[jdwp_command(Vec, 2, 8)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct NestedTypes { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the current status of the reference type. + /// + /// The status indicates the extent to which the reference type has been + /// initialized, as described in section 2.1.6 of The Java™ Virtual Machine + /// Specification. + /// + /// If the class is linked the PREPARED and VERIFIED bits in the returned + /// status bits will be set. + /// + /// If the class is initialized the INITIALIZED bit in the returned status + /// bits will be set. + /// + /// If an error occurred during initialization then the ERROR bit in the + /// returned status bits will be set. + /// + /// The returned status bits are undefined for array types and for primitive + /// classes (such as java.lang.Integer.TYPE). + #[jdwp_command(ClassStatus, 2, 9)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Status { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the interfaces declared as implemented by this class. + /// + /// Interfaces indirectly implemented (extended by the implemented interface + /// or implemented by a superclass) are not included. + #[jdwp_command(Vec, 2, 10)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Interfaces { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the class object corresponding to this type. + #[jdwp_command(ClassObjectID, 2, 11)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ClassObject { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the value of the SourceDebugExtension attribute. + /// + /// Since JDWP version 1.4. Requires canGetSourceDebugExtension capability - + /// see [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command(String, 2, 12)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SourceDebugExtension { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + /// Returns the JNI signature of a reference type along with the generic + /// signature if there is one. + /// + /// Generic signatures are described in the signature attribute section in + /// The Java™ Virtual Machine Specification. + /// + /// Since JDWP version 1.5. + #[jdwp_command(2, 13)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SignatureWithGeneric { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + #[derive(Debug, JdwpReadable)] + pub struct SignatureWithGenericReply { + /// The JNI signature for the reference type. + pub signature: String, + /// The generic signature for the reference type or an empty string if + /// there is none. + pub generic_signature: String, + } + + /// Returns information, including the generic signature if any, for each + /// field in a reference type. + /// + /// Inherited fields are not included. + /// + /// The field list will include any synthetic fields created by the + /// compiler. + /// + /// Fields are returned in the order they occur in the class file. + /// + /// Generic signatures are described in the signature attribute section in + /// The Java™ Virtual Machine Specification. + /// + /// Since JDWP version 1.5. + + #[jdwp_command(Vec, 2, 14)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct FieldsWithGeneric { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + #[derive(Debug, JdwpReadable)] + pub struct FieldWithGeneric { + /// The field ID + pub field_id: FieldID, + /// The name of the field + pub name: String, + /// The JNI signature of the field + pub signature: String, + /// The generic signature of the field if there is one + pub generic_signature: Option, + /// The modifier bit flags (also known as access flags) which provide + /// additional information on the field declaration. + /// + /// Individual flag values are defined in Chapter 4 of The Java™ Virtual + /// Machine Specification. + /// + /// In addition, the 0xf0000000 bit identifies the field as synthetic, + /// if the synthetic attribute capability is available. + pub mod_bits: FieldModifiers, + } + + /// Returns information, including the generic signature if any, for each + /// method in a reference type. Inherited methodss are not included. + /// The list of methods will include constructors (identified with the name + /// "<init>"), the initialization method (identified with the name + /// "<clinit>") if present, and any synthetic methods created by the + /// compiler. Methods are returned in the order they occur in the class + /// file. + /// + /// Generic signatures are described in the signature attribute section in + /// The Java™ Virtual Machine Specification. + /// + /// Since JDWP version 1.5. + #[jdwp_command(Vec, 2, 15)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct MethodsWithGeneric { + /// The reference type ID + ref_type: ReferenceTypeID, + } + + #[derive(Debug, JdwpReadable)] + pub struct MethodWithGeneric { + /// The method ID + pub method_id: MethodID, + /// The name of the method + pub name: String, + /// The JNI signature of the method + pub signature: String, + /// The generic signature of the method, or an empty string if there is + /// none + pub generic_signature: String, + /// The modifier bit flags (also known as access flags) which provide + /// additional information on the method declaration. + /// + /// Individual flag values are defined in Chapter 4 of The Java™ Virtual + /// Machine Specification. + /// + /// In addition, the 0xf0000000 bit identifies the method as synthetic, + /// if the synthetic attribute capability is available. + pub mod_bits: MethodModifiers, + } + + /// Returns instances of this reference type. + /// + /// Only instances that are reachable for the purposes of garbage collection + /// are returned. + #[jdwp_command(Vec, 2, 16)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Instances { + /// The reference type ID + ref_type: ReferenceTypeID, + /// Maximum number of instances to return. + max_instances: InstanceLimit, + } + + #[derive(Debug, Clone)] + pub enum InstanceLimit { + All, + Limit(NonZeroU32), + } + + impl InstanceLimit { + /// A shorthand for `InstanceLimit::Limit`. + /// + /// # Panics + /// Panics if `limit` is zero. + pub fn limit(limit: u32) -> Self { + InstanceLimit::Limit(NonZeroU32::new(limit).expect("Instance limit was zero")) + } + } + + impl JdwpWritable for InstanceLimit { + fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { + match self { + InstanceLimit::All => 0u32.write(write), + InstanceLimit::Limit(limit) => limit.get().write(write), + } + } + } + + /// Returns the class object corresponding to this type. + #[jdwp_command(2, 17)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ClassFileVersion { + /// The class + ref_type: ReferenceTypeID, + } + + #[derive(Debug, JdwpReadable)] + pub struct ClassFileVersionReply { + /// Major version number + pub major_version: u32, + /// Minor version number + pub minor_version: u32, + } + + /// Return the raw bytes of the constant pool in the format of the + /// constant_pool item of the Class File Format in The Java™ Virtual Machine + /// Specification. + /// + /// Since JDWP version 1.6. Requires canGetConstantPool capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command(2, 18)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ConstantPool { + /// The class + ref_type: ReferenceTypeID, + } + + #[derive(JdwpReadable)] + pub struct ConstantPoolReply { + /// Total number of constant pool entries plus one. + /// + /// This corresponds to the constant_pool_count item of the Class File + /// Format in The Java™ Virtual Machine Specification. + pub count: u32, + /// Raw bytes of the constant pool + pub bytes: Vec, + } + + // special debug so that trace logs dont take a quadrillion lines + impl Debug for ConstantPoolReply { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let hex_bytes = self + .bytes + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + + struct Unquoted(String); + + impl Debug for Unquoted { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(&self.0) + } + } + + f.debug_struct("ConstantPoolReply") + .field("count", &self.count) + .field("bytes", &Unquoted(hex_bytes)) + .finish() + } + } +} + +/// ClassType Command Set (3) +pub mod class_type { + use super::*; + + /// Returns the immediate superclass of a class. + /// + /// The return is null if the class is java.lang.Object. + #[jdwp_command(Option, 3, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Superclass { + /// The class type ID. + class_id: ClassID, + } + + /// Sets the value of one or more static fields. + /// + /// Each field must be member of the class type or one of its superclasses, + /// superinterfaces, or implemented interfaces. + /// + /// Access control is not enforced; for example, the values of private + /// fields can be set. + /// + /// Final fields cannot be set. + /// + /// For primitive values, the value's type must match the field's type + /// exactly. + /// + /// For object values, there must exist a widening reference conversion from + /// the value's type to thefield's type and the field's type must be + /// loaded. + #[jdwp_command((), 3, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SetValues<'a> { + /// The class type ID. + class_id: ClassID, + /// Fields to set and their values. + values: &'a [(FieldID, UntaggedValue)], + } + + /// Invokes a static method. The method must be member of the class type or + /// one of its superclasses, superinterfaces, or implemented interfaces. + /// Access control is not enforced; for example, private methods can be + /// invoked. + /// + /// The method invocation will occur in the specified thread. Method + /// invocation can occur only if the specified thread has been suspended + /// by an event. Method invocation is not supported when the target VM + /// has been suspended by the front-end. + /// + /// The specified method is invoked with the arguments in the specified + /// argument list. The method invocation is synchronous; the reply + /// packet is not sent until the invoked method returns in the target + /// VM. The return value (possibly the void value) is included in the + /// reply packet. If the invoked method throws an exception, the + /// exception object ID is set in the reply packet; otherwise, the + /// exception object ID is null. + /// + /// For primitive arguments, the argument value's type must match the + /// argument's type exactly. For object arguments, there must exist a + /// widening reference conversion from the argument value's type to the + /// argument's type and the argument's type must be loaded. + /// + /// By default, all threads in the target VM are resumed while the method is + /// being invoked if they were previously suspended by an event or by + /// command. This is done to prevent the deadlocks that will occur if + /// any of the threads own monitors that will be needed by the invoked + /// method. It is possible that breakpoints or other events might occur + /// during the invocation. Note, however, that this implicit resume acts + /// exactly like the ThreadReference + /// [resume](super::thread_reference::Resume) command, so if the + /// thread's suspend count is greater than 1, it will remain in a suspended + /// state during the invocation. By default, when the invocation completes, + /// all threads in the target VM are suspended, regardless their state + /// before the invocation. + /// + /// The resumption of other threads during the invoke can be prevented by + /// specifying the + /// [INVOKE_SINGLE_THREADED](crate::spec::InvokeOptions::SINGLE_THREADED) + /// bit flag in the options field; however, there is no protection + /// against or recovery from the deadlocks described above, so this + /// option should be used with great caution. Only the specified thread + /// will be resumed (as described for all threads above). Upon + /// completion of a single threaded invoke, the invoking thread will be + /// suspended once again. Note that any threads started during the + /// single threaded invocation will not be suspended when the invocation + /// completes. + /// + /// If the target VM is disconnected during the invoke (for example, through + /// the VirtualMachine [dispose](super::virtual_machine::Dispose) + /// command) the method invocation continues. + + #[jdwp_command(3, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct InvokeMethod<'a> { + /// The class type ID + class_id: ClassID, + /// The thread in which to invoke + thread_id: ThreadID, + /// The method to invoke + method_id: MethodID, + /// Arguments to the method + arguments: &'a [Value], + // Invocation options + options: InvokeOptions, + } + + #[jdwp_command(3, 4)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct NewInstance<'a> { + /// The class type ID. + class_id: ClassID, + /// The thread in which to invoke the constructor. + thread_id: ThreadID, + /// The constructor to invoke. + method_id: MethodID, + /// Arguments for the constructor method. + arguments: &'a [Value], + // Constructor invocation options + options: InvokeOptions, + } + + #[derive(Debug)] + pub enum NewInstanceReply { + /// The newly created object. + NewObject(TaggedObjectID), + /// The thrown exception. + Exception(TaggedObjectID), + } + + // better types everyone + impl JdwpReadable for NewInstanceReply { + fn read(read: &mut JdwpReader) -> io::Result { + let new_object = Option::::read(read)?; + let exception = Option::::read(read)?; + + match (new_object, exception) { + (Some(new_object), None) => Ok(NewInstanceReply::NewObject(new_object)), + (None, Some(exception)) => Ok(NewInstanceReply::Exception(exception)), + _ => Err(io::Error::from(io::ErrorKind::InvalidData)), + } + } + } +} + +/// ArrayType Command Set (4) +pub mod array_type { + use super::*; + + /// Creates a new array object of this type with a given length. + #[jdwp_command(4, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct NewInstance { + /// The array type of the new instance + array_type_id: ArrayTypeID, + /// The length of the array + length: u32, + } + + #[derive(Debug, JdwpReadable)] + pub struct NewInstanceReply { + // should always be Tag::Array + _tag: Tag, + /// The newly created array object + pub new_array: ArrayID, + } + + impl Deref for NewInstanceReply { + type Target = ArrayID; + + fn deref(&self) -> &Self::Target { + &self.new_array + } + } +} + +/// InterfaceType Command Set (5) +pub mod interface_type { + use super::*; + + /// Invokes a static method. The method must not be a static initializer. + /// The method must be a member of the interface type. + /// + /// Since JDWP version 1.8 + /// + /// The method invocation will occur in the specified thread. Method + /// invocation can occur only if the specified thread has been suspended by + /// an event. Method invocation is not supported when the target VM has been + /// suspended by the front-end. + /// + /// The specified method is invoked with the arguments in the specified + /// argument list. The method invocation is synchronous; the reply packet is + /// not sent until the invoked method returns in the target VM. The return + /// value (possibly the void value) is included in the reply packet. If the + /// invoked method throws an exception, the exception object ID is set in + /// the reply packet; otherwise, the exception object ID is null. + /// + /// For primitive arguments, the argument value's type must match the + /// argument's type exactly. For object arguments, there must exist a + /// widening reference conversion from the argument value's type to the + /// argument's type and the argument's type must be loaded. + /// + /// By default, all threads in the target VM are resumed while the method is + /// being invoked if they were previously suspended by an event or by a + /// command. This is done to prevent the deadlocks that will occur if any of + /// the threads own monitors that will be needed by the invoked method. It + /// is possible that breakpoints or other events might occur during the + /// invocation. Note, however, that this implicit resume acts exactly like + /// the ThreadReference resume command, so if the thread's suspend count is + /// greater than 1, it will remain in a suspended state during the + /// invocation. By default, when the invocation completes, all threads in + /// the target VM are suspended, regardless their state before the + /// invocation. + /// + /// The resumption of other threads during the invoke can be prevented by + /// specifying the SINGLE_THREADED bit flag in the options field; + /// however, there is no protection against or recovery from the deadlocks + /// described above, so this option should be used with great caution. Only + /// the specified thread will be resumed (as described for all threads + /// above). Upon completion of a single threaded invoke, the invoking thread + /// will be suspended once again. Note that any threads started during the + /// single threaded invocation will not be suspended when the invocation + /// completes. + + // If the target VM is disconnected during the invoke (for example, through the VirtualMachine + // [Dispose](super::virtual_machine::Dispose) command) the method invocation continues. + #[jdwp_command(5, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct InvokeMethod<'a> { + /// The interface type ID + interface_id: InterfaceID, + /// The thread in which to invoke + thread_id: ThreadID, + /// The method to invoke + method_id: MethodID, + /// The argument values + arguments: &'a [Value], + /// Invocation options + options: InvokeOptions, + } +} + +/// Method Command Set (6) +pub mod method { + use super::*; + + /// Returns line number information for the method, if present. + /// + /// The line table maps source line numbers to the initial code index of the + /// line. + /// + /// The line table is ordered by code index (from lowest to highest). + /// + /// The line number information is constant unless a new class definition is + /// installed using + /// [RedefineClasses](super::virtual_machine::RedefineClasses). + #[jdwp_command(6, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct LineTable { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, + } + + #[derive(Debug, JdwpReadable)] + pub struct LineTableReply { + /// Lowest valid code index for the method, >=0, or -1 if the method is + /// native + pub start: i64, + /// Highest valid code index for the method, >=0, or -1 if the method is + /// native + pub end: i64, + /// The entries of the line table for this method. + pub lines: Vec, + } + + #[derive(Debug, JdwpReadable)] + pub struct Line { + /// Initial code index of the line, start <= lineCodeIndex < end + pub line_code_index: u64, + /// Line number. + pub line_number: u32, + } + + /// Returns variable information for the method. + /// + /// The variable table includes arguments and locals declared within the + /// method. For instance methods, the "this" reference is included in + /// the table. Also, synthetic variables may be present. + #[jdwp_command(6, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct VariableTable { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, + } + + #[derive(Debug, JdwpReadable)] + pub struct VariableTableReply { + /// The number of words in the frame used by arguments. Eight-byte + /// arguments use two words; all others use one. + pub arg_cnt: u32, + /// The variables. + pub variables: Vec, + } + + #[derive(Debug, JdwpReadable)] + pub struct Variable { + /// First code index at which the variable is visible. + /// + /// Used in conjunction with length. The variable can be get or set only + /// when the current codeIndex <= current frame code index < codeIndex + + /// length + pub code_index: u64, + /// The variable's name. + pub name: String, + /// The variable type's JNI signature. + pub signature: String, + /// Unsigned value used in conjunction with codeIndex. + /// + /// The variable can be get or set only when the current codeIndex <= + /// current frame code index < code index + length + pub length: u32, + /// The local variable's index in its frame + pub slot: u32, + } + + /// Retrieve the method's bytecodes as defined in The Java™ Virtual Machine + /// Specification. + /// + /// Requires `canGetBytecodes` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command(Vec, 6, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Bytecodes { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, + } + + /// Determine if this method is obsolete. + /// + /// A method is obsolete if it has been replaced by a non-equivalent method + /// using the [RedefineClasses](super::virtual_machine::RedefineClasses) + /// command. The original and redefined methods are considered equivalent if + /// their bytecodes are the same except for indices into the constant pool + /// and the referenced constants are equal. + #[jdwp_command(bool, 6, 4)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct IsObsolete { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, + } + + /// Returns variable information for the method, including generic + /// signatures for the variables. + /// + /// The variable table includes arguments and locals declared within the + /// method. For instance methods, the "this" reference is included in + /// the table. Also, synthetic variables may be present. Generic + /// signatures are described in the signature attribute section in The + /// Java™ Virtual Machine Specification. + /// + /// Since JDWP version 1.5. + #[jdwp_command(6, 5)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct VariableTableWithGeneric { + /// The class. + reference_type_id: ReferenceTypeID, + /// The method. + method_id: MethodID, + } + + #[derive(Debug, JdwpReadable)] + pub struct VariableTableWithGenericReply { + /// The number of words in the frame used by arguments. Eight-byte + /// arguments use two words; all others use one. + pub arg_cnt: u32, + /// The variables. + pub variables: Vec, + } + + #[derive(Debug, JdwpReadable)] + pub struct VariableWithGeneric { + /// First code index at which the variable is visible. + /// + /// Used in conjunction with length. The variable can be get or set only + /// when the current codeIndex <= current frame code index < codeIndex + + /// length + pub code_index: u64, + /// The variable's name. + pub name: String, + /// The variable type's JNI signature. + pub signature: String, + /// The variable type's generic signature or an empty string if there is + /// none. + pub generic_signature: String, + /// Unsigned value used in conjunction with codeIndex. + /// + /// The variable can be get or set only when the current codeIndex <= + /// current frame code index < code index + length + pub length: u32, + /// The local variable's index in its frame + pub slot: u32, + } +} + +/// Field Command Set (8) +pub mod field {} + +/// ObjectReference Command Set (9) +pub mod object_reference { + use super::*; + + /// Returns the runtime type of the object. The runtime type will be a class + /// or an array. + #[jdwp_command(TaggedReferenceTypeID, 9, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ReferenceType { + /// The object ID + object: ObjectID, + } + + /// Returns the value of one or more instance fields. + /// + /// Each field must be member of the object's type or one of its + /// superclasses, superinterfaces, or implemented interfaces. Access + /// control is not enforced; for example, the values of private fields + /// can be obtained. + #[jdwp_command(C::Map, 9, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct GetValues> { + /// The object ID + object: ObjectID, + /// Fields to get + fields: C, + } + + /// Sets the value of one or more instance fields. + /// + /// Each field must be member of the object's type or one of its + /// superclasses, superinterfaces, or implemented interfaces. Access + /// control is not enforced; for example, the values of private fields + /// can be set. For primitive values, the value's type must match the + /// field's type exactly. For object values, there must be a widening + /// reference conversion from the value's type to the field's type and + /// the field's type must be loaded. + #[jdwp_command((), 9, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SetValues<'a> { + /// The object ID + object: ObjectID, + /// Fields and the values to set them to + fields: &'a [(FieldID, UntaggedValue)], + } + + /// Returns monitor information for an object. + /// + /// All threads in the VM must be suspended. + /// + /// Requires `can_get_monitor_info` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command(9, 5)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct MonitorInfo { + /// The object ID + object: ObjectID, + } + + #[derive(Debug, JdwpReadable)] + pub struct MonitorInfoReply { + /// The monitor owner, or null if it is not currently owned + pub owner: Option, + /// The number of times the monitor has been entered. + pub entry_count: i32, + /// The threads that are waiting for the monitor 0 if there is no + /// current owner + pub waiters: Vec, + } + + /// Invokes a instance method. + /// + /// The method must be member of the object's type or one of its + /// superclasses, superinterfaces, or implemented interfaces. Access + /// control is not enforced; for example, private methods can be + /// invoked. + /// + /// The method invocation will occur in the specified thread. Method + /// invocation can occur only if the specified thread has been suspended + /// by an event. Method invocation is not supported when the target VM + /// has been suspended by the front-end. + /// + /// The specified method is invoked with the arguments in the specified + /// argument list. The method invocation is synchronous; the reply + /// packet is not sent until the invoked method returns in the target + /// VM. The return value (possibly the void value) is included in the + /// reply packet. + /// + /// For primitive arguments, the argument value's type must match the + /// argument's type exactly. For object arguments, there must be a + /// widening reference conversion from the argument value's type to the + /// argument's type and the argument's type must be loaded. + /// + /// By default, all threads in the target VM are resumed while the method is + /// being invoked if they were previously suspended by an event or by a + /// command. This is done to prevent the deadlocks that will occur if + /// any of the threads own monitors that will be needed by the invoked + /// method. It is possible that breakpoints or other events might occur + /// during the invocation. Note, however, that this implicit resume acts + /// exactly like the ThreadReference resume command, so if the thread's + /// suspend count is greater than 1, it will remain in a suspended state + /// during the invocation. By default, when the invocation completes, + /// all threads in the target VM are suspended, regardless their state + /// before the invocation. + /// + /// The resumption of other threads during the invoke can be prevented by + /// specifying the INVOKE_SINGLE_THREADED bit flag in the options field; + /// however, there is no protection against or recovery from the deadlocks + /// described above, so this option should be used with great caution. Only + /// the specified thread will be resumed (as described for all threads + /// above). Upon completion of a single threaded invoke, the invoking + /// thread will be suspended once again. Note that any threads started + /// during the single threaded invocation will not be suspended when the + /// invocation completes. + /// + /// If the target VM is disconnected during the invoke (for example, through + /// the VirtualMachine dispose command) the method invocation continues. + #[jdwp_command(9, 6)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct InvokeMethod<'a> { + /// The object ID + object: ObjectID, + /// The thread in which to invoke + thread: ThreadID, + /// The method to invoke + method: (ClassID, MethodID), + /// The arguments + arguments: &'a [Value], + /// Invocation options + options: InvokeOptions, + } + + /// Prevents garbage collection for the given object. + /// + /// By default all objects in back-end replies may be collected at any time + /// the target VM is running. A call to this command guarantees that the + /// object will not be collected. The [EnableCollection] command can be + /// used to allow collection once again. + /// + /// Note that while the target VM is suspended, no garbage collection will + /// occur because all threads are suspended. The typical examination of + /// variables, fields, and arrays during the suspension is safe without + /// explicitly disabling garbage collection. + /// + /// This method should be used sparingly, as it alters the pattern of + /// garbage collection in the target VM and, consequently, may result in + /// application behavior under the debugger that differs from its + /// non-debugged behavior. + #[jdwp_command((), 9, 7)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct DisableCollection { + /// The object ID + object: ObjectID, + } + + /// Permits garbage collection for this object. + /// + /// By default all objects returned by JDWP may become unreachable in the + /// target VM, and hence may be garbage collected. A call to this + /// command is necessary only if garbage collection was previously + /// disabled with the [DisableCollection] command. + #[jdwp_command((), 9, 8)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct EnableCollection { + /// The object ID + object: ObjectID, + } + + /// Determines whether an object has been garbage collected in the target + /// VM. + #[jdwp_command(bool, 9, 9)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct IsCollected { + /// The object ID + object: ObjectID, + } + + /// Returns objects that directly reference this object. Only objects that + /// are reachable for the purposes of garbage collection are returned. + /// Note that an object can also be referenced in other ways, such as + /// from a local variable in a stack frame, or from a JNI global + /// reference. Such non-object referrers are not returned by this + /// command. + /// + /// Since JDWP version 1.6. + /// + /// Requires `can_get_instance_info` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command(Vec, 9, 10)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ReferringObjects { + /// The object ID + object: ObjectID, + /// Maximum number of referring objects to return. Must be non-negative. + /// If zero, all referring objects are returned. + max_referrers: u32, + } +} + +/// StringReference Command Set (10) +pub mod string_reference { + use super::*; + + /// Returns the characters contained in the string. + #[jdwp_command(String, 10, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Value { + /// The String object ID + string_object: StringID, + } +} + +/// ThreadReference Command Set (11) +pub mod thread_reference { + use super::*; + + /// Returns the thread name. + #[jdwp_command(String, 11, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Name { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Suspends the thread. + /// + /// Unlike `java.lang.Thread.suspend()`, suspends of both the virtual + /// machine and individual threads are counted. Before a thread will run + /// again, it must be resumed the same number of times it has been + /// suspended. + /// + /// Suspending single threads with command has the same dangers + /// `java.lang.Thread.suspend()`. If the suspended thread holds a monitor + /// needed by another running thread, deadlock is possible in the target + /// VM (at least until the suspended thread is resumed again). + /// + /// The suspended thread is guaranteed to remain suspended until resumed + /// through one of the JDI resume methods mentioned above; the + /// application in the target VM cannot resume the suspended thread + /// through `java.lang.Thread.resume()`. + /// + /// Note that this doesn't change the status of the thread (see the + /// [ThreadStatus] command.) For example, if it was Running, it will still + /// appear running to other threads. + #[jdwp_command((), 11, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Suspend { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Resumes the execution of a given thread. + /// + /// If this thread was not previously suspended by the front-end, calling + /// this command has no effect. Otherwise, the count of pending suspends + /// on this thread is decremented. If it is decremented to 0, the thread + /// will continue to execute. + #[jdwp_command((), 11, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Resume { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Returns the current status of a thread. + /// + /// The thread status reply indicates the thread status the last time it was + /// running. the suspend status provides information on the thread's + /// suspension, if any. + #[jdwp_command((ThreadStatus, SuspendStatus), 11, 4)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Status { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Returns the thread group that contains a given thread. + #[jdwp_command(ThreadGroupID, 11, 5)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ThreadGroup { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Returns the current call stack of a suspended thread. + /// + /// The sequence of frames starts with the currently executing frame, + /// followed by its caller, and so on. The thread must be suspended, and + /// the returned frameID is valid only while the thread is suspended. + #[jdwp_command(Vec<(FrameID, Location)>, 11, 6)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Frames { + /// The thread object ID. + pub thread: ThreadID, + /// The index of the first frame to retrieve. + pub start_frame: u32, + /// The amount of frames to retrieve. + pub limit: FrameLimit, + } + + /// A nice readable enum to be used in place of raw `i32` with a special + /// meaning for -1. + #[derive(Debug, Clone)] + pub enum FrameLimit { + Limit(u32), + AllRemaining, + } + + impl JdwpWritable for FrameLimit { + fn write(&self, write: &mut JdwpWriter) -> std::io::Result<()> { + match self { + FrameLimit::Limit(n) => n.write(write), + FrameLimit::AllRemaining => (-1i32).write(write), + } + } + } + + /// Returns the count of frames on this thread's stack. + /// + /// The thread must be suspended, and the returned count is valid only while + /// the thread is suspended. + /// + /// Returns [ThreadNotSuspended](crate::spec::ErrorCode::ThreadNotSuspended) + /// if not suspended. + #[jdwp_command(u32, 11, 7)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct FrameCount { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Returns the objects whose monitors have been entered by this thread. + /// + /// The thread must be suspended, and the returned information is relevant + /// only while the thread is suspended. + /// + /// Requires `can_get_owned_monitor_info` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command(Vec, 11, 8)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct OwnedMonitors { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Returns the object, if any, for which this thread is waiting. + /// + /// The thread may be waiting to enter a monitor, or it may be waiting, via + /// the `java.lang.Object.wait` method, for another thread to invoke the + /// notify method. The thread must be suspended, and the returned + /// information is relevant only while the thread is suspended. + /// + /// Requires `can_get_current_contended_monitor` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command(Option, 11, 9)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct CurrentContendedMonitor { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Stops the thread with an asynchronous exception, as if done by + /// `java.lang.Thread.stop` + #[jdwp_command((), 11, 10)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Stop { + /// The thread object ID. + pub thread: ThreadID, + /// Asynchronous exception. + /// + /// This object must be an instance of `java.lang.Throwable` or a + /// subclass + pub throwable: TaggedObjectID, + } + + /// Interrupt the thread, as if done by `java.lang.Thread.interrupt` + #[jdwp_command((), 11, 11)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Interrupt { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Get the suspend count for this thread. + /// + /// The suspend count is the number of times the thread has been suspended + /// through the thread-level or VM-level suspend commands without a + /// corresponding resume + #[jdwp_command(u32, 11, 12)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SuspendCount { + /// The thread object ID. + pub thread: ThreadID, + } + + /// Returns monitor objects owned by the thread, along with stack depth at + /// which the monitor was acquired. + /// + /// Stack depth can be unknown (e.g., for monitors acquired by JNI + /// MonitorEnter). The thread must be suspended, and the returned + /// information is relevant only while the thread is suspended. + /// + /// Requires `can_get_monitor_frame_info` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + #[jdwp_command(Vec<(TaggedObjectID, StackDepth)>, 11, 13)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct OwnedMonitorsStackDepthInfo { + /// The thread object ID. + pub thread: ThreadID, + } + + #[derive(Debug, Clone)] + pub enum StackDepth { + Depth(u32), + Unknown, + } + + impl JdwpReadable for StackDepth { + fn read(read: &mut JdwpReader) -> std::io::Result { + let depth = match i32::read(read)? { + -1 => StackDepth::Unknown, + n => StackDepth::Depth(n as u32), + }; + Ok(depth) + } + } + + /// Force a method to return before it reaches a return statement. + /// + /// The method which will return early is referred to as the called method. + /// The called method is the current method (as defined by the Frames + /// section in The Java™ Virtual Machine Specification) for the + /// specified thread at the time this command is received. + /// + /// The specified thread must be suspended. The return occurs when execution + /// of Java programming language code is resumed on this thread. Between + /// sending this command and resumption of thread execution, the state + /// of the stack is undefined. + /// + /// No further instructions are executed in the called method. Specifically, + /// finally blocks are not executed. Note: this can cause inconsistent + /// states in the application. + /// + /// A lock acquired by calling the called method (if it is a synchronized + /// method) and locks acquired by entering synchronized blocks within the + /// called method are released. Note: this does not apply to JNI locks + /// or java.util.concurrent.locks locks. + /// + /// Events, such as [MethodExit](super::event::Event::MethodExit), are + /// generated as they would be in a normal return. + /// + /// The called method must be a non-native Java programming language method. + /// Forcing return on a thread with only one frame on the stack causes the + /// thread to exit when resumed. + /// + /// For void methods, the value must be a void value. For methods that + /// return primitive values, the value's type must match the return type + /// exactly. For object values, there must be a widening reference + /// conversion from the value's type to the return type type and the + /// return type must be loaded. + /// + /// Since JDWP version 1.6. Requires `can_force_early_return` capability - + /// see [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command((), 11, 14)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ForceEarlyReturn { + /// The thread object ID. + pub thread: ThreadID, + /// The value to return. + pub value: Value, + } +} + +/// ThreadGroupReference Command Set (12) +pub mod thread_group_reference { + use super::*; + + /// Returns the thread group name. + #[jdwp_command(String, 12, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Name { + /// The thread group object ID + group: ThreadGroupID, + } + + /// Returns the thread group, if any, which contains a given thread group. + #[jdwp_command(Option, 12, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Parent { + /// The thread group object ID + group: ThreadGroupID, + } + + /// Returns the live threads and active thread groups directly contained in + /// this thread group. + /// + /// Threads and thread groups in child thread groups are not included. + /// + /// A thread is alive if it has been started and has not yet been stopped. + /// + /// See `java.lang.ThreadGroup` for information about active ThreadGroups. + #[jdwp_command(12, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Children { + /// The thread group object ID + group: ThreadGroupID, + } + + #[derive(Debug, JdwpReadable)] + pub struct ChildrenReply { + /// Live direct child threads + pub child_threads: Vec, + /// Active child thread groups + pub child_groups: Vec, + } +} + +/// ArrayReference Command Set (13) +pub mod array_reference { + use super::*; + + /// Returns the number of components in a given array. + #[jdwp_command(u32, 13, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Length { + /// The array object ID + array_id: ArrayID, + } + + /// Returns a range of array components. + /// + /// The specified range must be within the bounds of the array. + #[jdwp_command(ArrayRegion, 13, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct GetValues { + /// The array object ID + array_id: ArrayID, + /// The first index to retrieve + first_index: u32, + /// The number of components to retrieve + length: u32, + } + + /// Sets a range of array components. + /// + /// The specified range must be within the bounds of the array. + /// + /// For primitive values, each value's type must match the array component + /// type exactly. + /// + /// For object values, there must be a widening reference conversion from + /// the value's type to the array component type and the array component + /// type must be loaded. + #[jdwp_command((), 13, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SetValues<'a, V: JdwpValue> { + /// The array object ID + array_id: ArrayID, + /// The first index to set + first_index: u32, + /// Values to set + values: &'a [V], + } +} + +/// ClassLoaderReference Command Set (14) +pub mod class_loader_reference { + use super::*; + + /// Returns a list of all classes which this class loader has been requested + /// to load. + /// + /// This class loader is considered to be an initiating class loader for + /// each class in the returned list. The list contains each reference + /// type defined by this loader and any types for which loading was + /// delegated by this class loader to another class loader. + /// + /// The visible class list has useful properties with respect to the type + /// namespace. + /// + /// A particular type name will occur at most once in the list. + /// + /// Each field or variable declared with that type name in a class defined + /// by this class loader must be resolved to that single type. + /// + /// No ordering of the returned list is guaranteed. + #[jdwp_command(Vec, 14, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct VisibleClasses { + /// The class loader object ID + class_loader_id: ClassLoaderID, + } +} + +/// EventRequest Command Set (15) +pub mod event_request { + use super::*; + + #[derive(Debug, Clone, PartialEq, Eq, Hash, JdwpWritable)] + #[repr(u8)] + pub enum Modifier<'a> { + /// Limit the requested event to be reported at most once after a given + /// number of occurrences. + /// + /// The event is not reported the first count - 1 times this filter is + /// reached. + /// + /// To request a one-off event, call this method with a count of 1. + /// + /// Once the count reaches 0, any subsequent filters in this request are + /// applied. + /// + /// If none of those filters cause the event to be suppressed, the event + /// is reported. + /// + /// Otherwise, the event is not reported. + /// + /// In either case subsequent events are never reported for this + /// request. + /// + /// This modifier can be used with any event kind. + Count( + /// Count before event. One for one-off + i32, + ) = ModifierKind::Count as u8, + + /// Conditional on expression + Conditional { + /// For the future + expr_id: i32, + } = ModifierKind::Conditional as u8, + + /// Restricts reported events to those in the given thread. + /// This modifier can be used with any event kind except for class + /// unload. + ThreadOnly( + /// Required thread + ThreadID, + ) = ModifierKind::ThreadOnly as u8, + + /// For class prepare events, restricts the events generated by this + /// request to be the preparation of the given reference type + /// and any subtypes. + /// + /// For monitor wait and waited events, restricts the events generated + /// by this request to those whose monitor object is of the + /// given reference type or any of its subtypes. + /// + /// For other events, restricts the events generated by this request to + /// those whose location is in the given reference type or any of its + /// subtypes. + /// + /// An event will be generated for any location in a reference type that + /// can be safely cast to the given reference type. + /// + /// This modifier can be used with any event kind except class unload, + /// thread start, and thread end. + ClassOnly( + /// Required class + ReferenceTypeID, + ) = ModifierKind::ClassOnly as u8, + + /// Restricts reported events to those for classes whose name matches + /// the given restricted regular expression. + /// + /// For class prepare events, the prepared class name is matched. + /// + /// For class unload events, the unloaded class name is matched. + /// + /// For monitor wait and waited events, the name of the class of the + /// monitor object is matched. + /// + /// For other events, the class name of the event's location is matched. + /// + /// This modifier can be used with any event kind except thread start + /// and thread end. + ClassMatch( + /// Required class pattern. + /// + /// Matches are limited to exact matches of the given class pattern + /// and matches of patterns that begin or end with `*`; + /// for example, `*.Foo` or `java.*`. + &'a str, + ) = ModifierKind::ClassMatch as u8, + + /// Restricts reported events to those for classes whose name does not + /// match the given restricted regular expression. + /// + /// For class prepare events, the prepared class name is matched. + /// + /// For class unload events, the unloaded class name is matched. + /// + /// For monitor wait and waited events, the name of the class of the + /// monitor object is matched. + /// + /// For other events, the class name of the event's location is matched. + /// + /// This modifier can be used with any event kind except thread start + /// and thread end. + ClassExclude( + /// Disallowed class pattern. + /// + /// Matches are limited to exact matches of the given class pattern + /// and matches of patterns that begin or end with `*`; + /// for example, `*.Foo` or `java.*`. + &'a str, + ) = ModifierKind::ClassExclude as u8, + + /// Restricts reported events to those that occur at the given location. + /// + /// This modifier can be used with breakpoint, field access, field + /// modification, step, and exception event kinds. + LocationOnly( + /// Required location + Location, + ) = ModifierKind::LocationOnly as u8, + + /// Restricts reported exceptions by their class and whether they are + /// caught or uncaught. + /// + /// This modifier can be used with exception event kinds only. + ExceptionOnly { + /// Exception to report. `None` means report exceptions of all + /// types. + /// + /// A non-null type restricts the reported exception events to + /// exceptions of the given type or any of its subtypes. + exception: Option, + /// Report caught exceptions + uncaught: bool, + /// Report uncaught exceptions. + /// + /// Note that it is not always possible to determine whether an + /// exception is caught or uncaught at the time it is thrown. + /// + /// See the exception event catch location under composite events + /// for more information. + caught: bool, + } = ModifierKind::ExceptionOnly as u8, + + /// Restricts reported events to those that occur for a given field. + /// + /// This modifier can be used with field access and field modification + /// event kinds only. + FieldOnly( + /// Type in which field is declared + ReferenceTypeID, + /// Required field + FieldID, + ) = ModifierKind::FieldOnly as u8, + + /// Restricts reported step events to those which satisfy depth and size + /// constraints. + /// + /// This modifier can be used with step event kinds only. + Step( + /// Thread in which to step + ThreadID, + /// Size of each step + StepSize, + /// Relative call stack limit + StepDepth, + ) = ModifierKind::Step as u8, + + /// Restricts reported events to those whose active 'this' object is the + /// given object. + /// + /// Match value is the null object for static methods. + /// + /// This modifier can be used with any event kind except class prepare, + /// class unload, thread start, and thread end. + /// + /// Introduced in JDWP version 1.4. + InstanceOnly( + /// Required 'this' object + ObjectID, + ) = ModifierKind::InstanceOnly as u8, + + /// Restricts reported class prepare events to those for reference types + /// which have a source name which matches the given restricted regular + /// expression. + /// + /// The source names are determined by the reference type's + /// SourceDebugExtension. + /// + /// This modifier can only be used with class prepare events. + /// + /// Since JDWP version 1.6. + /// + /// Requires the `can_use_source_name_filters` capability - see + /// [CapabilitiesNew](crate::spec::virtual_machine::CapabilitiesNew). + SourceNameMatch( + /// Required source name pattern. + /// Matches are limited to exact matches of the given pattern and + /// matches of patterns that begin or end with `*`; for example, + /// `*.Foo` or `java.*` + &'a str, + ) = ModifierKind::SourceNameMatch as u8, + } + + /// Set an event request. + /// + /// When the event described by this request occurs, an event is sent from + /// the target VM. + /// + /// If an event occurs that has not been requested then it is not sent from + /// the target VM. + /// + /// The two exceptions to this are the VM Start Event and the VM Death Event + /// which are automatically generated events - see + /// [Composite](super::event::Composite) command for further details. + #[jdwp_command(RequestID, 15, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Set<'a> { + /// Event kind to request. Some events may require a capability in order + /// to be requested. + event_kind: EventKind, + /// What threads are suspended when this event occurs? + /// + /// Note that the order of events and command replies accurately + /// reflects the order in which threads are suspended and + /// resumed. + /// + /// For example, if a VM-wide resume is processed before an event occurs + /// which suspends the VM, the reply to the resume command will be + /// written to the transport before the suspending event. + suspend_policy: SuspendPolicy, + /// Constraints used to control the number of generated events. + /// + /// Modifiers specify additional tests that an event must satisfy before + /// it is placed in the event queue. + /// + /// Events are filtered by applying each modifier to an event in the + /// order they are specified in this collection. Only events + /// that satisfy all modifiers are reported. + /// + /// An empty list means there are no modifiers in the request. + /// + /// Filtering can improve debugger performance dramatically by reducing + /// the amount of event traffic sent from the target VM to the + /// debugger. + modifiers: &'a [Modifier<'a>], + } + + /// Clear an event request. + /// + /// See [EventKind] for a complete list of events that can be cleared. + /// + /// Only the event request matching the specified event kind and + /// `request_id` is cleared. + /// + /// If there isn't a matching event request the command is a no-op and does + /// not result in an error. + /// + /// Automatically generated events do not have a corresponding event request + /// and may not be cleared using this command. + #[jdwp_command((), 15, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct Clear { + /// Event kind to clear + event_kind: EventKind, + /// ID of request to clear + request_id: RequestID, + } + + /// Removes all set breakpoints, a no-op if there are no breakpoints set. + #[jdwp_command((), 15, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ClearAllBreakpoints; +} + +/// StackFrame Command Set (16) +pub mod stack_frame { + use super::*; + + /// Returns the value of one or more local variables in a given frame. + /// + /// Each variable must be visible at the frame's code index. + /// + /// Even if local variable information is not available, values can be + /// retrieved if the front-end is able to determine the correct local + /// variable index. (Typically, this index can be determined for method + /// arguments from the method signature without access to the local + /// variable table information.) + #[jdwp_command(C::Map, 16, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct GetValues> { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, + /// Local variable indices and types to get. + pub slots: C, + } + + /// Sets the value of one or more local variables. + /// + /// Each variable must be visible at the current frame code index. For + /// primitive values, the value's type must match the variable's type + /// exactly. For object values, there must be a widening reference + /// conversion from the value's type to thevariable's type and the + /// variable's type must be loaded. + /// + /// Even if local variable information is not available, values can be set, + /// if the front-end is able to determine the correct local variable + /// index. (Typically, thisindex can be determined for method arguments + /// from the method signature without access to the local variable table + /// information.) + #[jdwp_command((), 16, 2)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct SetValues<'a> { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, + /// Local variable indices and values to set. + pub slots: &'a [(u32, Value)], + } + + /// Returns the value of the 'this' reference for this frame. + /// + /// If the frame's method is static or native, the reply will contain the + /// null object reference. + #[jdwp_command(Option, 16, 3)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ThisObject { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, + } + + /// Pop the top-most stack frames of the thread stack, up to, and including + /// 'frame'. The thread must be suspended to perform this command. The + /// top-most stack frames are discarded and the stack frame previous to + /// 'frame' becomes the current frame. The operand stack is restored -- + /// the argument values are added back and if the invoke was not + /// invokestatic, objectref is added back as well. The Java virtual + /// machine program counter is restored to the opcode of the invoke + /// instruction. + /// + /// Since JDWP version 1.4. + /// + /// Requires `canPopFrames` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + #[jdwp_command((), 16, 4)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct PopFrames { + /// The frame's thread. + pub thread_id: ThreadID, + /// The frame ID. + pub frame_id: FrameID, + } +} + +/// ClassObjectReference Command Set (17) +pub mod class_object_reference { + use super::*; + + /// Returns the reference type reflected by this class object. + #[jdwp_command(TaggedReferenceTypeID, 17, 1)] + #[derive(Debug, Clone, JdwpWritable)] + pub struct ReflectedType { + /// The class object + class_object_id: ClassObjectID, + } +} + +/// Event Command Set (64) +pub mod event { + use super::*; + + pub(crate) mod sealed { + /// This trait is used to reduce duplication between the JDWP-level and + /// high-level event enums. The enum is parameterized by a + /// domain, and there are two domains that are used: + /// - [Spec] is used for the JDWP-level enum + pub trait Domain { + type Thread; + type TaggedObject; + type TaggedReferenceType; + type Field; + } + } + + #[derive(Debug, Clone)] + pub struct Spec; + + impl sealed::Domain for Spec { + type Thread = ThreadID; + type TaggedObject = TaggedObjectID; + type TaggedReferenceType = TaggedReferenceTypeID; + + type Field = (TaggedReferenceTypeID, FieldID, Option); + } + + /// The field order in JDWP spec is the discriminator (EventKind) and then + /// the request id, that's why it's not moved out of the enum. + #[derive(Debug, Clone, JdwpReadable)] + #[repr(u8)] + pub enum Event { + /// Notification of step completion in the target VM. + /// + /// The step event is generated before the code at its location is + /// executed. + SingleStep( + /// Request that generated the event + RequestID, + /// Stepped thread + D::Thread, + /// Location stepped to + Location, + ) = EventKind::SingleStep as u8, + + /// Notification of a breakpoint in the target VM. + /// + /// The breakpoint event is generated before the code at its location is + /// executed. + Breakpoint( + /// Request that generated the event + RequestID, + /// Stepped thread + D::Thread, + /// Location stepped to + Location, + ) = EventKind::Breakpoint as u8, + + /// Notification of a method invocation in the target VM. + /// + /// This event is generated before any code in the invoked method has + /// executed. + /// + /// Method entry events are generated for both native and non-native + /// methods. + /// + /// In some VMs method entry events can occur for a particular thread + /// before its thread start event occurs if methods are called + /// as part of the thread's initialization. + MethodEntry( + /// Request that generated the event + RequestID, + /// Stepped thread + D::Thread, + /// Location stepped to + Location, + ) = EventKind::MethodEntry as u8, + + /// Notification of a method return in the target VM. + /// + /// This event is generated after all code in the method has executed, + /// + /// but the location of this event is the last executed location + /// in the method. + /// + /// Method exit events are generated for both native and non-native + /// methods. + /// + /// Method exit events are not generated if the method terminates with a + /// thrown exception. + MethodExit( + /// Request that generated the event + RequestID, + /// Stepped thread + D::Thread, + /// Location stepped to + Location, + ) = EventKind::MethodExit as u8, + + /// Notification of a method return in the target VM. + /// + /// This event is generated after all code in the method has executed, + /// + /// but the location of this event is the last executed location + /// in the method. + /// + /// Method exit events are generated for both native and non-native + /// methods. + /// + /// Method exit events are not generated if the method terminates with a + /// thrown exception. + /// + /// Since JDWP version 1.6. + MethodExitWithReturnValue( + /// Request that generated the event + RequestID, + /// Thread which exited method + D::Thread, + /// Location of exit + Location, + /// Value that will be returned by the method + Value, + ) = EventKind::MethodExitWithReturnValue as u8, + + /// Notification that a thread in the target VM is attempting to enter a + /// monitor that is already acquired by another thread. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorContendedEnter( + /// Request that generated the event + RequestID, + /// Thread which is trying to enter the monitor + D::Thread, + /// Monitor object reference + D::TaggedObject, + /// Location of contended monitor enter + Location, + ) = EventKind::MonitorContendedEnter as u8, + + /// Notification of a thread in the target VM is entering a monitor + /// after waiting for it to be released by another thread. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorContendedEntered( + /// Request that generated the event + RequestID, + /// Thread which entered monitor + D::Thread, + /// Monitor object reference + D::TaggedObject, + /// Location of contended monitor enter + Location, + ) = EventKind::MonitorContendedEntered as u8, + + /// Notification of a thread about to wait on a monitor object. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorWait( + /// Request that generated the event + RequestID, + /// Thread which is about to wait + D::Thread, + /// Monitor object reference + D::TaggedObject, + /// Location at which the wait will occur + Location, + /// Thread wait time in milliseconds + u64, + ) = EventKind::MonitorWait as u8, + + /// Notification that a thread in the target VM has finished waiting on + /// a monitor object. + /// + /// Requires `can_request_monitor_events` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + /// + /// Since JDWP version 1.6. + MonitorWaited( + /// Request that generated the event + RequestID, + /// Thread which waited + D::Thread, + /// Monitor object reference + D::TaggedObject, + /// Location at which the wait occurred + Location, + /// True if timed out + bool, + ) = EventKind::MonitorWaited as u8, + + /// Notification of an exception in the target VM. + /// + /// If the exception is thrown from a non-native method, the exception + /// event is generated at the location where the exception is + /// thrown. + /// + /// If the exception is thrown from a native method, the exception event + /// is generated at the first non-native location reached after + /// the exception is thrown. + Exception( + /// Request that generated the event + RequestID, + /// Thread with exception + D::Thread, + /// Location of exception throw (or first non-native location after + /// throw if thrown from a native method) + Location, + /// Thrown exception + D::TaggedObject, + /// Location of catch if caught. + /// + /// An exception is considered to be caught if, at the point of the + /// throw, the current location is dynamically enclosed in a try + /// statement that handles the exception. (See the JVM + /// specification for details). If there is such a try + /// statement, the catch location is the first location in the + /// appropriate catch clause. + /// + /// If there are native methods in the call stack at the time of the + /// exception, there are important restrictions to note about the + /// returned catch location. + /// + /// In such cases, it is not possible to predict whether an + /// exception will be handled by some native method on + /// the call stack. + /// + /// Thus, it is possible that exceptions considered uncaught here + /// will, in fact, be handled by a native method and not + /// cause termination of the target VM. + /// + /// Furthermore, it cannot be assumed that the catch location + /// returned here will ever be reached by the throwing + /// thread. If there is a native frame between the + /// current location and the catch location, the + /// exception might be handled and cleared in that + /// native method instead. + /// + /// Note that compilers can generate try-catch blocks in some cases + /// where they are not explicit in the source code; for example, + /// the code generated for synchronized and finally blocks can + /// contain implicit try-catch blocks. + /// + /// If such an implicitly generated try-catch is present on the call + /// stack at the time of the throw, the exception will be + /// considered caught even though it appears to be uncaught from + /// examination of the source code. + Option, + ) = EventKind::Exception as u8, + + /// Notification of a new running thread in the target VM. + /// + /// The new thread can be the result of a call to + /// `java.lang.Thread.start` or the result of attaching a new + /// thread to the VM though JNI. + /// + /// The notification is generated by the new thread some time before its + /// execution starts. + /// + /// Because of this timing, it is possible to receive other events for + /// the thread before this event is received. + /// + /// (Notably, Method Entry Events and Method Exit Events might occur + /// during thread initialization. + /// + /// It is also possible for the + /// [AllThreads](super::virtual_machine::AllThreads) command to return a + /// thread before its thread start event is received. + /// + /// Note that this event gives no information about the creation of the + /// thread object which may have happened much earlier, depending on the + /// VM being debugged. + ThreadStart( + /// Request that generated the event + RequestID, + /// Started thread + D::Thread, + ) = EventKind::ThreadStart as u8, + + /// Notification of a completed thread in the target VM. + /// + /// The notification is generated by the dying thread before it + /// terminates. + /// + /// Because of this timing, it is possible for + /// [AllThreads](super::virtual_machine::AllThreads) to return this + /// thread after this event is received. + /// + /// Note that this event gives no information about the lifetime of the + /// thread object. + /// + /// It may or may not be collected soon depending on what references + /// exist in the target VM. + ThreadDeath( + /// Request that generated the event + RequestID, + /// Ending thread + D::Thread, + ) = EventKind::ThreadDeath as u8, + + /// Notification of a class prepare in the target VM. + /// + /// See the JVM specification for a definition of class preparation. + /// + /// Class prepare events are not generated for primitive classes + /// (for example, `java.lang.Integer.TYPE`). + ClassPrepare( + /// Request that generated the event + RequestID, + /// Preparing thread. + /// + /// In rare cases, this event may occur in a debugger system thread + /// within the target VM. + /// + /// Debugger threads take precautions to prevent these events, but + /// they cannot be avoided under some conditions, + /// especially for some subclasses of `java.lang.Error`. + /// + /// If the event was generated by a debugger system thread, the + /// value returned by this method is null, and if the + /// requested suspend policy for the event was + /// [EventThread](SuspendPolicy::EventThread) + /// all threads will be suspended instead, and the composite event's + /// suspend policy will reflect this change. + /// + /// Note that the discussion above does not apply to system threads + /// created by the target VM during its normal (non-debug) + /// operation. + D::Thread, + /// Type being prepared + D::TaggedReferenceType, + /// Type signature + String, + /// Status of type + ClassStatus, + ) = EventKind::ClassPrepare as u8, + + /// Notification of a class unload in the target VM. + /// + /// There are severe constraints on the debugger back-end during garbage + /// collection, so unload information is greatly limited. + ClassUnload( + /// Request that generated the event + RequestID, + /// Type signature + String, + ) = EventKind::ClassUnload as u8, + + /// Notification of a field access in the target VM. + /// + /// Field modifications are not considered field accesses. + /// + /// Requires `can_watch_field_access` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + FieldAccess( + /// Request that generated the event + RequestID, + /// Accessing thread + D::Thread, + /// Location of access + Location, + /// Field being accessed + D::Field, + ) = EventKind::FieldAccess as u8, + + /// Notification of a field modification in the target VM. Requires + /// `can_watch_field_modification` capability - see + /// [CapabilitiesNew](super::virtual_machine::CapabilitiesNew). + FieldModification( + /// Request that generated the event + RequestID, + /// Modifying thread + D::Thread, + /// Location of modify + Location, + /// Field being modified + D::Field, + /// Value to be assigned + Value, + ) = EventKind::FieldModification as u8, + + /// Notification of initialization of a target VM. + /// + /// This event is received before the main thread is started and before + /// any application code has been executed. + /// + /// Before this event occurs a significant amount of system code has + /// executed and a number of system classes have been loaded. + /// + /// This event is always generated by the target VM, even if not + /// explicitly requested. + VmStart( + /// Request that generated the event (or None if this event is + /// automatically generated) + Option, + /// Initial thread + D::Thread, + ) = EventKind::VmStart as u8, + + VmDeath( + /// Request that generated the event + Option, + ) = EventKind::VmDeath as u8, + } + + impl Event { + pub fn kind(&self) -> EventKind { + // SAFETY: Self and EventKind fulfill the requirements + unsafe { crate::spec::tag(self) } + } + } + + /// Several events may occur at a given time in the target VM. For example, + /// there may be more than one breakpoint request for a given location or + /// you might single step to the same location as a breakpoint request. + /// These events are delivered together as a composite event. For + /// uniformity, a composite event is always used to deliver events, even if + /// there is only one event to report. + /// + /// The events that are grouped in a composite event are restricted in the + /// following ways: + /// - Only with other thread start events for the same thread: + /// - Thread Start Event + /// - Only with other thread death events for the same thread: + /// - Thread Death Event + /// - Only with other class prepare events for the same class: + /// - Class Prepare Event + /// - Only with other class unload events for the same class: + /// - Class Unload Event + /// - Only with other access watchpoint events for the same field access: + /// - Access Watchpoint Event + /// - Only with other modification watchpoint events for the same field + /// modification: + /// - Modification Watchpoint Event + /// - Only with other Monitor contended enter events for the same monitor + /// object: + /// - Monitor Contended Enter Event + /// - Only with other Monitor contended entered events for the same monitor + /// object: + /// - Monitor Contended Entered Event + /// - Only with other Monitor wait events for the same monitor object: + /// - Monitor Wait Event + /// - Only with other Monitor waited events for the same monitor object: + /// - Monitor Waited Event + /// - Only with other ExceptionEvents for the same exception occurrance: + /// - ExceptionEvent + /// - Only with other members of this group, at the same location and in the + /// same thread: + /// - Breakpoint Event + /// - Step Event + /// - Method Entry Event + /// - Method Exit Event + /// + /// The VM Start Event and VM Death Event are automatically generated + /// events. This means they do not need to be requested using the + /// [event_request::Set] command. The VM Start event signals the completion + /// of VM initialization. The VM Death event signals the termination of + /// the VM.If there is a debugger connected at the time when an + /// automatically generated event occurs it is sent from the target VM. + /// Automatically generated events may also be requested using the + /// [event_request::Set] command and thus multiple events of the same + /// event kind will be sent from the target VM when an event + /// occurs. Automatically generated events are sent with the `request_id` + /// field in the Event Data set to None. The value of the `suspend_policy` + /// field in the Event Data depends on the event. For the automatically + /// generated VM Start Event the value of `suspend_policy` is not defined + /// and is therefore implementation or configuration specific. + /// In the Sun implementation, for example, the `suspend_policy` is + /// specified as an option to the JDWP agent at launch-time.The + /// automatically generated VM Death Event will have the + /// `suspend_policy` set to [None](SuspendPolicy::None). + #[jdwp_command((), 64, 100)] + #[derive(Debug, Clone, JdwpReadable)] + pub struct Composite { + /// Which threads where suspended by this composite event? + pub suspend_policy: SuspendPolicy, + /// Events in set. + pub events: Vec>, + } +} diff --git a/src/spec/constants.rs b/src/spec/constants.rs new file mode 100644 index 0000000..39a6dcc --- /dev/null +++ b/src/spec/constants.rs @@ -0,0 +1,513 @@ +use std::{ + fmt::{Display, Formatter}, + io::{self, Error, ErrorKind, Read, Write}, +}; + +use bitflags::bitflags; + +use crate::codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}; + +macro_rules! jdwp_enum { + ( + #[repr($repr:ident)] + pub enum $e:ident { + $($(#[doc = $string:literal])* $name:ident = $id:literal),* + $(,)? + } + ) => { + #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] + #[repr($repr)] + pub enum $e { + $($(#[doc = $string])* $name = $id,)* + } + + impl TryFrom<$repr> for $e { + type Error = $repr; + + fn try_from(value: $repr) -> Result { + match value { + $($id => Ok($e::$name),)* + other => Err(other), + } + } + } + + impl JdwpReadable for $e { + fn read(read: &mut JdwpReader) -> std::io::Result { + Self::try_from($repr::read(read)?) + .map_err(|_| Error::from(ErrorKind::InvalidData)) + } + } + + impl JdwpWritable for $e { + fn write(&self, write: &mut JdwpWriter) -> std::io::Result<()> { + (*self as $repr).write(write) + } + } + }; + ( + #[derive(Display)] + #[repr($repr:ident)] + pub enum $e:ident { + $(#[doc = $string:literal] $name:ident = $id:literal),* + $(,)? + } + ) => { + jdwp_enum! { + #[repr($repr)] + pub enum $e { + $(#[doc = $string] $name = $id,)* + } + } + + impl Display for $e { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + $($e::$name => $string,)* + }) + } + } + }; + ( + $( + $(#[derive(Display)])? + #[repr($repr:ident)] + pub enum $e:ident { + $(#[doc = $string:literal] $name:ident = $id:literal),* + $(,)? + } + )* + ) => { + $( + jdwp_enum! { + $(#[derive(Display)])? + #[repr($repr)] + pub enum $e { + $(#[doc = $string] $name = $id,)* + } + } + )* + }; +} + +jdwp_enum! { + #[derive(Display)] + #[repr(u16)] + pub enum ErrorCode { + /// No error has occurred + None = 0, + /// Passed thread is null, is not a valid thread or has exited + InvalidThread = 10, + /// Thread group invalid + InvalidThreadGroup = 11, + /// Invalid priority + InvalidPriority = 12, + /// If the specified thread has not been suspended by an event + ThreadNotSuspended = 13, + /// Thread already suspended + ThreadSuspended = 14, + /// Thread has not been started or is now dead + ThreadNotAlive = 15, + /// If this reference type has been unloaded and garbage collected + InvalidObject = 20, + /// Invalid class + InvalidClass = 21, + /// Class has been loaded but not yet prepared + ClassNotPrepared = 22, + /// Invalid method + InvalidMethodid = 23, + /// Invalid location + InvalidLocation = 24, + /// Invalid field + InvalidFieldid = 25, + /// Invalid jframeID + InvalidFrameid = 30, + /// There are no more Java or JNI frames on the call stack + NoMoreFrames = 31, + /// Information about the frame is not available + OpaqueFrame = 32, + /// Operation can only be performed on current frame + NotCurrentFrame = 33, + /// The variable is not an appropriate type for the function used + TypeMismatch = 34, + /// Invalid slot + InvalidSlot = 35, + /// Item already set + Duplicate = 40, + /// Desired element not found + NotFound = 41, + /// Invalid monitor + InvalidMonitor = 50, + /// This thread doesn't own the monitor + NotMonitorOwner = 51, + /// The call has been interrupted before completion + Interrupt = 52, + /// The virtual machine attempted to read a class file and determined that the file is malformed or otherwise cannot be interpreted as a class file + InvalidClassFormat = 60, + /// A circularity has been detected while initializing a class + CircularClassDefinition = 61, + /// The verifier detected that a class file, though well formed, contained some sort of internal inconsistency or security problem + FailsVerification = 62, + /// Adding methods has not been implemented + AddMethodNotImplemented = 63, + /// Schema change has not been implemented + SchemaChangeNotImplemented = 64, + /// The state of the thread has been modified, and is now inconsistent + InvalidTypestate = 65, + /// A direct superclass is different for the new class version, or the set of directly implemented interfaces is different and canUnrestrictedlyRedefineClasses is false + HierarchyChangeNotImplemented = 66, + /// The new class version does not declare a method declared in the old class version and canUnrestrictedlyRedefineClasses is false + DeleteMethodNotImplemented = 67, + /// A class file has a version number not supported by this VM + UnsupportedVersion = 68, + /// The class name defined in the new class file is different from the name in the old class object + NamesDontMatch = 69, + /// The new class version has different modifiers and and canUnrestrictedlyRedefineClasses is false + ClassModifiersChangeNotImplemented = 70, + /// A method in the new class version has different modifiers than its counterpart in the old class version and and canUnrestrictedlyRedefineClasses is false + MethodModifiersChangeNotImplemented = 71, + /// The functionality is not implemented in this virtual machine + NotImplemented = 99, + /// Invalid pointer + NullPointer = 100, + /// Desired information is not available + AbsentInformation = 101, + /// The specified event type id is not recognized + InvalidEventType = 102, + /// Illegal argument + IllegalArgument = 103, + /// The function needed to allocate memory and no more memory was available for allocation + OutOfMemory = 110, + /// Debugging has not been enabled in this virtual machine. JVMTI cannot be used + AccessDenied = 111, + /// The virtual machine is not running + VmDead = 112, + /// An unexpected internal error has occurred + Internal = 113, + /// The thread being used to call this function is not attached to the virtual machine. Calls must be made from attached threads + UnattachedThread = 115, + /// object type id or class tag + InvalidTag = 500, + /// Previous invoke not complete + AlreadyInvoking = 502, + /// Index is invalid + InvalidIndex = 503, + /// The length is invalid + InvalidLength = 504, + /// The string is invalid + InvalidString = 506, + /// The class loader is invalid + InvalidClassLoader = 507, + /// The array is invalid + InvalidArray = 508, + /// Unable to load the transport + TransportLoad = 509, + /// Unable to initialize the transport + TransportInit = 510, + /// NATIVE_METHOD + NativeMethod = 511, + /// The count is invalid + InvalidCount = 512, + } +} + +jdwp_enum! { + #[repr(u8)] + pub enum EventKind { + SingleStep = 1, + Breakpoint = 2, + FramePop = 3, + Exception = 4, + UserDefined = 5, + ThreadStart = 6, + ThreadDeath = 7, + ClassPrepare = 8, + ClassUnload = 9, + ClassLoad = 10, + FieldAccess = 20, + FieldModification = 21, + ExceptionCatch = 30, + MethodEntry = 40, + MethodExit = 41, + MethodExitWithReturnValue = 42, + MonitorContendedEnter = 43, + MonitorContendedEntered = 44, + MonitorWait = 45, + MonitorWaited = 46, + VmStart = 90, + VmDeath = 99, + /// Never sent across JDWP + VmDisconnected = 100, + } +} + +jdwp_enum! { + #[repr(u32)] + pub enum ThreadStatus { + Zombie = 0, + Running = 1, + Sleeping = 2, + Monitor = 3, + Wait = 4, + } +} + +jdwp_enum! { + #[repr(u32)] + pub enum SuspendStatus { + NotSuspended = 0, + Suspended = 1, + } +} + +bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct ClassStatus: u32 { + const VERIFIED = 1; + const PREPARED = 2; + const INITIALIZED = 4; + const ERROR = 8; + + const OK = Self::VERIFIED.bits() | Self::PREPARED.bits() | Self::INITIALIZED.bits(); + } +} + +impl JdwpReadable for ClassStatus { + fn read(read: &mut JdwpReader) -> io::Result { + Self::from_bits(u32::read(read)?).ok_or_else(|| Error::from(ErrorKind::InvalidData)) + } +} + +impl JdwpWritable for ClassStatus { + fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { + self.bits().write(write) + } +} + +pub(crate) trait ByteTag {} + +jdwp_enum! { + #[repr(u8)] + pub enum TypeTag { + /// ReferenceType is a class + Class = 1, + /// ReferenceType is an interface + Interface = 2, + /// ReferenceType is an array + Array = 3, + } +} + +jdwp_enum! { + #[repr(u8)] + pub enum Tag { + /// '[' - an array object ([ObjectID](crate::spec::ObjectID) size). + Array = 91, + /// 'B' - a byte value (1 byte). + Byte = 66, + /// 'C' - a character value (2 bytes). + Char = 67, + /// 'L' - an object ([ObjectID](crate::spec::ObjectID) size). + Object = 76, + /// 'F' - a float value (4 bytes). + Float = 70, + /// 'D' - a double value (8 bytes). + Double = 68, + /// 'I' - an int value (4 bytes). + Int = 73, + /// 'J' - a long value (8 bytes). + Long = 74, + /// 'S' - a short value (2 bytes). + Short = 83, + /// 'V' - a void value (no bytes). + Void = 86, + /// 'Z' - a boolean value (1 byte). + Boolean = 90, + /// 's' - a String object ([ObjectID](crate::spec::ObjectID) size). + String = 115, + /// 't' - a Thread object ([ObjectID](crate::spec::ObjectID) size). + Thread = 116, + /// 'g' - a ThreadGroup object ([ObjectID](crate::spec::ObjectID) size). + ThreadGroup = 103, + /// 'l' - a ClassLoader object ([ObjectID](crate::spec::ObjectID) size). + ClassLoader = 108, + /// 'c' - a class object object ([ObjectID](crate::spec::ObjectID) size). + ClassObject = 99, + } +} + +impl ByteTag for Tag {} +impl ByteTag for TypeTag {} +impl ByteTag for EventKind {} + +impl JdwpReadable for Option +where + T: ByteTag + TryFrom, +{ + fn read(read: &mut JdwpReader) -> io::Result { + Ok(match u8::read(read)? { + 0 => None, + raw => Some(T::try_from(raw).map_err(|_| Error::from(ErrorKind::InvalidData))?), + }) + } +} + +jdwp_enum! { + #[repr(u32)] + pub enum StepDepth { + /// Step into any method calls that occur before the end of the step + Into = 0, + /// Step over any method calls that occur before the end of the step + Over = 1, + /// Step out of the current method + Out = 2, + } +} + +jdwp_enum! { + #[repr(u32)] + pub enum StepSize { + /// Step by the minimum possible amount (often a byte code instruction) + Min = 0, + /// Step to the next source line unless there is no line number + /// information in which case a MIN step is done instead + Line = 1, + } +} + +jdwp_enum! { + #[repr(u8)] + pub enum SuspendPolicy { + /// Suspend no threads when this event is encountered + None = 0, + /// Suspend the event thread when this event is encountered + EventThread = 1, + /// Suspend all threads when this event is encountered + All = 2, + } +} + +bitflags! { + #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] + pub struct InvokeOptions: u32 { + const NONE = 0x00; + /// otherwise, all threads started + const SINGLE_THREADED = 0x01; + /// otherwise, normal virtual invoke (instance methods only) + const NONVIRTUAL = 0x02; + } +} + +impl JdwpReadable for InvokeOptions { + fn read(read: &mut JdwpReader) -> io::Result { + Self::from_bits(u32::read(read)?).ok_or_else(|| Error::from(ErrorKind::InvalidData)) + } +} + +impl JdwpWritable for InvokeOptions { + fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { + self.bits().write(write) + } +} + +jdwp_enum! { + #[repr(u8)] + pub enum ModifierKind { + /// Limit the requested event to be reported at most once after a given + /// number of occurrences. + /// + /// The event is not reported the first count - 1 times this filter is + /// reached. To request a one-off event, call this method with a count + /// of 1. + Count = 1, + /// Conditional on expression + Conditional = 2, + /// Restricts reported events to those in the given thread. + /// This modifier can be used with any event kind except for class + /// unload. + ThreadOnly = 3, + /// For class prepare events, restricts the events generated by this + /// request to be the preparation of the given reference type and any + /// subtypes. + /// + /// For monitor wait and waited events, restricts the events generated + /// by this request to those whose monitor object is of the given + /// reference type or any of its subtypes. + /// + /// For other events, restricts the events generated by this request to + /// those whose location is in the given reference type or any of its + /// subtypes. + /// + /// An event will be generated for any location in a reference type + /// that can be safely cast to the given reference type. + /// + /// This modifier can be used with any event kind except class unload, + /// thread start, and thread end. + ClassOnly = 4, + /// Restricts reported events to those for classes whose name matches + /// the given restricted regular expression. + /// + /// For class prepare events, the prepared class name is matched. + /// + /// For class unload events, the unloaded class name is matched. + /// + /// For monitor wait and waited events, the name of the class of the + /// monitor object is matched. + /// + /// For other events, the class name of the event's location is + /// matched. + /// + /// This modifier can be used with any event kind except thread start + /// and thread end. + ClassMatch = 5, + /// Restricts reported events to those for classes whose name does not + /// match the given restricted regular expression. + /// + /// For class prepare events, the prepared class name is matched. + /// + /// For class unload events, the unloaded class name is matched. + /// + /// For monitor wait and waited events, the name of the class of the + /// monitor object is matched. + /// + /// For other events, the class name of the event's location is + /// matched. + /// + /// This modifier can be used with any event kind except thread start + /// and thread end. + ClassExclude = 6, + /// Restricts reported events to those that occur at the given + /// location. + /// + /// This modifier can be used with breakpoint, field access, field + /// modification, step, and exception event kinds. + LocationOnly = 7, + /// Restricts reported exceptions by their class and whether they are + /// caught or uncaught. + /// + /// This modifier can be used with exception event kinds only. + ExceptionOnly = 8, + /// Restricts reported events to those that occur for a given field. + /// + /// This modifier can be used with field access and field modification + /// event kinds only. + FieldOnly = 9, + /// Restricts reported step events to those which satisfy depth and + /// size constraints. + /// + /// This modifier can be used with step event kinds only. + Step = 10, + /// Restricts reported events to those whose active 'this' object is + /// the given object. Match value is the null object for static + /// methods. + /// + /// This modifier can be used with any event kind except class prepare, + /// class unload, thread start, and thread end. + /// + /// Introduced in JDWP version 1.4. + InstanceOnly = 11, + + // no jdwp doc, todo write something here I guess lol + SourceNameMatch = 12, + } +} diff --git a/src/spec/mod.rs b/src/spec/mod.rs new file mode 100644 index 0000000..d180037 --- /dev/null +++ b/src/spec/mod.rs @@ -0,0 +1,11 @@ +mod protocol; +pub use protocol::*; + +mod types; +pub use types::*; + +mod commands; +pub use commands::*; + +mod constants; +pub use constants::*; diff --git a/src/spec/protocol.rs b/src/spec/protocol.rs new file mode 100644 index 0000000..ff840a2 --- /dev/null +++ b/src/spec/protocol.rs @@ -0,0 +1,68 @@ +use crate::{ + codec::{JdwpReadable, JdwpWritable}, + spec::ErrorCode, +}; +use std::{fmt, fmt::Display}; + +pub trait Command { + const ID: CommandId; + + type Output; +} + +#[derive(Copy, Clone, Debug, PartialEq, Eq, JdwpReadable, JdwpWritable)] +pub struct CommandId { + command_set: u8, + command: u8, +} + +impl CommandId { + pub(crate) const fn new(command_set: u8, command: u8) -> CommandId { + CommandId { + command_set, + command, + } + } +} + +impl Display for CommandId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}.{}", self.command_set, self.command) + } +} + +#[derive(Debug, Copy, Clone, JdwpReadable, JdwpWritable)] +#[repr(u8)] +pub enum PacketMeta { + Command(CommandId) = 0x00, + Reply(ErrorCode) = 0x80, +} + +#[derive(Debug, Copy, Clone, JdwpReadable, JdwpWritable)] +pub struct PacketHeader { + length: u32, + id: u32, + meta: PacketMeta, +} + +impl PacketHeader { + pub const fn new(length: u32, id: u32, meta: PacketMeta) -> PacketHeader { + PacketHeader { length, id, meta } + } + + pub const fn length(&self) -> u32 { + self.length + } + + pub const fn id(&self) -> u32 { + self.id + } + + pub const fn meta(&self) -> PacketMeta { + self.meta + } +} + +impl PacketHeader { + pub(crate) const JDWP_SIZE: u32 = 4 + 4 + 1 + 2; +} diff --git a/src/types.rs b/src/spec/types.rs similarity index 76% rename from src/types.rs rename to src/spec/types.rs index b76e43b..80db907 100644 --- a/src/types.rs +++ b/src/spec/types.rs @@ -1,16 +1,17 @@ -use crate::{ - codec::{JdwpReadable, JdwpReader, JdwpWritable, JdwpWriter}, - enums::{Tag, TypeTag}, -}; use std::{ - fmt::{Debug, Formatter}, + fmt::{self, Debug}, io::{self, Read, Write}, ops::Deref, }; -use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use byteorder::{ReadBytesExt, WriteBytesExt, BE}; + +use crate::codec::*; + +use super::{ByteTag, Tag, TypeTag}; pub trait JdwpId: Clone + Copy { + /// Type of the underlying raw ID. type Raw; /// Creates an instance of Self from an arbitrary number. @@ -92,7 +93,7 @@ pub struct FrameID(u64); /// and the [ReferenceTypeID] are the same. /// /// A particular reference type will be identified by exactly one ID in JDWP -/// commands and replies throughout its lifetime A [ReferenceTypeID] is not +/// commands and replies throughout its lifetime. A [ReferenceTypeID] is not /// reused to identify a different reference type, regardless of whether the /// referenced class has been unloaded. #[derive(Copy, Clone, PartialEq, Eq, Hash)] @@ -115,7 +116,7 @@ macro_rules! ids { } impl Debug for $tpe { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, concat!(stringify!($tpe), "({})"), self.0) } } @@ -123,14 +124,14 @@ macro_rules! ids { impl JdwpReadable for $tpe { fn read(read: &mut JdwpReader) -> io::Result { let id_size = read.id_sizes.$id as usize; - read.read_uint::(id_size).map($tpe) + read.read_uint::(id_size).map($tpe) } } impl JdwpWritable for $tpe { fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { let id_size = write.id_sizes.$id as usize; - write.write_uint::(self.0, id_size) + write.write_uint::(self.0, id_size) } } @@ -229,15 +230,20 @@ macro_rules! wrapper_ids { } impl Debug for $tpe { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, concat!(stringify!($tpe), "({})"), self.0.0) } } + impl From<$tpe> for $deref { + fn from(id: $tpe) -> $deref { + id.0 + } + } + impl Deref for $tpe { type Target = $deref; - #[inline] fn deref(&self) -> &Self::Target { &self.0 } @@ -275,46 +281,189 @@ wrapper_ids! { } } +/// An opaque type for the event request id, which is represented in JDWP docs +/// as just a raw integer and exists only here in Rust similar to all the other +/// IDs. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +pub struct RequestID(i32); + +impl JdwpId for RequestID { + type Raw = i32; + + fn from_raw(raw: i32) -> Self { + Self(raw) + } + + fn raw(self) -> i32 { + self.0 + } +} + +impl JdwpReadable for Option { + fn read(read: &mut JdwpReader) -> io::Result { + match i32::read(read)? { + 0 => Ok(None), + x => Ok(Some(RequestID(x))), + } + } +} + +/// SAFETY: +/// T must be a #[repr(u8)] enum and all variants must have explicit +/// discriminators that are valid tag values. +pub(crate) unsafe fn tag(e: &T) -> U { + *(e as *const T as *const U) +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(u8)] +pub enum TaggedObjectID { + /// an array object + Array(ArrayID) = Tag::Array as u8, + /// an object + Object(ObjectID) = Tag::Object as u8, + /// a String object + String(StringID) = Tag::String as u8, + /// a Thread object + Thread(ThreadID) = Tag::Thread as u8, + /// a ThreadGroup object + ThreadGroup(ThreadGroupID) = Tag::ThreadGroup as u8, + /// a ClassLoader object + ClassLoader(ClassLoaderID) = Tag::ClassLoader as u8, + /// a class object object + ClassObject(ClassObjectID) = Tag::ClassObject as u8, +} + +impl TaggedObjectID { + pub fn tag(&self) -> Tag { + // SAFETY: Self and Tag fulfill the requirements + unsafe { tag(self) } + } +} + +impl Deref for TaggedObjectID { + type Target = ObjectID; + + fn deref(&self) -> &Self::Target { + use TaggedObjectID::*; + match self { + Array(id) => id, + Object(id) => id, + String(id) => id, + Thread(id) => id, + ThreadGroup(id) => id, + ClassLoader(id) => id, + ClassObject(id) => id, + } + } +} + +/// A tagged representation of [ReferenceTypeID], similar to how +/// [TaggedObjectID] is a representation of the [ObjectID]. +/// +/// This construct is not separated into a separate value type in JDWP spec and +/// exists only here in Rust, in JDWP it's usually represented by a pair of +/// [TypeTag] and [ReferenceTypeID] values. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] +#[repr(u8)] +pub enum TaggedReferenceTypeID { + /// a class reference + Class(ClassID) = TypeTag::Class as u8, + /// an interface reference + Interface(InterfaceID) = TypeTag::Interface as u8, + /// an array reference + Array(ArrayTypeID) = TypeTag::Array as u8, +} + +impl TaggedReferenceTypeID { + pub fn tag(&self) -> TypeTag { + // SAFETY: Self and TypeTag fulfill the requirements + unsafe { tag(self) } + } +} + +impl Deref for TaggedReferenceTypeID { + type Target = ReferenceTypeID; + + fn deref(&self) -> &Self::Target { + use TaggedReferenceTypeID::*; + match self { + Class(id) => id, + Interface(id) => id, + Array(id) => id, + } + } +} + /// A value retrieved from the target VM. /// This value can be an [ObjectID] or a primitive value (1 to 8 bytes). -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, JdwpReadable, JdwpWritable)] +#[repr(u8)] pub enum Value { - /// a void value (no bytes) - Void, /// a byte value (1 byte) - Byte(u8), + Byte(u8) = Tag::Byte as u8, /// a boolean value (1 byte) - Boolean(bool), + Boolean(bool) = Tag::Boolean as u8, /// a character value (2 bytes) - Char(u16), + Char(u16) = Tag::Char as u8, /// a short value (2 bytes) - Short(i16), + Short(i16) = Tag::Short as u8, /// an int value (4 bytes) - Int(i32), + Int(i32) = Tag::Int as u8, /// a long value (8 bytes) - Long(i64), + Long(i64) = Tag::Long as u8, /// a float value (4 bytes) - Float(f32), + Float(f32) = Tag::Float as u8, /// a double value (8 bytes) - Double(f64), + Double(f64) = Tag::Double as u8, /// an object ([ObjectID] size) - Object(ObjectID), + Object(ObjectID) = Tag::Object as u8, } impl Value { - pub fn tag(self) -> Tag { - match self { - Value::Void => Tag::Void, - Value::Byte(_) => Tag::Byte, - Value::Boolean(_) => Tag::Boolean, - Value::Char(_) => Tag::Char, - Value::Short(_) => Tag::Short, - Value::Int(_) => Tag::Int, - Value::Long(_) => Tag::Long, - Value::Float(_) => Tag::Float, - Value::Double(_) => Tag::Double, - Value::Object(_) => Tag::Object, - } + pub fn tag(&self) -> Tag { + // SAFETY: Self and Tag fulfill the requirements + unsafe { tag(self) } + } +} + +pub trait JdwpValue: JdwpWritable { + fn tagged(self) -> Value; +} + +macro_rules! jvm_values { + ($($tpe:ty => $tagged:ident)*) => { + $( + impl JdwpValue for $tpe { + fn tagged(self) -> Value { + Value::$tagged(self.into()) + } + } + )* + }; +} + +jvm_values! { + u8 => Byte + bool => Boolean + u16 => Char + i16 => Short + i32 => Int + i64 => Long + f32 => Float + f64 => Double + ObjectID => Object + ThreadID => Object + ThreadGroupID => Object + StringID => Object + ClassLoaderID => Object + ClassObjectID => Object + ArrayID => Object +} + +impl From for Value { + fn from(value: T) -> Self { + value.tagged() } } @@ -332,6 +481,12 @@ impl From for UntaggedValue { } } +impl From for UntaggedValue { + fn from(value: T) -> Self { + Self(value.tagged()) + } +} + impl Deref for UntaggedValue { type Target = Value; @@ -343,7 +498,6 @@ impl Deref for UntaggedValue { impl JdwpWritable for UntaggedValue { fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { match self.0 { - Value::Void => Ok(()), Value::Byte(v) => v.write(write), Value::Boolean(v) => v.write(write), Value::Char(v) => v.write(write), @@ -357,131 +511,25 @@ impl JdwpWritable for UntaggedValue { } } -macro_rules! tagged_jdwp_io { - ($enum:ident <-> $tag:ident, $($tpe:ident),* { $($read_extras:tt)* } { $($write_extras:tt)* }) => { - impl JdwpReadable for $enum { - fn read(read: &mut JdwpReader) -> io::Result { - match $tag::read(read)? { - $($tag::$tpe => JdwpReadable::read(read).map(Self::$tpe),)* - $($read_extras)* - } - } - } - - impl JdwpWritable for $enum { - fn write(&self, write: &mut JdwpWriter) -> io::Result<()> { - match self { - $(Self::$tpe(v) => { - $tag::$tpe.write(write)?; - v.write(write) - },)* - $($write_extras)* - } - } - } - }; - ($($tt:tt)*) => { tagged_jdwp_io!($($tt)* {} {}); } -} - -tagged_jdwp_io! { - Value <-> Tag, - Byte, Boolean, Char, Int, Short, Long, Float, Double, Object - { - Tag::Void => Ok(Value::Void), - _ => Err(io::Error::from(io::ErrorKind::InvalidData)) - } - { Self::Void => Ok(()) } -} - -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum TaggedObjectID { - /// an array object - Array(ArrayID), - /// an object - Object(ObjectID), - /// a String object - String(StringID), - /// a Thread object - Thread(ThreadID), - /// a ThreadGroup object - ThreadGroup(ThreadGroupID), - /// a ClassLoader object - ClassLoader(ClassLoaderID), - /// a class object object - ClassObject(ClassObjectID), -} - -impl TaggedObjectID { - pub fn tag(self) -> Tag { - use TaggedObjectID::*; - match self { - Array(_) => Tag::Array, - Object(_) => Tag::Object, - String(_) => Tag::String, - Thread(_) => Tag::Thread, - ThreadGroup(_) => Tag::ThreadGroup, - ClassLoader(_) => Tag::ClassLoader, - ClassObject(_) => Tag::ClassObject, - } - } - - pub fn decompose(self) -> (Tag, ObjectID) { - (self.tag(), *self) - } -} - -impl Deref for TaggedObjectID { - type Target = ObjectID; - - fn deref(&self) -> &Self::Target { - use TaggedObjectID::*; - match self { - Array(id) => id, - Object(id) => id, - String(id) => id, - Thread(id) => id, - ThreadGroup(id) => id, - ClassLoader(id) => id, - ClassObject(id) => id, - } - } -} - -tagged_jdwp_io! { - TaggedObjectID <-> Tag, - Array, Object, String, Thread, ThreadGroup, ClassLoader, ClassObject - { _ => Err(io::Error::from(io::ErrorKind::InvalidData)) } - {} -} - /// A compact representation of values used with some array operations. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, JdwpReadable, JdwpWritable)] +#[repr(u8)] pub enum ArrayRegion { - Byte(Vec), - Boolean(Vec), - Char(Vec), - Short(Vec), - Int(Vec), - Long(Vec), - Float(Vec), - Double(Vec), - Object(Vec), + Byte(Vec) = Tag::Byte as u8, + Boolean(Vec) = Tag::Boolean as u8, + Char(Vec) = Tag::Char as u8, + Short(Vec) = Tag::Short as u8, + Int(Vec) = Tag::Int as u8, + Long(Vec) = Tag::Long as u8, + Float(Vec) = Tag::Float as u8, + Double(Vec) = Tag::Double as u8, + Object(Vec) = Tag::Object as u8, } impl ArrayRegion { pub fn tag(&self) -> Tag { - use ArrayRegion::*; - match self { - Byte(_) => Tag::Byte, - Boolean(_) => Tag::Boolean, - Char(_) => Tag::Char, - Short(_) => Tag::Short, - Int(_) => Tag::Int, - Long(_) => Tag::Long, - Float(_) => Tag::Float, - Double(_) => Tag::Double, - Object(_) => Tag::Object, - } + // SAFETY: Self and Tag fulfill the requirements + unsafe { tag(self) } } pub fn len(&self) -> usize { @@ -515,62 +563,6 @@ impl ArrayRegion { } } -tagged_jdwp_io! { - ArrayRegion <-> Tag, - Byte, Boolean, Char, Short, Int, Long, Float, Double, Object - { _ => Err(io::Error::from(io::ErrorKind::InvalidData)) } - {} -} - -/// A tagged representation of [ReferenceTypeID], similar to how -/// [TaggedObjectID] is a representation of the [ObjectID]. -/// -/// This construct is not separated into a separate value type in JDWP spec and -/// exists only here in Rust, in JDWP it's usually represented by a pair of -/// [TypeTag] and [ReferenceTypeID] values. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub enum TaggedReferenceTypeID { - /// a class reference - Class(ClassID), - /// an interface reference - Interface(InterfaceID), - /// an array reference - Array(ArrayTypeID), -} - -impl TaggedReferenceTypeID { - pub fn tag(self) -> TypeTag { - use TaggedReferenceTypeID::*; - match self { - Class(_) => TypeTag::Class, - Interface(_) => TypeTag::Interface, - Array(_) => TypeTag::Array, - } - } - - pub fn decompose(self) -> (TypeTag, ReferenceTypeID) { - (self.tag(), *self) - } -} - -impl Deref for TaggedReferenceTypeID { - type Target = ReferenceTypeID; - - fn deref(&self) -> &Self::Target { - use TaggedReferenceTypeID::*; - match self { - Class(id) => &id.0, - Interface(id) => &id.0, - Array(id) => &id.0, - } - } -} - -tagged_jdwp_io! { - TaggedReferenceTypeID <-> TypeTag, - Class, Interface, Array -} - /// An executable location. /// /// The location is identified by one byte type tag followed by a a class_id @@ -605,12 +597,14 @@ impl JdwpReadable for Option { fn read(read: &mut JdwpReader) -> io::Result { use TaggedReferenceTypeID::*; - let Some(tag) = Option::::read(read)? else { return Ok(None); }; + let Some(tag) = Option::::read(read)? else { + return Ok(None); + }; let id = ReferenceTypeID::read(read)?; let res = match tag { - TypeTag::Class => Class(ClassID(id)), - TypeTag::Interface => Interface(InterfaceID(id)), - TypeTag::Array => Array(ArrayTypeID(id)), + TypeTag::Class => Class(JdwpId::from_raw(id.raw())), + TypeTag::Interface => Interface(JdwpId::from_raw(id.raw())), + TypeTag::Array => Array(JdwpId::from_raw(id.raw())), }; Ok(Some(res)) } @@ -618,7 +612,9 @@ impl JdwpReadable for Option { impl JdwpReadable for Option { fn read(read: &mut JdwpReader) -> io::Result { - let Some(reference_id) = Option::::read(read)? else { return Ok(None); }; + let Some(reference_id) = Option::::read(read)? else { + return Ok(None); + }; let res = Location { reference_id, method_id: JdwpReadable::read(read)?, @@ -633,7 +629,9 @@ impl JdwpReadable for Option { use JdwpReadable as R; use Value::*; - let Some(tag) = Option::::read(read)? else { return Ok(None); }; + let Some(tag) = Option::::read(read)? else { + return Ok(None); + }; let res = match tag { Tag::Byte => Byte(R::read(read)?), Tag::Char => Char(R::read(read)?), @@ -655,7 +653,9 @@ impl JdwpReadable for Option { use JdwpReadable as R; use TaggedObjectID::*; - let Some(tag) = Option::::read(read)? else { return Ok(None); }; + let Some(tag) = Option::::read(read)? else { + return Ok(None); + }; let res = match tag { Tag::Array => Array(R::read(read)?), Tag::Object => Object(R::read(read)?), @@ -670,32 +670,6 @@ impl JdwpReadable for Option { } } -/// An opaque type for the request id, which is represented in JDWP docs as just -/// a raw integer and exists only here in Rust similar to all the other IDs. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, JdwpReadable, JdwpWritable)] -pub struct RequestID(i32); - -impl JdwpId for RequestID { - type Raw = i32; - - fn from_raw(raw: i32) -> Self { - Self(raw) - } - - fn raw(self) -> i32 { - self.0 - } -} - -impl JdwpReadable for Option { - fn read(read: &mut JdwpReader) -> io::Result { - match i32::read(read)? { - 0 => Ok(None), - x => Ok(Some(RequestID(x))), - } - } -} - /// A response from 3 types of invoke method commands: virtual, static and /// interface static. The response is either a value or an exception - we model /// it with a enum for better ergonomics. diff --git a/tests/common.rs b/tests/common.rs index 053f326..c75a1c6 100644 --- a/tests/common.rs +++ b/tests/common.rs @@ -8,33 +8,42 @@ use std::{ process::{Child, Command, Stdio}, }; -use jdwp::client::JdwpClient; +use jdwp::{client::JdwpClient, highlevel::VM}; use lazy_static::lazy_static; pub type Result = std::result::Result>; #[derive(Debug)] -pub struct JvmHandle { +pub struct JvmHandle { pub jvm_process: Child, - jdwp_client: JdwpClient, port: u16, + value: Option, } -impl Deref for JvmHandle { - type Target = JdwpClient; +impl JvmHandle { + // only used in the vm exit test where the vm is taken by value + // but other tests including the common value flag this as unused + #[allow(unused)] + pub fn take(&mut self) -> T { + self.value.take().expect("Value was moved out") + } +} + +impl Deref for JvmHandle { + type Target = T; fn deref(&self) -> &Self::Target { - &self.jdwp_client + self.value.as_ref().expect("Value was moved out") } } -impl DerefMut for JvmHandle { +impl DerefMut for JvmHandle { fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.jdwp_client + self.value.as_mut().expect("Value was moved out") } } -impl Drop for JvmHandle { +impl Drop for JvmHandle { fn drop(&mut self) { match self.jvm_process.kill() { Ok(_) => {} @@ -103,7 +112,7 @@ fn ensure_fixture_is_compiled(fixture: &str) -> Result<(String, String)> { Ok((dir, capitalized)) } -pub fn launch_and_attach(fixture: &str) -> Result { +fn launch(fixture: &str) -> Result<(Child, u16)> { // ensure the logger was init let _ = env_logger::builder() .is_test(true) @@ -113,12 +122,11 @@ pub fn launch_and_attach(fixture: &str) -> Result { let (classpath, class_name) = ensure_fixture_is_compiled(fixture)?; let port = TcpListener::bind(("localhost", 0))?.local_addr()?.port(); - log::info!("Starting a JVM with JDWP port: {}", port); + log::info!("Starting a JVM with JDWP port: {port}"); let mut jvm_process = Command::new("java") .arg(format!( - "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address={}", - port + "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address={port}", )) .args(["-cp", &classpath, &class_name]) .stdout(Stdio::piped()) @@ -137,12 +145,29 @@ pub fn launch_and_attach(fixture: &str) -> Result { // "up" is printed by the java fixture class assert_eq!(stdout.next().unwrap()?, "up"); - let jdwp_client = JdwpClient::attach(("localhost", port)).expect("Can't connect to the JVM"); + Ok((jvm_process, port)) +} + +#[allow(unused)] // it's flagged as unused in test binaries that don't use it ¯\_(ツ)_/¯ +pub fn launch_and_attach(fixture: &str) -> Result> { + let (jvm_process, port) = launch(fixture)?; + let client = JdwpClient::connect(("localhost", port)).expect("Can't connect to the JVM"); + Ok(JvmHandle { + jvm_process, + port, + value: Some(client), + }) +} + +#[allow(unused)] // ditto +pub fn launch_and_attach_vm(fixture: &str) -> Result> { + let (jvm_process, port) = launch(fixture)?; + let vm = VM::connect(("localhost", port)).expect("Can't connect to the JVM"); Ok(JvmHandle { - jdwp_client, jvm_process, port, + value: Some(vm), }) } @@ -179,7 +204,7 @@ macro_rules! assert_snapshot { ($e:expr, @$lit:literal) => { insta::with_settings!({ filters => vec![ - (r"(?:ClassLoader|Field|Method|Object|Class|Interface|ArrayType)ID\(\d+\)", "[opaque_id]"), + (r"((?:ClassLoader|Field|Method|Object|Class|Interface|ArrayType)ID)\(\d+\)", "$1(opaque)"), ] }, { insta::assert_debug_snapshot!($e, @$lit); diff --git a/tests/derive_coverage.rs b/tests/derive_coverage.rs index 86e356b..6a14033 100644 --- a/tests/derive_coverage.rs +++ b/tests/derive_coverage.rs @@ -1,23 +1,21 @@ use insta::assert_snapshot; -use jdwp::{ - enums::{EventKind, InvokeOptions, SuspendPolicy, Tag}, - event_modifier::Modifier, - types::{JdwpId, TaggedObjectID, Value}, +use jdwp::spec::{ + EventKind, InvokeOptions, JdwpId, ObjectID, SuspendPolicy, Tag, TaggedObjectID, Value, }; +use std::fmt::Write; macro_rules! debug_and_clone { ($($p:ident::{$($e:expr,)*},)*) => {{ let mut s = String::new(); $( - s.push_str(stringify!($p)); - s.push_str("::{\n"); + writeln!(s, "{}::{{", stringify!($p)).unwrap(); $( - s.push_str(&format!(" {:?}\n", { - use jdwp::commands::$p::*; + writeln!(s, " {:?}", { + use jdwp::spec::$p::*; $e.clone() - })); + }).unwrap(); )* - s.push_str("}\n"); + writeln!(s, "}}").unwrap(); )* s }} @@ -33,7 +31,7 @@ where } #[test] -fn manually_cover_clone_and_debug() { +fn manually_cover_debug_and_clone() { let all_commands = debug_and_clone![ virtual_machine::{ Version, @@ -77,13 +75,13 @@ fn manually_cover_clone_and_debug() { SignatureWithGeneric::new(id()), FieldsWithGeneric::new(id()), MethodsWithGeneric::new(id()), - Instances::new(id(), 10), + Instances::new(id(), InstanceLimit::limit(10)), ClassFileVersion::new(id()), ConstantPool::new(id()), }, class_type::{ Superclass::new(id()), - SetValues::new(id(), &[(id(), Value::Int(123).into()), (id(), Value::Object(id()).into())]), + SetValues::new(id(), &[(id(), 123.into()), (id(), id::().into())]), InvokeMethod::new(id(), id(), id(), &[Value::Int(123), Value::Object(id())], InvokeOptions::NONE), NewInstance::new(id(), id(), id(), &[Value::Int(123), Value::Object(id())], InvokeOptions::SINGLE_THREADED), }, @@ -105,7 +103,7 @@ fn manually_cover_clone_and_debug() { GetValues::new(id(), vec![id(), id()]), SetValues::new(id(), &[(id(), Value::Int(123).into()), (id(), Value::Object(id()).into())]), MonitorInfo::new(id()), - InvokeMethod::new(id(), id(), id(), id(), &[Value::Int(123), Value::Object(id())], InvokeOptions::NONE), + InvokeMethod::new(id(), id(), (id(), id()), &[Value::Int(123), Value::Object(id())], InvokeOptions::NONE), DisableCollection::new(id()), EnableCollection::new(id()), IsCollected::new(id()), @@ -139,7 +137,7 @@ fn manually_cover_clone_and_debug() { array_reference::{ Length::new(id()), GetValues::new(id(), 0, 10), - SetValues::new(id(), 0, &[Value::Float(123.0).into(), Value::Float(42.0).into()]), + SetValues::new(id(), 0, &[123.0, 42.0]), }, class_loader_reference::{ VisibleClasses::new(id()), @@ -207,7 +205,7 @@ fn manually_cover_clone_and_debug() { SignatureWithGeneric { ref_type: ReferenceTypeID(123) } FieldsWithGeneric { ref_type: ReferenceTypeID(123) } MethodsWithGeneric { ref_type: ReferenceTypeID(123) } - Instances { ref_type: ReferenceTypeID(123), max_instances: 10 } + Instances { ref_type: ReferenceTypeID(123), max_instances: Limit(10) } ClassFileVersion { ref_type: ReferenceTypeID(123) } ConstantPool { ref_type: ReferenceTypeID(123) } } @@ -215,7 +213,7 @@ fn manually_cover_clone_and_debug() { Superclass { class_id: ClassID(123) } SetValues { class_id: ClassID(123), values: [(FieldID(123), UntaggedValue(Int(123))), (FieldID(123), UntaggedValue(Object(ObjectID(123))))] } InvokeMethod { class_id: ClassID(123), thread_id: ThreadID(123), method_id: MethodID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(0x0) } - NewInstance { class_id: ClassID(123), thread_id: ThreadID(123), method_id: MethodID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(NONE | SINGLE_THREADED) } + NewInstance { class_id: ClassID(123), thread_id: ThreadID(123), method_id: MethodID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(SINGLE_THREADED) } } array_type::{ NewInstance { array_type_id: ArrayTypeID(123), length: 10 } @@ -235,14 +233,14 @@ fn manually_cover_clone_and_debug() { GetValues { object: ObjectID(123), fields: [FieldID(123), FieldID(123)] } SetValues { object: ObjectID(123), fields: [(FieldID(123), UntaggedValue(Int(123))), (FieldID(123), UntaggedValue(Object(ObjectID(123))))] } MonitorInfo { object: ObjectID(123) } - InvokeMethod { object: ObjectID(123), thread: ThreadID(123), class: ClassID(123), method: FieldID(123), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(0x0) } + InvokeMethod { object: ObjectID(123), thread: ThreadID(123), method: (ClassID(123), MethodID(123)), arguments: [Int(123), Object(ObjectID(123))], options: InvokeOptions(0x0) } DisableCollection { object: ObjectID(123) } EnableCollection { object: ObjectID(123) } IsCollected { object: ObjectID(123) } ReferringObjects { object: ObjectID(123), max_referrers: 10 } } string_reference::{ - Value { string_object: ObjectID(123) } + Value { string_object: StringID(123) } } thread_reference::{ Name { thread: ThreadID(123) } @@ -269,7 +267,7 @@ fn manually_cover_clone_and_debug() { array_reference::{ Length { array_id: ArrayID(123) } GetValues { array_id: ArrayID(123), first_index: 0, length: 10 } - SetValues { array_id: ArrayID(123), first_index: 0, values: [UntaggedValue(Float(123.0)), UntaggedValue(Float(42.0))] } + SetValues { array_id: ArrayID(123), first_index: 0, values: [123.0, 42.0] } } class_loader_reference::{ VisibleClasses { class_loader_id: ClassLoaderID(123) } diff --git a/tests/event.rs b/tests/event.rs index 811ff30..f9e18d0 100644 --- a/tests/event.rs +++ b/tests/event.rs @@ -1,20 +1,29 @@ -use jdwp::{ - commands::{ - event::Event, - event_request, - reference_type::{Fields, Methods}, - thread_reference, - virtual_machine::{AllThreads, ClassBySignature}, - }, - enums::{EventKind, SuspendPolicy}, - event_modifier::Modifier, - types::Value, +use jdwp::spec::{ + event::Event, + event_request::{Clear, Modifier, Set}, + reference_type::{Fields, Methods}, + thread_reference, + virtual_machine::{AllThreads, ClassBySignature}, + EventKind, SuspendPolicy, Value, }; mod common; use common::Result; +// #[test] +// fn field_modification_new() -> Result { +// let vm = common::launch_and_attach_vm("basic")?; + +// let (ref_type, _) = vm.class_by_signature("LBasic;")?; + +// let main_thread = vm.main_thread()?; +// let ticks = ref_type.field("ticks")?; +// // let tick = ref_type.method("tick")?; + +// Ok(()) +// } + #[test] fn field_modification() -> Result { let mut client = common::launch_and_attach("basic")?; @@ -45,14 +54,14 @@ fn field_modification() -> Result { let field_only = Modifier::FieldOnly(*type_id, ticks.field_id); - let request_id = client.send(event_request::Set::new( + let request_id = client.send(Set::new( EventKind::FieldModification, SuspendPolicy::None, &[field_only], ))?; - match &client.host_events().recv()?.events[..] { - [Event::FieldModification(req_id, tid, loc, (rid, fid), oid, v)] => { + match &client.receive_events().recv()?.events[..] { + [Event::FieldModification(req_id, tid, loc, (rid, fid, oid), v)] => { assert_eq!(*req_id, request_id); // should be modified in main thread @@ -75,10 +84,7 @@ fn field_modification() -> Result { e => panic!("Unexpected event set received: {:#?}", e), } - client.send(event_request::Clear::new( - EventKind::FieldModification, - request_id, - ))?; + client.send(Clear::new(EventKind::FieldModification, request_id))?; Ok(()) } diff --git a/tests/reference_type.rs b/tests/reference_type.rs index b1740a7..f0bc128 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -1,20 +1,12 @@ -use std::{assert_eq, fmt::Debug, io::Cursor}; - use jdwp::{ - client::JdwpClient, - codec::{JdwpReadable, JdwpWritable}, - commands::{ - class_object_reference::ReflectedType, - reference_type::{ - ClassFileVersion, ClassLoader, ClassObject, ConstantPool, Fields, GetValues, Instances, - Interfaces, Methods, Modifiers, NestedTypes, Signature, SourceFile, Status, - }, + highlevel::{JvmObject, ReferenceType, TaggedReferenceType, VM}, + jvm::{ConstantPoolItem, ConstantPoolValue, FieldModifiers}, + spec::{ + reference_type::{ClassFileVersion, ConstantPool, InstanceLimit, Methods}, virtual_machine::ClassBySignature, - Command, }, - jvm::{ConstantPoolItem, ConstantPoolValue, FieldModifiers}, - types::{InterfaceID, ReferenceTypeID, TaggedReferenceTypeID}, }; +use std::{assert_eq, error::Error, io::Cursor, ops::Deref}; mod common; @@ -25,59 +17,51 @@ const ARRAY_CLS: &str = "[I"; const CASES: &[&str] = &[OUR_CLS, "Ljava/lang/String;", "Ljava/util/List;", ARRAY_CLS]; -fn get_responses( - client: &mut JdwpClient, - signatures: &[&str], - new: fn(ReferenceTypeID) -> C, -) -> Result> -where - C: Command + JdwpWritable + Debug, - C::Output: JdwpReadable + Debug, -{ - signatures - .iter() - .map(|item| { - let (type_id, _) = *client.send(ClassBySignature::new(*item))?; - Ok(client.send(new(*type_id))?) - }) - .collect() -} - -trait GetSignature { - fn get_signature(&self, client: &mut JdwpClient) -> Result; +trait VmExt { + fn call_for_types( + &self, + signatures: &[&str], + call: fn(TaggedReferenceType) -> std::result::Result, + ) -> Result>; } -impl GetSignature for TaggedReferenceTypeID { - fn get_signature(&self, client: &mut JdwpClient) -> Result { - let sig = client.send(Signature::new(**self))?; - Ok(format!("{:?}({sig})", self.tag())) +impl VmExt for VM { + fn call_for_types( + &self, + signatures: &[&str], + call: fn(TaggedReferenceType) -> std::result::Result, + ) -> Result> { + signatures + .iter() + .map(|item| Ok(call(self.class_by_signature(item)?.0)?)) + .collect() } } -impl GetSignature for InterfaceID { - fn get_signature(&self, client: &mut JdwpClient) -> Result { - Ok(client.send(Signature::new(**self))?) - } +trait CollExt { + fn signatures(self) -> Result>; } -fn get_signatures(client: &mut JdwpClient, iterable: I) -> Result> +impl CollExt for I where - S: GetSignature, - I: IntoIterator, + I: IntoIterator, + I::Item: Deref, { - let mut sigs = iterable - .into_iter() - .map(|ref_id| ref_id.get_signature(client)) - .collect::>>()?; - sigs.sort_unstable(); - Ok(sigs) + fn signatures(self) -> Result> { + let mut sigs = self + .into_iter() + .map(|ref_type| Ok(ref_type.signature()?)) + .collect::>>()?; + sigs.sort_unstable(); + Ok(sigs) + } } #[test] fn signature() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let signatures = get_responses(&mut client, CASES, Signature::new)?; + let signatures = vm.call_for_types(CASES, |t| t.signature())?; assert_snapshot!(signatures, @r###" [ @@ -92,14 +76,16 @@ fn signature() -> Result { #[test] fn class_loader() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let class_loaders = get_responses(&mut client, CASES, ClassLoader::new)?; + let class_loaders = vm.call_for_types(CASES, |t| t.class_loader())?; assert_snapshot!(class_loaders, @r###" [ Some( - [opaque_id], + WrapperJvmObject( + ClassLoaderID(opaque), + ), ), None, None, @@ -112,9 +98,9 @@ fn class_loader() -> Result { #[test] fn modifiers() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let modifiers = get_responses(&mut client, CASES, Modifiers::new)?; + let modifiers = vm.call_for_types(CASES, |t| t.modifiers())?; assert_snapshot!(modifiers, @r###" [ @@ -138,54 +124,72 @@ fn modifiers() -> Result { #[test] fn fields() -> Result { - let mut client = common::launch_and_attach("basic")?; - - let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - - let mut fields = client.send(Fields::new(*type_id))?; + let vm = common::launch_and_attach_vm("basic")?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; + let mut fields = class_type.fields()?; fields.sort_by_key(|f| f.name.clone()); assert_snapshot!(fields, @r###" [ - Field { - field_id: [opaque_id], + StaticField { name: "running", signature: "LBasic;", - mod_bits: FieldModifiers( + generic_signature: None, + modifiers: FieldModifiers( PUBLIC | STATIC, ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), }, - Field { - field_id: [opaque_id], + StaticField { name: "secondInstance", signature: "LBasic;", - mod_bits: FieldModifiers( + generic_signature: None, + modifiers: FieldModifiers( PUBLIC | STATIC, ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), }, - Field { - field_id: [opaque_id], + StaticField { name: "staticInt", signature: "I", - mod_bits: FieldModifiers( + generic_signature: None, + modifiers: FieldModifiers( STATIC, ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), }, - Field { - field_id: [opaque_id], + StaticField { name: "ticks", signature: "J", - mod_bits: FieldModifiers( + generic_signature: None, + modifiers: FieldModifiers( PUBLIC, ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), }, - Field { - field_id: [opaque_id], + StaticField { name: "unused", signature: "Ljava/lang/String;", - mod_bits: FieldModifiers( + generic_signature: None, + modifiers: FieldModifiers( FINAL, ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), }, ] "###); @@ -205,7 +209,7 @@ fn methods() -> Result { assert_snapshot!(methods, @r###" [ Method { - method_id: [opaque_id], + method_id: MethodID(opaque), name: "", signature: "()V", mod_bits: MethodModifiers( @@ -213,7 +217,7 @@ fn methods() -> Result { ), }, Method { - method_id: [opaque_id], + method_id: MethodID(opaque), name: "", signature: "()V", mod_bits: MethodModifiers( @@ -221,7 +225,7 @@ fn methods() -> Result { ), }, Method { - method_id: [opaque_id], + method_id: MethodID(opaque), name: "getAsInt", signature: "()I", mod_bits: MethodModifiers( @@ -229,7 +233,7 @@ fn methods() -> Result { ), }, Method { - method_id: [opaque_id], + method_id: MethodID(opaque), name: "main", signature: "([Ljava/lang/String;)V", mod_bits: MethodModifiers( @@ -237,7 +241,7 @@ fn methods() -> Result { ), }, Method { - method_id: [opaque_id], + method_id: MethodID(opaque), name: "ping", signature: "(Ljava/lang/Object;)V", mod_bits: MethodModifiers( @@ -245,7 +249,7 @@ fn methods() -> Result { ), }, Method { - method_id: [opaque_id], + method_id: MethodID(opaque), name: "tick", signature: "()V", mod_bits: MethodModifiers( @@ -260,31 +264,29 @@ fn methods() -> Result { #[test] fn get_values() -> Result { - let mut client = common::launch_and_attach("basic")?; - - let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - - let mut fields = client.send(Fields::new(*type_id))?; + let vm = common::launch_and_attach_vm("basic")?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; + let mut fields = class_type.fields()?; fields.sort_by_key(|f| f.name.clone()); let fields = fields .into_iter() .filter_map(|f| { - f.mod_bits + f.modifiers .contains(FieldModifiers::STATIC) - .then_some(f.field_id) + .then_some(f.id()) }) .collect::>(); - let values = client.send(GetValues::new(*type_id, fields))?; + let values = class_type.child(fields).get()?; assert_snapshot!(values, @r###" [ Object( - [opaque_id], + ObjectID(opaque), ), Object( - [opaque_id], + ObjectID(opaque), ), Int( 42, @@ -297,13 +299,12 @@ fn get_values() -> Result { #[test] fn source_file() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let source_files = get_responses( - &mut client, - &[OUR_CLS, "Ljava/lang/String;", "Ljava/util/List;"], - SourceFile::new, - )?; + let source_files = vm + .call_for_types(&[OUR_CLS, "Ljava/lang/String;", "Ljava/util/List;"], |t| { + t.source_file() + })?; assert_snapshot!(source_files, @r###" [ @@ -313,8 +314,8 @@ fn source_file() -> Result { ] "###); - let (type_id, _) = *client.send(ClassBySignature::new(ARRAY_CLS))?; - let array_source_file = client.send(SourceFile::new(*type_id)); + let (array_type, _) = vm.class_by_signature(ARRAY_CLS)?; + let array_source_file = array_type.source_file(); assert_snapshot!(array_source_file, @r###" Err( @@ -329,45 +330,43 @@ fn source_file() -> Result { #[test] fn nested_types() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; - let mut nested_types = client.send(NestedTypes::new(*type_id))?; + let mut nested_types = class_type.nested_types()?; nested_types.sort_by_key(|t| t.tag() as u8); - - let nested_types = get_signatures(&mut client, nested_types)?; + let nested_types = nested_types.signatures()?; assert_snapshot!(nested_types, @r###" [ - "Class(LBasic$NestedClass;)", - "Interface(LBasic$NestedInterface;)", + "LBasic$NestedClass;", + "LBasic$NestedInterface;", ] "###); - let (type_id, _) = *client.send(ClassBySignature::new("Ljava/util/HashMap;"))?; + let (class_type, _) = vm.class_by_signature("Ljava/util/HashMap;")?; - let mut nested_types = client.send(NestedTypes::new(*type_id))?; + let mut nested_types = class_type.nested_types()?; nested_types.sort_by_key(|t| t.tag() as u8); - - let nested_types = get_signatures(&mut client, nested_types)?; + let nested_types = nested_types.signatures()?; assert_snapshot!(nested_types, @r###" [ - "Class(Ljava/util/HashMap$EntryIterator;)", - "Class(Ljava/util/HashMap$EntrySet;)", - "Class(Ljava/util/HashMap$EntrySpliterator;)", - "Class(Ljava/util/HashMap$HashIterator;)", - "Class(Ljava/util/HashMap$HashMapSpliterator;)", - "Class(Ljava/util/HashMap$KeyIterator;)", - "Class(Ljava/util/HashMap$KeySet;)", - "Class(Ljava/util/HashMap$KeySpliterator;)", - "Class(Ljava/util/HashMap$Node;)", - "Class(Ljava/util/HashMap$TreeNode;)", - "Class(Ljava/util/HashMap$UnsafeHolder;)", - "Class(Ljava/util/HashMap$ValueIterator;)", - "Class(Ljava/util/HashMap$ValueSpliterator;)", - "Class(Ljava/util/HashMap$Values;)", + "Ljava/util/HashMap$EntryIterator;", + "Ljava/util/HashMap$EntrySet;", + "Ljava/util/HashMap$EntrySpliterator;", + "Ljava/util/HashMap$HashIterator;", + "Ljava/util/HashMap$HashMapSpliterator;", + "Ljava/util/HashMap$KeyIterator;", + "Ljava/util/HashMap$KeySet;", + "Ljava/util/HashMap$KeySpliterator;", + "Ljava/util/HashMap$Node;", + "Ljava/util/HashMap$TreeNode;", + "Ljava/util/HashMap$UnsafeHolder;", + "Ljava/util/HashMap$ValueIterator;", + "Ljava/util/HashMap$ValueSpliterator;", + "Ljava/util/HashMap$Values;", ] "###); @@ -376,9 +375,9 @@ fn nested_types() -> Result { #[test] fn status() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let statuses = get_responses(&mut client, CASES, Status::new)?; + let statuses = vm.call_for_types(CASES, |t| t.status())?; assert_snapshot!(statuses, @r###" [ @@ -402,11 +401,11 @@ fn status() -> Result { #[test] fn interfaces() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - let interfaces = client.send(Interfaces::new(*type_id))?; - let interfaces = get_signatures(&mut client, interfaces)?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; + let interfaces = class_type.interfaces()?; + let interfaces = interfaces.signatures()?; assert_snapshot!(interfaces, @r###" [ @@ -414,10 +413,9 @@ fn interfaces() -> Result { ] "###); - let (type_id, _) = *client.send(ClassBySignature::new("Ljava/util/ArrayList;"))?; - - let interfaces = client.send(Interfaces::new(*type_id))?; - let interfaces = get_signatures(&mut client, interfaces)?; + let (class_type, _) = vm.class_by_signature("Ljava/util/ArrayList;")?; + let interfaces = class_type.interfaces()?; + let interfaces = interfaces.signatures()?; assert_snapshot!(interfaces, @r###" [ @@ -433,32 +431,36 @@ fn interfaces() -> Result { #[test] fn class_object() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - let class_object = client.send(ClassObject::new(*type_id))?; - let ref_id = client.send(ReflectedType::new(class_object))?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; + let class = class_type.class()?; + let class_type_2 = class.reflected_type()?; - assert_eq!(type_id, ref_id); + assert_eq!(class_type.id(), class_type_2.id()); Ok(()) } #[test] fn instances() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; - let instances = client.send(Instances::new(*type_id, 10))?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; + let instances = class_type.instances(InstanceLimit::limit(10))?; // the running instance and the one in the static field assert_snapshot!(instances, @r###" [ Object( - [opaque_id], + JvmObject( + ObjectID(opaque), + ), ), Object( - [opaque_id], + JvmObject( + ObjectID(opaque), + ), ), ] "###); diff --git a/tests/thread_group_reference.rs b/tests/thread_group_reference.rs index 3c62c51..b4879a9 100644 --- a/tests/thread_group_reference.rs +++ b/tests/thread_group_reference.rs @@ -1,49 +1,38 @@ use std::collections::HashSet; use common::Result; -use jdwp::commands::{ - thread_group_reference::{Children, Name, Parent}, - thread_reference, - virtual_machine::TopLevelThreadGroups, -}; mod common; #[test] fn system_tree_names() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let thread_group_ids = client.send(TopLevelThreadGroups).unwrap(); - assert_eq!(thread_group_ids.len(), 1); - let thread_group = thread_group_ids[0]; + let thread_groups = vm.top_level_thread_groups()?; + assert_eq!(thread_groups.len(), 1); + let thread_group = thread_groups.into_iter().next().unwrap(); - let children = client.send(Children::new(thread_group))?; + let (child_groups, threads) = thread_group.children()?; - let parent_names = children - .child_groups + let parent_names = child_groups .iter() - .map(|id| { - let parent = client - .send(Parent::new(*id))? - .expect("Thread Group Parent was None"); - let name = client.send(Name::new(parent))?; - Ok(name) + .map(|group| { + let parent = group.parent()?.expect("Thread Group Parent was None"); + Ok(parent.name()?) }) .collect::>>()?; - let child_names = children - .child_groups + let child_names = child_groups .iter() - .map(|id| Ok(client.send(Name::new(*id))?)) + .map(|group| Ok(group.name()?)) .collect::>>()? .into_iter() .filter(|name| name != "InnocuousThreadGroup") // not present on jdk8 .collect::>(); - let thread_names = children - .child_threads + let thread_names = threads .iter() - .map(|id| Ok(client.send(thread_reference::Name::new(*id))?)) + .map(|thread| Ok(thread.name()?)) .collect::>>()?; let expected_threads = &["Signal Dispatcher", "Reference Handler", "Finalizer"]; diff --git a/tests/thread_reference.rs b/tests/thread_reference.rs index 0e90afb..bf78447 100644 --- a/tests/thread_reference.rs +++ b/tests/thread_reference.rs @@ -3,17 +3,15 @@ use std::assert_eq; use common::Result; use jdwp::{ client::JdwpClient, - commands::{ + spec::{ reference_type::{Methods, Signature}, - thread_group_reference, thread_reference::{ CurrentContendedMonitor, ForceEarlyReturn, FrameCount, FrameLimit, Frames, Name, - OwnedMonitors, OwnedMonitorsStackDepthInfo, Resume, Status, Suspend, SuspendCount, - ThreadGroup, + OwnedMonitors, OwnedMonitorsStackDepthInfo, Suspend, }, virtual_machine::AllThreads, + TaggedReferenceTypeID, ThreadID, Value, }, - types::{TaggedReferenceTypeID, ThreadID, Value}, }; mod common; @@ -50,16 +48,16 @@ macro_rules! check_host_suspended { #[test] fn suspend_resume_status_and_count() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let main = get_main_thread(&mut client)?; + let main = vm.main_thread()?; - let suspend_count = client.send(SuspendCount::new(main))?; + let suspend_count = main.suspend_count()?; assert_eq!(suspend_count, 0); - client.send(Suspend::new(main))?; + main.suspend()?; - let status = client.send(Status::new(main))?; + let status = main.status()?; assert_snapshot!(status, @r###" ( Sleeping, @@ -67,19 +65,19 @@ fn suspend_resume_status_and_count() -> Result { ) "###); - client.send(Suspend::new(main))?; - client.send(Suspend::new(main))?; - client.send(Suspend::new(main))?; + main.suspend()?; + main.suspend()?; + main.suspend()?; - let suspend_count = client.send(SuspendCount::new(main))?; + let suspend_count = main.suspend_count()?; assert_eq!(suspend_count, 4); - client.send(Resume::new(main))?; - client.send(Resume::new(main))?; - client.send(Resume::new(main))?; - client.send(Resume::new(main))?; + main.resume()?; + main.resume()?; + main.resume()?; + main.resume()?; - let status = client.send(Status::new(main))?; + let status = main.status()?; assert_snapshot!(status, @r###" ( Sleeping, @@ -92,13 +90,9 @@ fn suspend_resume_status_and_count() -> Result { #[test] fn thread_group() -> Result { - let mut client = common::launch_and_attach("basic")?; - let main = get_main_thread(&mut client)?; - - let thread_group = client.send(ThreadGroup::new(main))?; - let name = client.send(thread_group_reference::Name::new(thread_group))?; + let vm = common::launch_and_attach_vm("basic")?; - assert_eq!(name, "main"); + assert_eq!(vm.main_thread()?.group()?.name()?, "main"); Ok(()) } @@ -205,7 +199,7 @@ fn current_contended_monitor() -> Result { assert_snapshot!(current_contended_monitor, @r###" Some( Object( - [opaque_id], + ObjectID(opaque), ), ) "###); diff --git a/tests/virtual_machine.rs b/tests/virtual_machine.rs index bf39693..147a774 100644 --- a/tests/virtual_machine.rs +++ b/tests/virtual_machine.rs @@ -1,9 +1,6 @@ use std::assert_eq; -use jdwp::{ - client::ClientError, - commands::{string_reference::Value, thread_reference, virtual_machine::*}, -}; +use jdwp::{highlevel::JvmObject, spec::virtual_machine::*}; mod common; @@ -18,8 +15,8 @@ const CASES: &[&str] = &[ #[test] fn version() -> Result { - let mut client = common::launch_and_attach("basic")?; - let reply = client.send(Version)?; + let vm = common::launch_and_attach_vm("basic")?; + let reply = vm.version()?; let version = match common::java_version() { 8 => (1, 8), @@ -33,18 +30,20 @@ fn version() -> Result { #[test] fn class_by_signature() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; let classes = CASES .iter() - .map(|&signature| Ok(client.send(ClassBySignature::new(signature))?.0)) + .map(|&signature| Ok(vm.class_by_signature(signature)?)) .collect::>>()?; assert_snapshot!(classes, @r###" [ ( Class( - [opaque_id], + WrapperJvmObject( + ClassID(opaque), + ), ), ClassStatus( VERIFIED | PREPARED | INITIALIZED, @@ -52,7 +51,9 @@ fn class_by_signature() -> Result { ), ( Interface( - [opaque_id], + WrapperJvmObject( + InterfaceID(opaque), + ), ), ClassStatus( VERIFIED | PREPARED | INITIALIZED, @@ -60,7 +61,9 @@ fn class_by_signature() -> Result { ), ( Array( - [opaque_id], + WrapperJvmObject( + ArrayTypeID(opaque), + ), ), ClassStatus( 0x0, @@ -68,7 +71,9 @@ fn class_by_signature() -> Result { ), ( Array( - [opaque_id], + WrapperJvmObject( + ArrayTypeID(opaque), + ), ), ClassStatus( 0x0, @@ -82,14 +87,14 @@ fn class_by_signature() -> Result { #[test] fn all_classes() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let classes = client.send(AllClasses)?; + let classes = vm.all_classes()?; let mut filtered = classes .iter() .filter_map(|c| CASES.contains(&&*c.signature).then_some(c)) .collect::>(); - filtered.sort_unstable_by_key(|c| c.signature.clone()); + filtered.sort_unstable_by_key(|c| &c.signature); // safe to assume that more classes that those are loaded by the JVM assert!(classes.len() > CASES.len()); @@ -97,37 +102,49 @@ fn all_classes() -> Result { assert_snapshot!(filtered, @r###" [ Class { - type_id: Class( - [opaque_id], + object: Class( + WrapperJvmObject( + ClassID(opaque), + ), ), signature: "Ljava/lang/String;", + generic_signature: None, status: ClassStatus( VERIFIED | PREPARED | INITIALIZED, ), }, Class { - type_id: Interface( - [opaque_id], + object: Interface( + WrapperJvmObject( + InterfaceID(opaque), + ), ), signature: "Ljava/util/List;", + generic_signature: None, status: ClassStatus( VERIFIED | PREPARED | INITIALIZED, ), }, Class { - type_id: Array( - [opaque_id], + object: Array( + WrapperJvmObject( + ArrayTypeID(opaque), + ), ), signature: "[I", + generic_signature: None, status: ClassStatus( 0x0, ), }, Class { - type_id: Array( - [opaque_id], + object: Array( + WrapperJvmObject( + ArrayTypeID(opaque), + ), ), signature: "[Ljava/lang/String;", + generic_signature: None, status: ClassStatus( 0x0, ), @@ -140,12 +157,12 @@ fn all_classes() -> Result { #[test] fn all_threads() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let mut thread_names = client - .send(AllThreads)? + let mut thread_names = vm + .all_threads()? .iter() - .map(|id| Ok(client.send(thread_reference::Name::new(*id))?)) + .map(|thread| Ok(thread.name()?)) .collect::>>()?; thread_names.sort_unstable(); @@ -165,20 +182,39 @@ fn all_threads() -> Result { #[test] fn dispose() -> Result { + let mut vm = common::launch_and_attach_vm("basic")?; + + // just a smoke test I guess + vm.take().dispose()?; + + Ok(()) +} + +#[test] +fn dispose_error() -> Result { let mut client = common::launch_and_attach("basic")?; client.send(Dispose)?; - assert!(matches!(client.send(Version), Err(ClientError::Disposed))); + assert_snapshot!((client.send(Version), client.send(Version)), @r###" + ( + Err( + Disposed, + ), + Err( + Disposed, + ), + ) + "###); Ok(()) } #[test] fn id_sizes() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let id_sizes = client.send(IDSizes)?; + let id_sizes = vm.id_sizes()?; // Everything seems to just be 64bit everywhere I test it assert_snapshot!(id_sizes, @r###" @@ -196,39 +232,39 @@ fn id_sizes() -> Result { #[test] fn suspend_resume() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - client.send(Suspend)?; - client.send(Suspend)?; - client.send(Resume)?; - client.send(Resume)?; + // another smoke test/just coverage + vm.suspend()?; + vm.suspend()?; + vm.resume()?; + vm.resume()?; // extra resume should be a no-op - client.send(Resume)?; + vm.resume()?; Ok(()) } #[test] fn exit() -> Result { - let mut client = common::launch_and_attach("basic")?; + let mut vm = common::launch_and_attach_vm("basic")?; - client.send(Exit::new(2))?; + vm.take().exit(2)?; - assert_eq!(client.jvm_process.wait()?.code(), Some(2)); + assert_eq!(vm.jvm_process.wait()?.code(), Some(2)); Ok(()) } #[test] fn string_roundtrip() -> Result { - let mut client = common::launch_and_attach("basic")?; - - let string = "this is a string"; + let vm = common::launch_and_attach_vm("basic")?; - let string_id = client.send(CreateString::new(string))?; + let string = "this is a string with a secret: M日\u{10401}\u{7F}"; - let string_value = client.send(Value::new(*string_id))?; + let jvm_string = vm.create_string(string)?; + let string_value = jvm_string.value()?; assert_eq!(string_value, string); @@ -237,9 +273,9 @@ fn string_roundtrip() -> Result { #[test] fn capabilities() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let capabilities = client.send(Capabilities)?; + let capabilities = vm.capabilities()?; // those seem to be all enabled on JDKs I test with assert_snapshot!(capabilities, @r###" @@ -259,9 +295,9 @@ fn capabilities() -> Result { #[test] fn class_path() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let reply = client.send(ClassPaths)?; + let reply = vm.classpaths()?; assert!(reply .classpaths @@ -273,24 +309,24 @@ fn class_path() -> Result { #[test] fn hold_release_events() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - client.send(HoldEvents)?; - client.send(HoldEvents)?; - client.send(ReleaseEvents)?; - client.send(ReleaseEvents)?; + vm.hold_events()?; + vm.hold_events()?; + vm.release_events()?; + vm.release_events()?; // extra release should be a no-op - client.send(ReleaseEvents)?; + vm.release_events()?; Ok(()) } #[test] fn capabilities_new() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let capabilities = client.send(CapabilitiesNew)?; + let capabilities = vm.capabilities_new()?; // on JDKs I test with this seems to be the case assert_snapshot!(capabilities, @r###" @@ -326,24 +362,26 @@ fn capabilities_new() -> Result { #[test] fn set_default_stratum() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - // atm we have nothing to test so we just check that it doesn't error - client.send(SetDefaultStratum::new("Not Java"))?; + // another smoke, test if this doesn't just error + vm.set_default_stratum("NotJava")?; Ok(()) } #[test] fn instance_counts() -> Result { - let mut client = common::launch_and_attach("basic")?; + let vm = common::launch_and_attach_vm("basic")?; - let (type_id, _) = *client.send(ClassBySignature::new("LBasic;"))?; - let (type_id2, _) = *client.send(ClassBySignature::new("LBasic$NestedClass;"))?; + let (ref_type, _) = vm.class_by_signature("LBasic;")?; + let (ref_type2, _) = vm.class_by_signature("LBasic$NestedClass;")?; - let counts = client.send(InstanceCounts::new(vec![*type_id, *type_id2]))?; + let counts = vm.instance_counts(vec![ref_type.id(), ref_type2.id()])?; + let counts2 = [ref_type.instance_count()?, ref_type2.instance_count()?]; assert_eq!(counts, [2, 0]); + assert_eq!(counts2, [2, 0]); Ok(()) } From 77ab1b5637a8f8543c79aa9a6868dfcb5aa24a04 Mon Sep 17 00:00:00 2001 From: Anton Bulakh Date: Wed, 26 Jul 2023 04:39:22 +0300 Subject: [PATCH 9/9] Add a couple more tests --- src/highlevel/object_reference.rs | 13 +- src/highlevel/reference_type.rs | 23 ++- src/highlevel/vm.rs | 2 +- tests/array.rs | 48 ++++++ tests/fixtures/Basic.java | 9 +- tests/reference_type.rs | 275 +++++++++++++++++++++++++++++- tests/virtual_machine.rs | 89 +++++++++- 7 files changed, 449 insertions(+), 10 deletions(-) create mode 100644 tests/array.rs diff --git a/src/highlevel/object_reference.rs b/src/highlevel/object_reference.rs index c428da1..c80193e 100644 --- a/src/highlevel/object_reference.rs +++ b/src/highlevel/object_reference.rs @@ -6,6 +6,7 @@ use crate::{ class_object_reference::ReflectedType, string_reference::Value, thread_group_reference::{self, Children, Parent}, + virtual_machine::DisposeObjects, ArrayID, ArrayRegion, ClassLoaderID, ClassObjectID, JdwpValue, ObjectID, StringID, Tag, TaggedObjectID, ThreadGroupID, }, @@ -18,7 +19,17 @@ use super::{ pub type ObjectReference = PlainJvmObject; -impl ObjectReference {} +impl ObjectReference { + pub fn dispose_single(&self) -> Result<(), ClientError> { + self.dispose(1) + } + + pub fn dispose(&self, refcount: u32) -> Result<(), ClientError> { + self.client() + .get() + .send(DisposeObjects::new(&[(self.id(), refcount)])) + } +} pub type JvmArray = ExtendedJvmObject; diff --git a/src/highlevel/reference_type.rs b/src/highlevel/reference_type.rs index 73ba90c..6d7c94a 100644 --- a/src/highlevel/reference_type.rs +++ b/src/highlevel/reference_type.rs @@ -132,7 +132,7 @@ impl ReferenceType { .send(SourceDebugExtension::new(self.id())) } - pub fn signature_with_generic(&self) -> Result { + pub fn signature_generic(&self) -> Result { self.client() .get() .send(SignatureWithGeneric::new(self.id())) @@ -236,6 +236,27 @@ impl TaggedReferenceType { // SAFETY: Self and TypeTag fulfill the requirements unsafe { crate::spec::tag(self) } } + + pub fn unwrap_class(self) -> ClassType { + match self { + TaggedReferenceType::Class(class) => class, + _ => panic!("Expected a class"), + } + } + + pub fn unwrap_interface(self) -> InterfaceType { + match self { + TaggedReferenceType::Interface(interface) => interface, + _ => panic!("Expected an interface"), + } + } + + pub fn unwrap_array(self) -> ArrayType { + match self { + TaggedReferenceType::Array(array) => array, + _ => panic!("Expected an array"), + } + } } impl Deref for TaggedReferenceType { diff --git a/src/highlevel/vm.rs b/src/highlevel/vm.rs index 803cefe..4bcd40f 100644 --- a/src/highlevel/vm.rs +++ b/src/highlevel/vm.rs @@ -162,7 +162,7 @@ impl VM { .send(virtual_machine::SetDefaultStratum::new(stratum)) } - pub fn all_classes_with_generic(&self) -> Result, ClientError> { + pub fn all_classes_generic(&self) -> Result, ClientError> { let classes = self.client.get().send(AllClassesWithGeneric)?; let classes = classes .into_iter() diff --git a/tests/array.rs b/tests/array.rs new file mode 100644 index 0000000..fa34bdf --- /dev/null +++ b/tests/array.rs @@ -0,0 +1,48 @@ +use common::Result; + +mod common; + +#[test] +fn length() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + let (class_type, _) = vm.class_by_signature("[I")?; + + let array_type = class_type.unwrap_array(); + let array = array_type.new_instance(10)?; + + assert_eq!(array.length()?, 10); + + Ok(()) +} + +#[test] +fn set_get_values() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + let (class_type, _) = vm.class_by_signature("[I")?; + + let array_type = class_type.unwrap_array(); + let array = array_type.new_instance(10)?; + + array.set_values(0, &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10])?; + + let region = array.get_values(0, 10)?; + + assert_snapshot!(region, @r###" + Int( + [ + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + ], + ) + "###); + + Ok(()) +} diff --git a/tests/fixtures/Basic.java b/tests/fixtures/Basic.java index dbd764a..278d495 100644 --- a/tests/fixtures/Basic.java +++ b/tests/fixtures/Basic.java @@ -1,11 +1,11 @@ import java.util.HashMap; import java.util.function.IntSupplier; -class Basic implements IntSupplier { +class Basic implements IntSupplier { static int staticInt = 42; - public static Basic running = new Basic(); - public static Basic secondInstance = new Basic(); + public static Basic running = new Basic<>(); + public static Basic secondInstance = new Basic<>(); public long ticks = 0; @@ -49,6 +49,9 @@ private static void ping(Object ignored) { // noop lol } + static void withGeneric(int param1, T param2) { + } + class NestedClass { float field; } diff --git a/tests/reference_type.rs b/tests/reference_type.rs index f0bc128..9e41006 100644 --- a/tests/reference_type.rs +++ b/tests/reference_type.rs @@ -2,7 +2,9 @@ use jdwp::{ highlevel::{JvmObject, ReferenceType, TaggedReferenceType, VM}, jvm::{ConstantPoolItem, ConstantPoolValue, FieldModifiers}, spec::{ - reference_type::{ClassFileVersion, ConstantPool, InstanceLimit, Methods}, + reference_type::{ + ClassFileVersion, ConstantPool, InstanceLimit, Methods, MethodsWithGeneric, + }, virtual_machine::ClassBySignature, }, }; @@ -74,6 +76,52 @@ fn signature() -> Result { Ok(()) } +#[test] +fn signature_generic() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + + // String has extra interfaces in java 17 + let signatures = vm.call_for_types(&[OUR_CLS, "Ljava/util/List;", ARRAY_CLS], |t| { + t.signature_generic() + })?; + + assert_snapshot!(signatures, @r###" + [ + SignatureWithGenericReply { + signature: "LBasic;", + generic_signature: "Ljava/lang/Object;Ljava/util/function/IntSupplier;", + }, + SignatureWithGenericReply { + signature: "Ljava/util/List;", + generic_signature: "Ljava/lang/Object;Ljava/util/Collection;", + }, + SignatureWithGenericReply { + signature: "[I", + generic_signature: "", + }, + ] + "###); + + Ok(()) +} + +#[test] +fn source_debug_extension() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + + let result = vm.class_by_signature(OUR_CLS)?.0.source_debug_extension(); + + assert_snapshot!(result, @r###" + Err( + HostError( + AbsentInformation, + ), + ) + "###); + + Ok(()) +} + #[test] fn class_loader() -> Result { let vm = common::launch_and_attach_vm("basic")?; @@ -96,6 +144,37 @@ fn class_loader() -> Result { Ok(()) } +#[test] +fn visible_classes() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + + let (ref_type, _) = vm.class_by_signature(OUR_CLS)?; + let class_loader = ref_type.class_loader()?.unwrap(); + + let visible_classes = class_loader.visible_classes()?; + + let signatures = visible_classes + .iter() + .map(|c| Ok(c.signature()?)) + .collect::>>()?; + + const EXPECTED: &[&str] = &[ + OUR_CLS, + "LBasic$NestedInterface;", + "Ljava/lang/Class;", + "Ljava/lang/ClassLoader;", + "Ljava/lang/Thread;", + "Ljava/lang/System;", + ]; + + assert!( + signatures.iter().any(|s| EXPECTED.contains(&&**s)), + "Visible classes don't contain our expected subset" + ); + + Ok(()) +} + #[test] fn modifiers() -> Result { let vm = common::launch_and_attach_vm("basic")?; @@ -136,7 +215,7 @@ fn fields() -> Result { signature: "LBasic;", generic_signature: None, modifiers: FieldModifiers( - PUBLIC | STATIC, + PUBLIC | STATIC | 0x800, ), object: NestedJvmObject( ReferenceTypeID(2), @@ -148,7 +227,86 @@ fn fields() -> Result { signature: "LBasic;", generic_signature: None, modifiers: FieldModifiers( - PUBLIC | STATIC, + PUBLIC | STATIC | 0x800, + ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), + }, + StaticField { + name: "staticInt", + signature: "I", + generic_signature: None, + modifiers: FieldModifiers( + STATIC, + ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), + }, + StaticField { + name: "ticks", + signature: "J", + generic_signature: None, + modifiers: FieldModifiers( + PUBLIC, + ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), + }, + StaticField { + name: "unused", + signature: "Ljava/lang/String;", + generic_signature: None, + modifiers: FieldModifiers( + FINAL, + ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), + }, + ] + "###); + + Ok(()) +} + +#[test] +fn fields_generic() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; + let mut fields = class_type.fields_generic()?; + fields.sort_by_key(|f| f.name.clone()); + + assert_snapshot!(fields, @r###" + [ + StaticField { + name: "running", + signature: "LBasic;", + generic_signature: Some( + "LBasic;", + ), + modifiers: FieldModifiers( + PUBLIC | STATIC | 0x800, + ), + object: NestedJvmObject( + ReferenceTypeID(2), + FieldID(opaque), + ), + }, + StaticField { + name: "secondInstance", + signature: "LBasic;", + generic_signature: Some( + "LBasic<*>;", + ), + modifiers: FieldModifiers( + PUBLIC | STATIC | 0x800, ), object: NestedJvmObject( ReferenceTypeID(2), @@ -256,6 +414,94 @@ fn methods() -> Result { PUBLIC, ), }, + Method { + method_id: MethodID(opaque), + name: "withGeneric", + signature: "(ILjava/util/function/IntSupplier;)V", + mod_bits: MethodModifiers( + STATIC, + ), + }, + ] + "###); + + Ok(()) +} + +#[test] +fn methods_generic() -> Result { + let mut client = common::launch_and_attach("basic")?; + + let (type_id, _) = *client.send(ClassBySignature::new(OUR_CLS))?; + + let mut methods = client.send(MethodsWithGeneric::new(*type_id))?; + methods.sort_by_key(|f| f.name.clone()); + + assert_snapshot!(methods, @r###" + [ + MethodWithGeneric { + method_id: MethodID(opaque), + name: "", + signature: "()V", + generic_signature: "", + mod_bits: MethodModifiers( + STATIC, + ), + }, + MethodWithGeneric { + method_id: MethodID(opaque), + name: "", + signature: "()V", + generic_signature: "", + mod_bits: MethodModifiers( + 0x0, + ), + }, + MethodWithGeneric { + method_id: MethodID(opaque), + name: "getAsInt", + signature: "()I", + generic_signature: "", + mod_bits: MethodModifiers( + PUBLIC, + ), + }, + MethodWithGeneric { + method_id: MethodID(opaque), + name: "main", + signature: "([Ljava/lang/String;)V", + generic_signature: "", + mod_bits: MethodModifiers( + PUBLIC | STATIC, + ), + }, + MethodWithGeneric { + method_id: MethodID(opaque), + name: "ping", + signature: "(Ljava/lang/Object;)V", + generic_signature: "", + mod_bits: MethodModifiers( + PRIVATE | STATIC, + ), + }, + MethodWithGeneric { + method_id: MethodID(opaque), + name: "tick", + signature: "()V", + generic_signature: "", + mod_bits: MethodModifiers( + PUBLIC, + ), + }, + MethodWithGeneric { + method_id: MethodID(opaque), + name: "withGeneric", + signature: "(ILjava/util/function/IntSupplier;)V", + generic_signature: "(ITT;)V", + mod_bits: MethodModifiers( + STATIC, + ), + }, ] "###); @@ -491,6 +737,22 @@ fn class_file_version() -> Result { Ok(()) } +#[test] +fn superclass() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + let (class_type, _) = vm.class_by_signature(OUR_CLS)?; + + let superclass = class_type.unwrap_class().superclass()?.unwrap(); + let supersuperclass = superclass.superclass()?; + + assert_snapshot!(supersuperclass, @"None"); + + let superclass = superclass.signature()?; + assert_snapshot!(superclass, @r###""Ljava/lang/Object;""###); + + Ok(()) +} + #[test] fn constant_pool() -> Result { let mut client = common::launch_and_attach("basic")?; @@ -581,12 +843,15 @@ fn constant_pool() -> Result { "Utf8(\"()V\")", "Utf8(\"()[Ljava/lang/Class;\")", "Utf8(\"(I)V\")", + "Utf8(\"(ILjava/util/function/IntSupplier;)V\")", "Utf8(\"(J)V\")", "Utf8(\"(Ljava/lang/Object;)V\")", "Utf8(\"(Ljava/lang/String;)Ljava/lang/Class;\")", "Utf8(\"(Ljava/lang/String;)V\")", "Utf8(\"(Ljava/lang/Throwable;)V\")", "Utf8(\"([Ljava/lang/String;)V\")", + "Utf8(\"(ITT;)V\")", + "Utf8(\"Ljava/lang/Object;Ljava/util/function/IntSupplier;\")", "Utf8(\"\")", "Utf8(\"\")", "Utf8(\"Basic\")", @@ -600,11 +865,14 @@ fn constant_pool() -> Result { "Utf8(\"InnerClasses\")", "Utf8(\"J\")", "Utf8(\"LBasic;\")", + "Utf8(\"LBasic<*>;\")", + "Utf8(\"LBasic;\")", "Utf8(\"LineNumberTable\")", "Utf8(\"Ljava/io/PrintStream;\")", "Utf8(\"Ljava/lang/String;\")", "Utf8(\"NestedClass\")", "Utf8(\"NestedInterface\")", + "Utf8(\"Signature\")", "Utf8(\"SourceFile\")", "Utf8(\"StackMapTable\")", "Utf8(\"exit\")", @@ -636,6 +904,7 @@ fn constant_pool() -> Result { "Utf8(\"ticks\")", "Utf8(\"unused\")", "Utf8(\"up\")", + "Utf8(\"withGeneric\")", ] "###); diff --git a/tests/virtual_machine.rs b/tests/virtual_machine.rs index 147a774..d9447b0 100644 --- a/tests/virtual_machine.rs +++ b/tests/virtual_machine.rs @@ -1,6 +1,9 @@ use std::assert_eq; -use jdwp::{highlevel::JvmObject, spec::virtual_machine::*}; +use jdwp::{ + highlevel::JvmObject, + spec::{reference_type::InstanceLimit, virtual_machine::*}, +}; mod common; @@ -155,6 +158,72 @@ fn all_classes() -> Result { Ok(()) } +#[test] +fn all_classes_generic() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + + const CASES: &[&str] = &[ + // "Ljava/lang/String;", has extra interfaces on jvm 17 + "Ljava/util/List;", + "[I", + "[Ljava/lang/String;", + ]; + + let classes = vm.all_classes_generic()?; + let mut filtered = classes + .iter() + .filter_map(|c| CASES.contains(&&*c.signature).then_some(c)) + .collect::>(); + filtered.sort_unstable_by_key(|c| &c.signature); + + assert!(classes.len() > CASES.len()); + + assert_snapshot!(filtered, @r###" + [ + Class { + object: Interface( + WrapperJvmObject( + InterfaceID(opaque), + ), + ), + signature: "Ljava/util/List;", + generic_signature: Some( + "Ljava/lang/Object;Ljava/util/Collection;", + ), + status: ClassStatus( + VERIFIED | PREPARED | INITIALIZED, + ), + }, + Class { + object: Array( + WrapperJvmObject( + ArrayTypeID(opaque), + ), + ), + signature: "[I", + generic_signature: None, + status: ClassStatus( + 0x0, + ), + }, + Class { + object: Array( + WrapperJvmObject( + ArrayTypeID(opaque), + ), + ), + signature: "[Ljava/lang/String;", + generic_signature: None, + status: ClassStatus( + 0x0, + ), + }, + ] + "###); + + Ok(()) +} + #[test] fn all_threads() -> Result { let vm = common::launch_and_attach_vm("basic")?; @@ -385,3 +454,21 @@ fn instance_counts() -> Result { Ok(()) } + +#[test] +fn dispose_object() -> Result { + let vm = common::launch_and_attach_vm("basic")?; + + let (ref_type, _) = vm.class_by_signature("LBasic;")?; + + let instance = ref_type + .instances(InstanceLimit::limit(1))? + .into_iter() + .next() + .unwrap(); + + // just a smoke too + instance.dispose_single()?; + + Ok(()) +}