Skip to content

Commit

Permalink
Add jdwp streams implemented abstract class
Browse files Browse the repository at this point in the history
[jdwp] make jdwp schema immutable and hashable

[jdwp] make struct serialization synchronous

Codegen dataclassess (facebookexperimental#79)

* Wrote test to validate primitive type mapping

* Added test for type alias definition

* Built dataclasses code generator

* Built test for dataclassess code generator

* Changed to unittest

* Updated struct dataclass function to support all types

* feat: built dataclass generator

* Added copyright

* Added nested struct support

* Added test for nested struct

* Feat: update functions and put it in a class.

* chore: update tests

* Casted types to pass pyre check

* Updated types

* chore: Refactor typing.cast code

* Added copyright

* refactor: update tests to reflect new schema changes

* fix: Fix pyre typing errors

[jdwp] do not emit array length and union tag fields

[jdwp] map union tags and array lengths to int

[jdwp] fix type for union fields

[jdwp] fix names of fields containing spaces

[jdwp] generate projects.jdwp.runtime.structs

[jdwp] add tests importing components of the runtime library

implement async abstract methods, add TCP conn

update files

update conn and streams

add buck deps

add jvm connection and implement jvm streams
  • Loading branch information
michalgr authored and wekesa360 committed Dec 7, 2023
1 parent aaf01d1 commit 215109c
Show file tree
Hide file tree
Showing 17 changed files with 689 additions and 65 deletions.
3 changes: 2 additions & 1 deletion projects/jdwp/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
python_binary(
name = "main",
main = "main.py",
deps = [],
deps = [":lib"],
)


python_library(
name = "lib",
srcs = glob(["**/*.py"]),
visibility = ["PUBLIC"],
deps = [ "//projects/jdwp/runtime:runtime"]
)
9 changes: 9 additions & 0 deletions projects/jdwp/codegen/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,15 @@ python_binary(
visibility=["//projects/jdwp/runtime/..."],
)

python_binary(
name="generate-dataclasses",
main_module="projects.jdwp.codegen.dataclass_generator",
deps=[
":codegen",
],
visibility=["//projects/jdwp/runtime/..."],
)

python_library(
name="codegen",
srcs=glob(["**/*.py"]),
Expand Down
153 changes: 153 additions & 0 deletions projects/jdwp/codegen/dataclass_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.

import enum
from textwrap import dedent
from projects.jdwp.codegen.types import python_type_for
import typing

from projects.jdwp.defs.schema import (
Array,
ArrayLength,
Command,
CommandSet,
Field,
Struct,
TaggedUnion,
UnionTag,
)


StructLink = typing.Tuple[Struct, Field, Struct]


class StructGenerator:
def __init__(self, root: Struct, name: str):
self.__root = root
self.__struct_to_name = compute_struct_names(root, name)

def __get_python_type_for(self, struct: Struct, field: Field) -> str:
type = field.type
match type:
case Struct():
return self.__struct_to_name[type]
case Array():
array_type = typing.cast(Array, type)
return f"typing.List[{self.__struct_to_name[array_type.element_type]}]"
case TaggedUnion():
tagged_union_type = typing.cast(TaggedUnion, type)
union_types = [
self.__struct_to_name[case_struct]
for (_, case_struct) in tagged_union_type.cases
]
union_types_str = ", ".join(union_types)
return f"typing.Union[{union_types_str}]"
case _:
return python_type_for(type)

def __is_explicit_field(self, field: Field) -> bool:
return not isinstance(field.type, (ArrayLength, UnionTag))

def __get_field_name(self, field: Field) -> str:
words = field.name.split(" ")
words = [words[0]] + [word.capitalize() for word in words[1:]]
return "".join(words)

def __generate_dataclass(self, struct: Struct) -> str:
name = self.__struct_to_name[struct]
fields_def = "\n".join(
f" {self.__get_field_name(field)}: {self.__get_python_type_for(struct, field)}"
for field in struct.fields
if self.__is_explicit_field(field)
)
class_def = f"@dataclasses.dataclass(frozen=True)\nclass {name}:\n{fields_def}"
return dedent(class_def)

def generate(self) -> typing.Generator[str, None, None]:
for _, _, nested in reversed(list(nested_structs(self.__root))):
yield self.__generate_dataclass(nested)
yield self.__generate_dataclass(self.__root)


def format_enum_name(enum_value: enum.Enum) -> str:
words = enum_value.name.split("_")
formatted_name = "".join(word.capitalize() for word in words)
return f"{formatted_name}Type"


def nested_structs(root: Struct) -> typing.Generator[StructLink, None, None]:
for field in root.fields:
field_type = field.type
match field_type:
case Array():
array_type = typing.cast(Array, field_type)
yield root, field, array_type.element_type
yield from nested_structs(array_type.element_type)
case TaggedUnion():
tagged_union_type = typing.cast(TaggedUnion, field_type)
for _, struct in tagged_union_type.cases:
yield root, field, struct
yield from nested_structs(struct)
case Struct():
yield root, field, field_type
yield from nested_structs(field_type)


def compute_struct_names(root: Struct, name: str) -> typing.Mapping[Struct, str]:
names = {root: name}
for parent, field, nested in nested_structs(root):
sanitized_field_name = "".join(
word.capitalize() for word in field.name.split(" ")
)
type = field.type
match type:
case Struct():
names[nested] = f"{names[parent]}{sanitized_field_name}"
case Array():
names[nested] = f"{names[parent]}{sanitized_field_name}Element"
case TaggedUnion():
tagged_union_type = typing.cast(TaggedUnion, type)
for case_value, case_struct in tagged_union_type.cases:
case_name = format_enum_name(case_value)
names[
case_struct
] = f"{names[parent]}{sanitized_field_name}Case{case_name}"
return names


def generate_for_command(command: Command) -> typing.Generator[str, None, None]:
if command.out:
yield from StructGenerator(command.out, f"{command.name}Out").generate()
if command.reply:
yield from StructGenerator(command.reply, f"{command.name}Reply").generate()


def generate_for_command_set(
command_set: CommandSet,
) -> typing.Generator[str, None, None]:
for command in command_set.commands:
yield from generate_for_command(command)


def generate_for_all_command_sets() -> typing.Generator[str, None, None]:
# TODO: refactor this once PR90 is merged
from projects.jdwp.defs.command_sets.virtual_machine import VirtualMachine
from projects.jdwp.defs.command_sets.reference_type import ReferenceType
from projects.jdwp.defs.command_sets.event_request import EventRequest

yield from generate_for_command_set(VirtualMachine)
yield from generate_for_command_set(ReferenceType)
yield from generate_for_command_set(EventRequest)


def main():
print("import dataclasses")
print("import typing")
print("from projects.jdwp.runtime.type_aliases import *")

for struct_definition in generate_for_all_command_sets():
print()
print(struct_definition)


if __name__ == "__main__":
main()
11 changes: 9 additions & 2 deletions projects/jdwp/codegen/types.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.

from projects.jdwp.defs.schema import OpaqueType, IdType, IntegralType, Type
from projects.jdwp.defs.schema import (
ArrayLength,
OpaqueType,
IdType,
IntegralType,
Type,
UnionTag,
)
import typing


Expand All @@ -17,7 +24,7 @@ def python_type_for(jdwp_type: Type) -> str:
return __OPAQUE_TYPE_MAPPING[jdwp_type]
case IdType():
return jdwp_type.value[0].upper() + jdwp_type.value[1:] + "Type"
case IntegralType():
case IntegralType() | ArrayLength() | UnionTag():
return "int"
case _:
raise Exception("not implemented")
28 changes: 14 additions & 14 deletions projects/jdwp/defs/command_sets/event_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,20 +81,20 @@
"Modifier cases",
TaggedUnion(
__SetCommand_out_modKind,
{
ModifierKind.COUNT: CountModifier,
ModifierKind.CONDITIONAL: ConditionalModifier,
ModifierKind.THREAD_ONLY: ThreadOnlyModifier,
ModifierKind.CLASS_ONLY: ClassOnlyModifier,
ModifierKind.CLASS_MATCH: ClassMatchModifier,
ModifierKind.CLASS_EXCLUDE: ClassExcludeModifier,
ModifierKind.STEP: StepModifier,
ModifierKind.LOCATION_ONLY: LocationOnlyModifier,
ModifierKind.EXCEPTION_ONLY: ExceptionOnlyModifier,
ModifierKind.FIELD_ONLY: FieldOnlyModifier,
ModifierKind.INSTANCE_ONLY: InstanceOnlyModifier,
ModifierKind.SOURCE_NAME_MATCH: SourceNameMatchModifier,
},
[
(ModifierKind.COUNT, CountModifier),
(ModifierKind.CONDITIONAL, ConditionalModifier),
(ModifierKind.THREAD_ONLY, ThreadOnlyModifier),
(ModifierKind.CLASS_ONLY, ClassOnlyModifier),
(ModifierKind.CLASS_MATCH, ClassMatchModifier),
(ModifierKind.CLASS_EXCLUDE, ClassExcludeModifier),
(ModifierKind.STEP, StepModifier),
(ModifierKind.LOCATION_ONLY, LocationOnlyModifier),
(ModifierKind.EXCEPTION_ONLY, ExceptionOnlyModifier),
(ModifierKind.FIELD_ONLY, FieldOnlyModifier),
(ModifierKind.INSTANCE_ONLY, InstanceOnlyModifier),
(ModifierKind.SOURCE_NAME_MATCH, SourceNameMatchModifier),
],
),
"Modifier cases.",
)
Expand Down
18 changes: 15 additions & 3 deletions projects/jdwp/defs/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from __future__ import annotations
from dataclasses import dataclass
from typing import Optional, Generic, TypeVar, Type as TypeAlias, Union
from typing import Optional, Generic, Tuple, TypeVar, Type as TypeAlias, Union
from collections.abc import Mapping
from enum import Enum
from collections.abc import Set, Sequence
Expand Down Expand Up @@ -41,7 +41,7 @@ class IdType(Enum):
OBJECT_ID = "objectID"
THREAD_ID = "threadID"
THREAD_GROUP_ID = "threadGroupID"
STRING_ID = "stringId"
STRING_ID = "stringID"
CLASS_LOADER_ID = "classLoaderID"
CLASS_OBJECT_ID = "classObjectID"
ARRAY_ID = "arrayID"
Expand Down Expand Up @@ -85,7 +85,10 @@ class TaggedUnion(Generic[EnumT]):
"""Tagged Union class type"""

tag: Field[UnionTag[EnumT]]
cases: Mapping[EnumT, Struct]
cases: Sequence[Tuple[EnumT, Struct]]

def __post_init__(self):
object.__setattr__(self, "cases", tuple(self.cases))


@dataclass(frozen=True)
Expand Down Expand Up @@ -114,6 +117,9 @@ class Struct:

fields: Sequence[Field[Type]]

def __post_init__(self):
object.__setattr__(self, "fields", tuple(self.fields))


@dataclass(frozen=True)
class Command:
Expand All @@ -125,6 +131,9 @@ class Command:
reply: Optional[Struct]
error: Set[ErrorType]

def __post_init__(self):
object.__setattr__(self, "error", frozenset(self.error))


@dataclass(frozen=True)
class CommandSet:
Expand All @@ -133,3 +142,6 @@ class CommandSet:
name: str
id: int
commands: Sequence[Command]

def __post_init__(self):
object.__setattr__(self, "commands", tuple(self.commands))
18 changes: 15 additions & 3 deletions projects/jdwp/main.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.

import asyncio
from projects.jdwp.runtime.jvm_connection import JVMConnection

def main():
return None

async def main():
host = "localhost"
port = 8880

connection = JVMConnection(host, port)

await connection.connect()

await connection.handshake()

await connection.close()


if __name__ == "__main__":
main()
asyncio.run(main())
17 changes: 14 additions & 3 deletions projects/jdwp/runtime/BUCK
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,24 @@ genrule(
cmd = "$(exe //projects/jdwp/codegen:generate-new-types) > $OUT",
)

genrule(
name = "structs",
out = "structs.py",
cmd = "$(exe //projects/jdwp/codegen:generate-dataclasses) > $OUT",
)

python_library(
name = "runtime",
srcs = [
":structs",
":type-aliases",
"async_streams.py",
"jdwpstruct.py",
"jdwp_streams.py",
"jvm_connection.py",
],
visibility = ["PUBLIC", ],


deps = [],
deps = [
"//projects/jdwp/defs:defs",
],
)
Loading

0 comments on commit 215109c

Please sign in to comment.